Skip to content

Commit 020655b

Browse files
nikomatsakisMark-Simulacrum
authored andcommitted
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 947c0de commit 020655b

File tree

4 files changed

+46
-25
lines changed

4 files changed

+46
-25
lines changed

compiler/rustc_infer/src/infer/combine.rs

+5
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,11 @@ impl TypeRelation<'tcx> for Generalizer<'_, 'tcx> {
645645
.type_variables()
646646
.new_var(self.for_universe, Diverging::NotDiverging, origin);
647647
let u = self.tcx().mk_ty_var(new_var_id);
648+
649+
// Record that we replaced `vid` with `new_var_id` as part of a generalization
650+
// operation. This is needed to detect cyclic types. To see why, see the
651+
// docs in the `type_variables` module.
652+
self.infcx.inner.borrow_mut().type_variables().sub(vid, new_var_id);
648653
debug!("generalize: replacing original vid={:?} with new={:?}", vid, u);
649654
Ok(u)
650655
}

compiler/rustc_infer/src/infer/mod.rs

+19-15
Original file line numberDiff line numberDiff line change
@@ -1004,23 +1004,27 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
10041004
param_env: ty::ParamEnv<'tcx>,
10051005
predicate: ty::PolySubtypePredicate<'tcx>,
10061006
) -> Option<InferResult<'tcx, ()>> {
1007-
// Subtle: it's ok to skip the binder here and resolve because
1008-
// `shallow_resolve` just ignores anything that is not a type
1009-
// variable, and because type variable's can't (at present, at
1007+
// Check for two unresolved inference variables, in which case we can
1008+
// make no progress. This is partly a micro-optimization, but it's
1009+
// also an opportunity to "sub-unify" the variables. This isn't
1010+
// *necessary* to prevent cycles, because they would eventually be sub-unified
1011+
// anyhow during generalization, but it helps with diagnostics (we can detect
1012+
// earlier that they are sub-unified).
1013+
//
1014+
// Note that we can just skip the binders here because
1015+
// type variables can't (at present, at
10101016
// least) capture any of the things bound by this binder.
10111017
//
1012-
// NOTE(nmatsakis): really, there is no *particular* reason to do this
1013-
// `shallow_resolve` here except as a micro-optimization.
1014-
// Naturally I could not resist.
1015-
let two_unbound_type_vars = {
1016-
let a = self.shallow_resolve(predicate.skip_binder().a);
1017-
let b = self.shallow_resolve(predicate.skip_binder().b);
1018-
a.is_ty_var() && b.is_ty_var()
1019-
};
1020-
1021-
if two_unbound_type_vars {
1022-
// Two unbound type variables? Can't make progress.
1023-
return None;
1018+
// Note that this sub here is not just for diagnostics - it has semantic
1019+
// effects as well.
1020+
let r_a = self.shallow_resolve(predicate.skip_binder().a);
1021+
let r_b = self.shallow_resolve(predicate.skip_binder().b);
1022+
match (r_a.kind(), r_b.kind()) {
1023+
(&ty::Infer(ty::TyVar(a_vid)), &ty::Infer(ty::TyVar(b_vid))) => {
1024+
self.inner.borrow_mut().type_variables().sub(a_vid, b_vid);
1025+
return None;
1026+
}
1027+
_ => {}
10241028
}
10251029

10261030
Some(self.commit_if_ok(|_snapshot| {

compiler/rustc_infer/src/infer/sub.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,15 @@ impl TypeRelation<'tcx> for Sub<'combine, 'infcx, 'tcx> {
8585
let a = infcx.inner.borrow_mut().type_variables().replace_if_possible(a);
8686
let b = infcx.inner.borrow_mut().type_variables().replace_if_possible(b);
8787
match (a.kind(), b.kind()) {
88-
(&ty::Infer(TyVar(a_vid)), &ty::Infer(TyVar(b_vid))) => {
88+
(&ty::Infer(TyVar(_)), &ty::Infer(TyVar(_))) => {
8989
// Shouldn't have any LBR here, so we can safely put
9090
// this under a binder below without fear of accidental
9191
// capture.
9292
assert!(!a.has_escaping_bound_vars());
9393
assert!(!b.has_escaping_bound_vars());
9494

9595
// can't make progress on `A <: B` if both A and B are
96-
// type variables, so record an obligation. We also
97-
// have to record in the `type_variables` tracker that
98-
// the two variables are equal modulo subtyping, which
99-
// is important to the occurs check later on.
100-
infcx.inner.borrow_mut().type_variables().sub(a_vid, b_vid);
96+
// type variables, so record an obligation.
10197
self.fields.obligations.push(Obligation::new(
10298
self.fields.trace.cause.clone(),
10399
self.fields.param_env,

compiler/rustc_infer/src/infer/type_variable.rs

+20-4
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)