Skip to content

Commit 2ee8914

Browse files
nikomatsakisMark-Simulacrum
authored andcommitted
introduce new fallback algorithm
We now fallback type variables using the following rules: * Construct a coercion graph `A -> B` where `A` and `B` are unresolved type variables or the `!` type. * Let D be those variables that are reachable from `!`. * Let N be those variables that are reachable from a variable not in D. * All variables in (D \ N) fallback to `!`. * All variables in (D & N) fallback to `()`.
1 parent e0c38af commit 2ee8914

File tree

7 files changed

+347
-55
lines changed

7 files changed

+347
-55
lines changed

compiler/rustc_infer/src/infer/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,11 +707,17 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
707707
/// No attempt is made to resolve `ty`.
708708
pub fn type_var_diverges(&'a self, ty: Ty<'_>) -> Diverging {
709709
match *ty.kind() {
710-
ty::Infer(ty::TyVar(vid)) => self.inner.borrow_mut().type_variables().var_diverges(vid),
710+
ty::Infer(ty::TyVar(vid)) => self.ty_vid_diverges(vid),
711711
_ => Diverging::NotDiverging,
712712
}
713713
}
714714

715+
/// Returns true if the type inference variable `vid` was created
716+
/// as a diverging type variable. No attempt is made to resolve `vid`.
717+
pub fn ty_vid_diverges(&'a self, vid: ty::TyVid) -> Diverging {
718+
self.inner.borrow_mut().type_variables().var_diverges(vid)
719+
}
720+
715721
/// Returns the origin of the type variable identified by `vid`, or `None`
716722
/// if this is not a type variable.
717723
///
@@ -1070,6 +1076,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
10701076
})
10711077
}
10721078

1079+
/// Number of type variables created so far.
1080+
pub fn num_ty_vars(&self) -> usize {
1081+
self.inner.borrow_mut().type_variables().num_vars()
1082+
}
1083+
10731084
pub fn next_ty_var_id(&self, diverging: Diverging, origin: TypeVariableOrigin) -> TyVid {
10741085
self.inner.borrow_mut().type_variables().new_var(self.universe(), diverging, origin)
10751086
}

compiler/rustc_middle/src/ty/sty.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,6 +1672,14 @@ impl<'tcx> TyS<'tcx> {
16721672
matches!(self.kind(), Infer(TyVar(_)))
16731673
}
16741674

