Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[beta] backport fix for #40951 #41269

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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