Skip to content

Commit

Permalink
backport solution for rust-lang#40951
Browse files Browse the repository at this point in the history
  • Loading branch information
nikomatsakis committed Apr 13, 2017
1 parent ca3d118 commit b0345d1
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 40 deletions.
43 changes: 30 additions & 13 deletions src/librustc/infer/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,16 @@ impl<'infcx, 'gcx, 'tcx> CombineFields<'infcx, 'gcx, 'tcx> {
Ok(())
}

/// Attempts to generalize `ty` for the type variable `for_vid`. This checks for cycle -- that
/// is, whether the type `ty` references `for_vid`. If `make_region_vars` is true, it will also
/// replace all regions with fresh variables. Returns `TyError` in the case of a cycle, `Ok`
/// Attempts to generalize `ty` for the type variable `for_vid`.
/// This checks for cycle -- that is, whether the type `ty`
/// references `for_vid`. If `make_region_vars` is true, it will
/// also replace all regions/unbound-type-variables with fresh
/// variables. Returns `TyError` in the case of a cycle, `Ok`
/// otherwise.
///
/// Preconditions:
///
/// - `for_vid` is a "root vid"
fn generalize(&self,
ty: Ty<'tcx>,
for_vid: ty::TyVid,
Expand All @@ -275,7 +281,7 @@ impl<'infcx, 'gcx, 'tcx> CombineFields<'infcx, 'gcx, 'tcx> {
let mut generalize = Generalizer {
infcx: self.infcx,
span: self.trace.cause.span,
for_vid: for_vid,
for_vid_sub_root: self.infcx.type_variables.borrow_mut().sub_root_var(for_vid),
make_region_vars: make_region_vars,
cycle_detected: false
};
Expand All @@ -291,7 +297,7 @@ impl<'infcx, 'gcx, 'tcx> CombineFields<'infcx, 'gcx, 'tcx> {
struct Generalizer<'cx, 'gcx: 'cx+'tcx, 'tcx: 'cx> {
infcx: &'cx InferCtxt<'cx, 'gcx, 'tcx>,
span: Span,
for_vid: ty::TyVid,
for_vid_sub_root: ty::TyVid,
make_region_vars: bool,
cycle_detected: bool,
}
Expand All @@ -303,17 +309,17 @@ impl<'cx, 'gcx, 'tcx> ty::fold::TypeFolder<'gcx, 'tcx> for Generalizer<'cx, 'gcx

fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
// Check to see whether the type we are genealizing references
// `vid`. At the same time, also update any type variables to
// the values that they are bound to. This is needed to truly
// check for cycles, but also just makes things readable.
//
// (In particular, you could have something like `$0 = Box<$1>`
// where `$1` has already been instantiated with `Box<$0>`)
// any other type variable related to `vid` via
// subtyping. This is basically our "occurs check", preventing
// us from creating infinitely sized types.
match t.sty {
ty::TyInfer(ty::TyVar(vid)) => {
let mut variables = self.infcx.type_variables.borrow_mut();
let vid = variables.root_var(vid);
if vid == self.for_vid {
let sub_vid = variables.sub_root_var(vid);
if sub_vid == self.for_vid_sub_root {
// If sub-roots are equal, then `for_vid` and
// `vid` are related via subtyping.
self.cycle_detected = true;
self.tcx().types.err
} else {
Expand All @@ -322,7 +328,18 @@ impl<'cx, 'gcx, 'tcx> ty::fold::TypeFolder<'gcx, 'tcx> for Generalizer<'cx, 'gcx
drop(variables);
self.fold_ty(u)
}
None => t,
None => {
if self.make_region_vars {
let origin = variables.origin(vid);
let new_var_id = variables.new_var(false, origin, None);
let u = self.tcx().mk_var(new_var_id);
debug!("generalize: replacing original vid={:?} with new={:?}",
vid, u);
u
} else {
t
}
}
}
}
}
Expand Down
67 changes: 45 additions & 22 deletions src/librustc/infer/fudge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use ty::{self, TyCtxt};
use infer::type_variable::TypeVariableMap;
use ty::{self, Ty, TyCtxt};
use ty::fold::{TypeFoldable, TypeFolder};

use super::InferCtxt;
Expand Down Expand Up @@ -54,57 +55,52 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
/// the actual types (`?T`, `Option<?T`) -- and remember that
/// after the snapshot is popped, the variable `?T` is no longer
/// unified.
///
/// Assumptions:
/// - no new type variables are created during `f()` (asserted
/// below); this simplifies our logic since we don't have to
/// check for escaping type variables
pub fn fudge_regions_if_ok<T, E, F>(&self,
origin: &RegionVariableOrigin,
f: F) -> Result<T, E> where
F: FnOnce() -> Result<T, E>,
T: TypeFoldable<'tcx>,
{
let (region_vars, value) = self.probe(|snapshot| {
let vars_at_start = self.type_variables.borrow().num_vars();
debug!("fudge_regions_if_ok(origin={:?})", origin);

let (type_variables, region_vars, value) = self.probe(|snapshot| {
match f() {
Ok(value) => {
let value = self.resolve_type_vars_if_possible(&value);

// At this point, `value` could in principle refer
// to regions that have been created during the
// snapshot (we assert below that `f()` does not
// create any new type variables, so there
// shouldn't be any of those). Once we exit
// `probe()`, those are going to be popped, so we
// will have to eliminate any references to them.

assert_eq!(self.type_variables.borrow().num_vars(), vars_at_start,
"type variables were created during fudge_regions_if_ok");
// to types/regions that have been created during
// the snapshot. Once we exit `probe()`, those are
// going to be popped, so we will have to
// eliminate any references to them.

let type_variables =
self.type_variables.borrow_mut().types_created_since_snapshot(
&snapshot.type_snapshot);
let region_vars =
self.region_vars.vars_created_since_snapshot(
&snapshot.region_vars_snapshot);

Ok((region_vars, value))
Ok((type_variables, region_vars, value))
}
Err(e) => Err(e),
}
})?;

// At this point, we need to replace any of the now-popped
// region variables that appear in `value` with a fresh region
// variable. We can't do this during the probe because they
// would just get popped then too. =)
// type/region variables that appear in `value` with a fresh
// variable of the appropriate kind. We can't do this during
// the probe because they would just get popped then too. =)

// Micro-optimization: if no variables have been created, then
// `value` can't refer to any of them. =) So we can just return it.
if region_vars.is_empty() {
if type_variables.is_empty() && region_vars.is_empty() {
return Ok(value);
}

let mut fudger = RegionFudger {
infcx: self,
type_variables: &type_variables,
region_vars: &region_vars,
origin: origin
};
Expand All @@ -115,6 +111,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {

pub struct RegionFudger<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
type_variables: &'a TypeVariableMap,
region_vars: &'a Vec<ty::RegionVid>,
origin: &'a RegionVariableOrigin,
}
Expand All @@ -124,6 +121,32 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for RegionFudger<'a, 'gcx, 'tcx> {
self.infcx.tcx
}

fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
match ty.sty {
ty::TyInfer(ty::InferTy::TyVar(vid)) => {
match self.type_variables.get(&vid) {
None => {
// This variable was created before the
// "fudging". Since we refresh all type
// variables to their binding anyhow, we know
// that it is unbound, so we can just return
// it.
debug_assert!(self.infcx.type_variables.borrow_mut().probe(vid).is_none());
ty
}

Some(&origin) => {
// This variable was created during the
// fudging. Recreate it with a fresh variable
// here.
self.infcx.next_ty_var(origin)
}
}
}
_ => ty.super_fold_with(self),
}
}

fn fold_region(&mut self, r: &'tcx ty::Region) -> &'tcx ty::Region {
match *r {
ty::ReVar(v) if self.region_vars.contains(&v) => {
Expand Down
90 changes: 86 additions & 4 deletions src/librustc/infer/type_variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,39 @@ use std::cmp::min;
use std::marker::PhantomData;
use std::mem;
use std::u32;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::snapshot_vec as sv;
use rustc_data_structures::unify as ut;

pub struct TypeVariableTable<'tcx> {
values: sv::SnapshotVec<Delegate<'tcx>>,

/// Two variables are unified in `eq_relations` when we have a
/// constraint `?X == ?Y`.
eq_relations: ut::UnificationTable<ty::TyVid>,

/// Two variables are unified in `eq_relations` when we have a
/// constraint `?X <: ?Y` *or* a constraint `?Y <: ?X`. This second
/// table exists only to help with the occurs check. In particular,
/// we want to report constraints like these as an occurs check
/// violation:
///
/// ?1 <: ?3
/// Box<?3> <: ?1
///
/// This works because `?1` and `?3` are unified in the
/// `sub_relations` relation (not in `eq_relations`). Then when we
/// process the `Box<?3> <: ?1` constraint, we do an occurs check
/// on `Box<?3>` and find a potential cycle.
///
/// This is reasonable because, in Rust, subtypes have the same
/// "skeleton" and hence there is no possible type such that
/// (e.g.) `Box<?3> <: ?3` for any `?3`.
sub_relations: ut::UnificationTable<ty::TyVid>,
}

/// Reasons to create a type inference variable
#[derive(Copy, Clone)]
pub enum TypeVariableOrigin {
MiscVariable(Span),
NormalizeProjectionType(Span),
Expand All @@ -42,8 +66,11 @@ pub enum TypeVariableOrigin {
DivergingStmt(Span),
DivergingBlockExpr(Span),
LatticeVariable(Span),
Generalized(ty::TyVid),
}

pub type TypeVariableMap = FxHashMap<ty::TyVid, TypeVariableOrigin>;

struct TypeVariableData<'tcx> {
value: TypeVariableValue<'tcx>,
origin: TypeVariableOrigin,
Expand Down Expand Up @@ -72,6 +99,7 @@ pub struct Default<'tcx> {
pub struct Snapshot {
snapshot: sv::Snapshot,
eq_snapshot: ut::Snapshot<ty::TyVid>,
sub_snapshot: ut::Snapshot<ty::TyVid>,
}

enum UndoEntry<'tcx> {
Expand Down Expand Up @@ -106,6 +134,7 @@ impl<'tcx> TypeVariableTable<'tcx> {
TypeVariableTable {
values: sv::SnapshotVec::new(),
eq_relations: ut::UnificationTable::new(),
sub_relations: ut::UnificationTable::new(),
}
}

Expand Down Expand Up @@ -135,6 +164,7 @@ impl<'tcx> TypeVariableTable<'tcx> {
let a = self.root_var(a);
let b = self.root_var(b);
if a != b {
self.sub_relations.union(a, b);
if dir == EqTo {
// a and b must be equal which we mark in the unification table
let root = self.eq_relations.union(a, b);
Expand Down Expand Up @@ -197,6 +227,7 @@ impl<'tcx> TypeVariableTable<'tcx> {
origin: TypeVariableOrigin,
default: Option<Default<'tcx>>,) -> ty::TyVid {
self.eq_relations.new_key(());
self.sub_relations.new_key(());
let index = self.values.push(TypeVariableData {
value: Bounded { relations: vec![], default: default },
origin: origin,
Expand All @@ -211,15 +242,41 @@ impl<'tcx> TypeVariableTable<'tcx> {
self.values.len()
}

/// Returns the "root" variable of `vid` in the `eq_relations`
/// equivalence table. All type variables that have been equated
/// will yield the same root variable (per the union-find
/// algorithm), so `root_var(a) == root_var(b)` implies that `a ==
/// b` (transitively).
pub fn root_var(&mut self, vid: ty::TyVid) -> ty::TyVid {
self.eq_relations.find(vid)
}

/// Returns the "root" variable of `vid` in the `sub_relations`
/// equivalence table. All type variables that have been are
/// related via equality or subtyping will yield the same root
/// variable (per the union-find algorithm), so `sub_root_var(a)
/// == sub_root_var(b)` implies that:
///
/// exists X. (a <: X || X <: a) && (b <: X || X <: b)
pub fn sub_root_var(&mut self, vid: ty::TyVid) -> ty::TyVid {
self.sub_relations.find(vid)
}

/// True if `a` and `b` have same "sub-root" (i.e., exists some
/// type X such that `forall i in {a, b}. (i <: X || X <: i)`.
pub fn sub_unified(&mut self, a: ty::TyVid, b: ty::TyVid) -> bool {
self.sub_root_var(a) == self.sub_root_var(b)
}

pub fn probe(&mut self, vid: ty::TyVid) -> Option<Ty<'tcx>> {
let vid = self.root_var(vid);
self.probe_root(vid)
}

pub fn origin(&self, vid: ty::TyVid) -> TypeVariableOrigin {
self.values.get(vid.index as usize).origin.clone()
}

/// Retrieves the type of `vid` given that it is currently a root in the unification table
pub fn probe_root(&mut self, vid: ty::TyVid) -> Option<Ty<'tcx>> {
debug_assert!(self.root_var(vid) == vid);
Expand All @@ -245,6 +302,7 @@ impl<'tcx> TypeVariableTable<'tcx> {
Snapshot {
snapshot: self.values.start_snapshot(),
eq_snapshot: self.eq_relations.snapshot(),
sub_snapshot: self.sub_relations.snapshot(),
}
}

Expand All @@ -260,13 +318,37 @@ impl<'tcx> TypeVariableTable<'tcx> {
}
});

self.values.rollback_to(s.snapshot);
self.eq_relations.rollback_to(s.eq_snapshot);
let Snapshot { snapshot, eq_snapshot, sub_snapshot } = s;
self.values.rollback_to(snapshot);
self.eq_relations.rollback_to(eq_snapshot);
self.sub_relations.rollback_to(sub_snapshot);
}

pub fn commit(&mut self, s: Snapshot) {
self.values.commit(s.snapshot);
self.eq_relations.commit(s.eq_snapshot);
let Snapshot { snapshot, eq_snapshot, sub_snapshot } = s;
self.values.commit(snapshot);
self.eq_relations.commit(eq_snapshot);
self.sub_relations.commit(sub_snapshot);
}

/// Returns a map `{V1 -> V2}`, where the keys `{V1}` are
/// ty-variables created during the snapshot, and the values
/// `{V2}` are the root variables that they were unified with,
/// along with their origin.
pub fn types_created_since_snapshot(&mut self, s: &Snapshot) -> TypeVariableMap {
let actions_since_snapshot = self.values.actions_since_snapshot(&s.snapshot);

actions_since_snapshot
.iter()
.filter_map(|action| match action {
&sv::UndoLog::NewElem(index) => Some(ty::TyVid { index: index as u32 }),
_ => None,
})
.map(|vid| {
let origin = self.values.get(vid.index as usize).origin.clone();
(vid, origin)
})
.collect()
}

pub fn types_escaping_snapshot(&mut self, s: &Snapshot) -> Vec<Ty<'tcx>> {
Expand Down
Loading

0 comments on commit b0345d1

Please sign in to comment.