Skip to content

Commit b37daab

Browse files
committed
move the sub-unify check and extend the documentation a bit
I didn't like the sub-unify code executing when a predicate was ENQUEUED, that felt fragile. I would have preferred to move the sub-unify code so that it only occurred during generalization, but that impacted diagnostics, so having it also occur when we process subtype predicates felt pretty reasonable. (I guess we only need one or the other, but I kind of prefer both, since the generalizer ultimately feels like the *right* place to guarantee the properties we want.)
1 parent 448eb37 commit b37daab

File tree

4 files changed

+45
-22
lines changed

4 files changed

+45
-22
lines changed

compiler/rustc_infer/src/infer/combine.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,11 @@ impl TypeRelation<'tcx> for Generalizer<'_, 'tcx> {
641641
.type_variables()
642642
.new_var(self.for_universe, Diverging::NotDiverging, origin);
643643
let u = self.tcx().mk_ty_var(new_var_id);
644+
645+
// Record that we replaced `vid` with `new_var_id` as part of a generalization
646+
// operation. This is needed to detect cyclic types. To see why, see the
647+
// docs in the `type_variables` module.
648+
self.infcx.inner.borrow_mut().type_variables().sub(vid, new_var_id);
644649
debug!("generalize: replacing original vid={:?} with new={:?}", vid, u);
645650
Ok(u)
646651
}

compiler/rustc_infer/src/infer/mod.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -961,23 +961,26 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
961961
param_env: ty::ParamEnv<'tcx>,
962962
predicate: ty::PolySubtypePredicate<'tcx>,
963963
) -> Option<InferResult<'tcx, ()>> {
964-
// Subtle: it's ok to skip the binder here and resolve because
965-
// `shallow_resolve` just ignores anything that is not a type
966-
// variable, and because type variable's can't (at present, at
967-
// least) capture any of the things bound by this binder.
964+
// Check for two unresolved inference variables, in which case we can
965+
// make no progress. This is partly a micro-optimization, but it's
966+
// also an opportunity to "sub-unify" the variables. This isn't
967+
// *necessary* to prevent cycles, because they would eventually be sub-unified
968+
// anyhow during generalization, but it helps with diagnostics (we can detect
969+
// earlier that they are sub-unified).
968970
//
969-
// NOTE(nmatsakis): really, there is no *particular* reason to do this
970-
// `shallow_resolve` here except as a micro-optimization.
971-
// Naturally I could not resist.
972-
let two_unbound_type_vars = {
973-
let a = self.shallow_resolve(predicate.skip_binder().a);
974-
let b = self.shallow_resolve(predicate.skip_binder().b);
975-
a.is_ty_var() && b.is_ty_var()
976-
};
977-
978-
if two_unbound_type_vars {
979-
// Two unbound type variables? Can't make progress.
980-
return None;
971+
// Note that we can just skip the binders here because
972+
// type variables can't (at present, at
973+
// least) capture any of the things bound by this binder.
974+
{
975+
let r_a = self.shallow_resolve(predicate.skip_binder().a);
976+
let r_b = self.shallow_resolve(predicate.skip_binder().b);
977+
match (r_a.kind(), r_b.kind()) {
978+
(&ty::Infer(ty::TyVar(a_vid)), &ty::Infer(ty::TyVar(b_vid))) => {
979+
self.inner.borrow_mut().type_variables().sub(a_vid, b_vid);
980+
return None;
981+
}
982+
_ => {}
983+
}
981984
}
982985

983986
Some(self.commit_if_ok(|_snapshot| {

compiler/rustc_infer/src/infer/sub.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl TypeRelation<'tcx> for Sub<'combine, 'infcx, 'tcx> {
8484
let a = infcx.inner.borrow_mut().type_variables().replace_if_possible(a);
8585
let b = infcx.inner.borrow_mut().type_variables().replace_if_possible(b);
8686
match (a.kind(), b.kind()) {
87-
(&ty::Infer(TyVar(a_vid)), &ty::Infer(TyVar(b_vid))) => {
87+
(&ty::Infer(TyVar(_)), &ty::Infer(TyVar(_))) => {
8888
// Shouldn't have any LBR here, so we can safely put
8989
// this under a binder below without fear of accidental
9090
// capture.
@@ -96,7 +96,6 @@ impl TypeRelation<'tcx> for Sub<'combine, 'infcx, 'tcx> {
9696
// have to record in the `type_variables` tracker that
9797
// the two variables are equal modulo subtyping, which
9898
// is important to the occurs check later on.
99-
infcx.inner.borrow_mut().type_variables().sub(a_vid, b_vid);
10099
self.fields.obligations.push(Obligation::new(
101100
self.fields.trace.cause.clone(),
102101
self.fields.param_env,

compiler/rustc_infer/src/infer/type_variable.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,30 @@ pub struct TypeVariableStorage<'tcx> {
7575
/// ?1 <: ?3
7676
/// Box<?3> <: ?1
7777
///
78-
/// This works because `?1` and `?3` are unified in the
79-
/// `sub_relations` relation (not in `eq_relations`). Then when we
80-
/// process the `Box<?3> <: ?1` constraint, we do an occurs check
81-
/// on `Box<?3>` and find a potential cycle.
78+
/// Without this second table, what would happen in a case like
79+
/// this is that we would instantiate `?1` with a generalized
80+
/// type like `Box<?6>`. We would then relate `Box<?3> <: Box<?6>`
81+
/// and infer that `?3 <: ?6`. Next, since `?1` was instantiated,
82+
/// we would process `?1 <: ?3`, generalize `?1 = Box<?6>` to `Box<?9>`,
83+
/// and instantiate `?3` with `Box<?9>`. Finally, we would relate
84+
/// `?6 <: ?9`. But now that we instantiated `?3`, we can process
85+
/// `?3 <: ?6`, which gives us `Box<?9> <: ?6`... and the cycle
86+
/// continues. (This is `occurs-check-2.rs`.)
87+
///
88+
/// What prevents this cycle is that when we generalize
89+
/// `Box<?3>` to `Box<?6>`, we also sub-unify `?3` and `?6`
90+
/// (in the generalizer). When we then process `Box<?6> <: ?3`,
91+
/// the occurs check then fails because `?6` and `?3` are sub-unified,
92+
/// and hence generalization fails.
8293
///
8394
/// This is reasonable because, in Rust, subtypes have the same
8495
/// "skeleton" and hence there is no possible type such that
8596
/// (e.g.) `Box<?3> <: ?3` for any `?3`.
97+
///
98+
/// In practice, we sometimes sub-unify variables in other spots, such
99+
/// as when processing subtype predicates. This is not necessary but is
100+
/// done to aid diagnostics, as it allows us to be more effective when
101+
/// we guide the user towards where they should insert type hints.
86102
sub_relations: ut::UnificationTableStorage<ty::TyVid>,
87103
}
88104

0 commit comments

Comments
 (0)