1675+
#[inline]
1676+
pub fn ty_vid(&self) -> Option<ty::TyVid> {
1677+
match self.kind() {
1678+
&Infer(TyVar(vid)) => Some(vid),
1679+
_ => None,
1680+
}
1681+
}
1682+
16751683
#[inline]
16761684
pub fn is_ty_infer(&self) -> bool {
16771685
matches!(self.kind(), Infer(_))

compiler/rustc_typeck/src/check/fallback.rs

Lines changed: 244 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
use crate::check::FnCtxt;
2+
use rustc_data_structures::{
3+
fx::FxHashMap, graph::vec_graph::VecGraph, graph::WithSuccessors, stable_set::FxHashSet,
4+
};
25
use rustc_infer::infer::type_variable::Diverging;
36
use rustc_middle::ty::{self, Ty};
47

@@ -8,22 +11,30 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
811
pub(super) fn type_inference_fallback(&self) -> bool {
912
// All type checking constraints were added, try to fallback unsolved variables.
1013
self.select_obligations_where_possible(false, |_| {});
11-
let mut fallback_has_occurred = false;
1214

15+
// Check if we have any unsolved varibales. If not, no need for fallback.
16+
let unsolved_variables = self.unsolved_variables();
17+
if unsolved_variables.is_empty() {
18+
return false;
19+
}
20+
21+
let diverging_fallback = self.calculate_diverging_fallback(&unsolved_variables);
22+
23+
let mut fallback_has_occurred = false;
1324
// We do fallback in two passes, to try to generate
1425
// better error messages.
1526
// The first time, we do *not* replace opaque types.
16-
for ty in &self.unsolved_variables() {
27+
for ty in unsolved_variables {
1728
debug!("unsolved_variable = {:?}", ty);
18-
fallback_has_occurred |= self.fallback_if_possible(ty);
29+
fallback_has_occurred |= self.fallback_if_possible(ty, &diverging_fallback);
1930
}
2031

21-
// We now see if we can make progress. This might
22-
// cause us to unify inference variables for opaque types,
23-
// since we may have unified some other type variables
24-
// during the first phase of fallback.
25-
// This means that we only replace inference variables with their underlying
26-
// opaque types as a last resort.
32+
// We now see if we can make progress. This might cause us to
33+
// unify inference variables for opaque types, since we may
34+
// have unified some other type variables during the first
35+
// phase of fallback. This means that we only replace
36+
// inference variables with their underlying opaque types as a
37+
// last resort.
2738
//
2839
// In code like this:
2940
//
@@ -62,36 +73,44 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
6273
//
6374
// - Unconstrained floats are replaced with with `f64`.
6475
//
65-
// - Non-numerics get replaced with `!` when `#![feature(never_type_fallback)]`
66-
// is enabled. Otherwise, they are replaced with `()`.
76+
// - Non-numerics may get replaced with `()` or `!`, depending on
77+
// how they were categorized by `calculate_diverging_fallback`
78+
// (and the setting of `#![feature(never_type_fallback)]`).
79+
//
80+
// Fallback becomes very dubious if we have encountered
81+
// type-checking errors. In that case, fallback to Error.
6782
//
68-
// Fallback becomes very dubious if we have encountered type-checking errors.
69-
// In that case, fallback to Error.
7083
// The return value indicates whether fallback has occurred.
71-
fn fallback_if_possible(&self, ty: Ty<'tcx>) -> bool {
84+
fn fallback_if_possible(
85+
&self,
86+
ty: Ty<'tcx>,
87+
diverging_fallback: &FxHashMap<Ty<'tcx>, Ty<'tcx>>,
88+
) -> bool {
7289
// Careful: we do NOT shallow-resolve `ty`. We know that `ty`
73-
// is an unsolved variable, and we determine its fallback based
74-
// solely on how it was created, not what other type variables
75-
// it may have been unified with since then.
90+
// is an unsolved variable, and we determine its fallback
91+
// based solely on how it was created, not what other type
92+
// variables it may have been unified with since then.
7693
//
77-
// The reason this matters is that other attempts at fallback may
78-
// (in principle) conflict with this fallback, and we wish to generate
79-
// a type error in that case. (However, this actually isn't true right now,
80-
// because we're only using the builtin fallback rules. This would be
81-
// true if we were using user-supplied fallbacks. But it's still useful
82-
// to write the code to detect bugs.)
94+
// The reason this matters is that other attempts at fallback
95+
// may (in principle) conflict with this fallback, and we wish
96+
// to generate a type error in that case. (However, this
97+
// actually isn't true right now, because we're only using the
98+
// builtin fallback rules. This would be true if we were using
99+
// user-supplied fallbacks. But it's still useful to write the
100+
// code to detect bugs.)
83101
//
84-
// (Note though that if we have a general type variable `?T` that is then unified
85-
// with an integer type variable `?I` that ultimately never gets
86-
// resolved to a special integral type, `?T` is not considered unsolved,
87-
// but `?I` is. The same is true for float variables.)
102+
// (Note though that if we have a general type variable `?T`
103+
// that is then unified with an integer type variable `?I`
104+
// that ultimately never gets resolved to a special integral
105+
// type, `?T` is not considered unsolved, but `?I` is. The
106+
// same is true for float variables.)
88107
let fallback = match ty.kind() {
89108
_ if self.is_tainted_by_errors() => self.tcx.ty_error(),
90109
ty::Infer(ty::IntVar(_)) => self.tcx.types.i32,
91110
ty::Infer(ty::FloatVar(_)) => self.tcx.types.f64,
92-
_ => match self.type_var_diverges(ty) {
93-
Diverging::Diverges => self.tcx.mk_diverging_default(),
94-
Diverging::NotDiverging => return false,
111+
_ => match diverging_fallback.get(&ty) {
112+
Some(&fallback_ty) => fallback_ty,
113+
None => return false,
95114
},
96115
};
97116
debug!("fallback_if_possible(ty={:?}): defaulting to `{:?}`", ty, fallback);
@@ -105,11 +124,10 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
105124
true
106125
}
107126

108-
/// Second round of fallback: Unconstrained type variables
109-
/// created from the instantiation of an opaque
110-
/// type fall back to the opaque type itself. This is a
111-
/// somewhat incomplete attempt to manage "identity passthrough"
112-
/// for `impl Trait` types.
127+
/// Second round of fallback: Unconstrained type variables created
128+
/// from the instantiation of an opaque type fall back to the
129+
/// opaque type itself. This is a somewhat incomplete attempt to
130+
/// manage "identity passthrough" for `impl Trait` types.
113131
///
114132
/// For example, in this code:
115133
///
@@ -158,4 +176,195 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
158176
return false;
159177
}
160178
}
179+
180+
/// The "diverging fallback" system is rather complicated. This is
181+
/// a result of our need to balance 'do the right thing' with
182+
/// backwards compatibility.
183+
///
184+
/// "Diverging" type variables are variables created when we
185+
/// coerce a `!` type into an unbound type variable `?X`. If they
186+
/// never wind up being constrained, the "right and natural" thing
187+
/// is that `?X` should "fallback" to `!`. This means that e.g. an
188+
/// expression like `Some(return)` will ultimately wind up with a
189+
/// type like `Option<!>` (presuming it is not assigned or
190+
/// constrained to have some other type).
191+
///
192+
/// However, the fallback used to be `()` (before the `!` type was
193+
/// added). Moreover, there are cases where the `!` type 'leaks
194+
/// out' from dead code into type variables that affect live
195+
/// code. The most common case is something like this:
196+
///
197+
/// ```rust
198+
/// match foo() {
199+
/// 22 => Default::default(), // call this type `?D`
200+
/// _ => return, // return has type `!`
201+
/// } // call the type of this match `?M`
202+
/// ```
203+
///
204+
/// Here, coercing the type `!` into `?M` will create a diverging
205+
/// type variable `?X` where `?X <: ?M`. We also have that `?D <:
206+
/// ?M`. If `?M` winds up unconstrained, then `?X` will
207+
/// fallback. If it falls back to `!`, then all the type variables
208+
/// will wind up equal to `!` -- this includes the type `?D`
209+
/// (since `!` doesn't implement `Default`, we wind up a "trait
210+
/// not implemented" error in code like this). But since the
211+
/// original fallback was `()`, this code used to compile with `?D
212+
/// = ()`. This is somewhat surprising, since `Default::default()`
213+
/// on its own would give an error because the types are
214+
/// insufficiently constrained.
215+
///
216+
/// Our solution to this dilemma is to modify diverging variables
217+
/// so that they can *either* fallback to `!` (the default) or to
218+
/// `()` (the backwards compatibility case). We decide which
219+
/// fallback to use based on whether there is a coercion pattern
220+
/// like this:
221+
///
222+
/// ```
223+
/// ?Diverging -> ?V
224+
/// ?NonDiverging -> ?V
225+
/// ?V != ?NonDiverging
226+
/// ```
227+
///
228+
/// Here `?Diverging` represents some diverging type variable and
229+
/// `?NonDiverging` represents some non-diverging type
230+
/// variable. `?V` can be any type variable (diverging or not), so
231+
/// long as it is not equal to `?NonDiverging`.
232+
///
233+
/// Intuitively, what we are looking for is a case where a
234+
/// "non-diverging" type variable (like `?M` in our example above)
235+
/// is coerced *into* some variable `?V` that would otherwise
236+
/// fallback to `!`. In that case, we make `?V` fallback to `!`,
237+
/// along with anything that would flow into `?V`.
238+
///
239+
/// The algorithm we use:
240+
/// * Identify all variables that are coerced *into* by a
241+
/// diverging variable. Do this by iterating over each
242+
/// diverging, unsolved variable and finding all variables
243+
/// reachable from there. Call that set `D`.
244+
/// * Walk over all unsolved, non-diverging variables, and find
245+
/// any variable that has an edge into `D`.
246+
fn calculate_diverging_fallback(
247+
&self,
248+
unsolved_variables: &[Ty<'tcx>],
249+
) -> FxHashMap<Ty<'tcx>, Ty<'tcx>> {
250+
debug!("calculate_diverging_fallback({:?})", unsolved_variables);
251+
252+
// Construct a coercion graph where an edge `A -> B` indicates
253+
// a type variable is that is coerced
254+
let coercion_graph = self.create_coercion_graph();
255+
256+
// Extract the unsolved type inference variable vids; note that some
257+
// unsolved variables are integer/float variables and are excluded.
258+
let unsolved_vids: Vec<_> =
259+
unsolved_variables.iter().filter_map(|ty| ty.ty_vid()).collect();
260+
261+
// Find all type variables that are reachable from a diverging
262+
// type variable. These will typically default to `!`, unless
263+
// we find later that they are *also* reachable from some
264+
// other type variable outside this set.
265+
let mut roots_reachable_from_diverging = FxHashSet::default();
266+
let mut diverging_vids = vec![];
267+
let mut non_diverging_vids = vec![];
268+
for &unsolved_vid in &unsolved_vids {
269+
debug!(
270+
"calculate_diverging_fallback: unsolved_vid={:?} diverges={:?}",
271+
unsolved_vid,
272+
self.infcx.ty_vid_diverges(unsolved_vid)
273+
);
274+
match self.infcx.ty_vid_diverges(unsolved_vid) {
275+
Diverging::Diverges => {
276+
diverging_vids.push(unsolved_vid);
277+
let root_vid = self.infcx.root_var(unsolved_vid);
278+
debug!(
279+
"calculate_diverging_fallback: root_vid={:?} reaches {:?}",
280+
root_vid,
281+
coercion_graph.depth_first_search(root_vid).collect::<Vec<_>>()
282+
);
283+
roots_reachable_from_diverging
284+
.extend(coercion_graph.depth_first_search(root_vid));
285+
}
286+
Diverging::NotDiverging => {
287+
non_diverging_vids.push(unsolved_vid);
288+
}
289+
}
290+
}
291+
debug!(
292+
"calculate_diverging_fallback: roots_reachable_from_diverging={:?}",
293+
roots_reachable_from_diverging,
294+
);
295+
296+
// Find all type variables N0 that are not reachable from a
297+
// diverging variable, and then compute the set reachable from
298+
// N0, which we call N. These are the *non-diverging* type
299+
// variables. (Note that this set consists of "root variables".)
300+
let mut roots_reachable_from_non_diverging = FxHashSet::default();
301+
for &non_diverging_vid in &non_diverging_vids {
302+
let root_vid = self.infcx.root_var(non_diverging_vid);
303+
if roots_reachable_from_diverging.contains(&root_vid) {
304+
continue;
305+
}
306+
roots_reachable_from_non_diverging.extend(coercion_graph.depth_first_search(root_vid));
307+
}
308+
debug!(
309+
"calculate_diverging_fallback: roots_reachable_from_non_diverging={:?}",
310+
roots_reachable_from_non_diverging,
311+
);
312+
313+
// For each diverging variable, figure out whether it can
314+
// reach a member of N. If so, it falls back to `()`. Else
315+
// `!`.
316+
let mut diverging_fallback = FxHashMap::default();
317+
for &diverging_vid in &diverging_vids {
318+
let diverging_ty = self.tcx.mk_ty_var(diverging_vid);
319+
let root_vid = self.infcx.root_var(diverging_vid);
320+
let can_reach_non_diverging = coercion_graph
321+
.depth_first_search(root_vid)
322+
.any(|n| roots_reachable_from_non_diverging.contains(&n));
323+
if can_reach_non_diverging {
324+
debug!("fallback to (): {:?}", diverging_vid);
325+
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
326+
} else {
327+
debug!("fallback to !: {:?}", diverging_vid);
328+
diverging_fallback.insert(diverging_ty, self.tcx.mk_diverging_default());
329+
}
330+
}
331+
332+
diverging_fallback
333+
}
334+
335+
/// Returns a graph whose nodes are (unresolved) inference variables and where
336+
/// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`.
337+
fn create_coercion_graph(&self) -> VecGraph<ty::TyVid> {
338+
let pending_obligations = self.fulfillment_cx.borrow_mut().pending_obligations();
339+
debug!("create_coercion_graph: pending_obligations={:?}", pending_obligations);
340+
let coercion_edges: Vec<(ty::TyVid, ty::TyVid)> = pending_obligations
341+
.into_iter()
342+
.filter_map(|obligation| {
343+
// The predicates we are looking for look like `Coerce(?A -> ?B)`.
344+
// They will have no bound variables.
345+
obligation.predicate.kind().no_bound_vars()
346+
})
347+
.filter_map(|atom| {
348+
if let ty::PredicateKind::Coerce(ty::CoercePredicate { a, b }) = atom {
349+
let a_vid = self.root_vid(a)?;
350+
let b_vid = self.root_vid(b)?;
351+
Some((a_vid, b_vid))
352+
} else {
353+
return None;
354+
};
355+
356+
let a_vid = self.root_vid(a)?;
357+
let b_vid = self.root_vid(b)?;
358+
Some((a_vid, b_vid))
359+
})
360+
.collect();
361+
debug!("create_coercion_graph: coercion_edges={:?}", coercion_edges);
362+
let num_ty_vars = self.infcx.num_ty_vars();
363+
VecGraph::new(num_ty_vars, coercion_edges)
364+
}
365+
366+
/// If `ty` is an unresolved type variable, returns its root vid.
367+
fn root_vid(&self, ty: Ty<'tcx>) -> Option<ty::TyVid> {
368+
Some(self.infcx.root_var(self.infcx.shallow_resolve(ty).ty_vid()?))
369+
}
161370
}

0 commit comments

Comments
 (0)