1
1
use crate :: check:: FnCtxt ;
2
+ use rustc_data_structures:: {
3
+ fx:: FxHashMap , graph:: vec_graph:: VecGraph , graph:: WithSuccessors , stable_set:: FxHashSet ,
4
+ } ;
2
5
use rustc_infer:: infer:: type_variable:: Diverging ;
3
6
use rustc_middle:: ty:: { self , Ty } ;
4
7
@@ -8,20 +11,28 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
8
11
self . select_obligations_where_possible ( false , |_| { } ) ;
9
12
let mut fallback_has_occurred = false ;
10
13
14
+ // Check if we have any unsolved varibales. If not, no need for fallback.
15
+ let unsolved_variables = self . unsolved_variables ( ) ;
16
+ if unsolved_variables. is_empty ( ) {
17
+ return ;
18
+ }
19
+
20
+ let diverging_fallback = self . calculate_diverging_fallback ( & unsolved_variables) ;
21
+
11
22
// We do fallback in two passes, to try to generate
12
23
// better error messages.
13
24
// The first time, we do *not* replace opaque types.
14
- for ty in & self . unsolved_variables ( ) {
25
+ for ty in unsolved_variables {
15
26
debug ! ( "unsolved_variable = {:?}" , ty) ;
16
- fallback_has_occurred |= self . fallback_if_possible ( ty) ;
27
+ fallback_has_occurred |= self . fallback_if_possible ( ty, & diverging_fallback ) ;
17
28
}
18
29
19
- // We now see if we can make progress. This might
20
- // cause us to unify inference variables for opaque types,
21
- // since we may have unified some other type variables
22
- // during the first phase of fallback.
23
- // This means that we only replace inference variables with their underlying
24
- // opaque types as a last resort.
30
+ // We now see if we can make progress. This might cause us to
31
+ // unify inference variables for opaque types, since we may
32
+ // have unified some other type variables during the first
33
+ // phase of fallback. This means that we only replace
34
+ // inference variables with their underlying opaque types as a
35
+ // last resort.
25
36
//
26
37
// In code like this:
27
38
//
@@ -58,36 +69,44 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
58
69
//
59
70
// - Unconstrained floats are replaced with with `f64`.
60
71
//
61
- // - Non-numerics get replaced with `!` when `#![feature(never_type_fallback)]`
62
- // is enabled. Otherwise, they are replaced with `()`.
72
+ // - Non-numerics may get replaced with `()` or `!`, depending on
73
+ // how they were categorized by `calculate_diverging_fallback`
74
+ // (and the setting of `#![feature(never_type_fallback)]`).
75
+ //
76
+ // Fallback becomes very dubious if we have encountered
77
+ // type-checking errors. In that case, fallback to Error.
63
78
//
64
- // Fallback becomes very dubious if we have encountered type-checking errors.
65
- // In that case, fallback to Error.
66
79
// The return value indicates whether fallback has occurred.
67
- fn fallback_if_possible ( & self , ty : Ty < ' tcx > ) -> bool {
80
+ fn fallback_if_possible (
81
+ & self ,
82
+ ty : Ty < ' tcx > ,
83
+ diverging_fallback : & FxHashMap < Ty < ' tcx > , Ty < ' tcx > > ,
84
+ ) -> bool {
68
85
// Careful: we do NOT shallow-resolve `ty`. We know that `ty`
69
- // is an unsolved variable, and we determine its fallback based
70
- // solely on how it was created, not what other type variables
71
- // it may have been unified with since then.
86
+ // is an unsolved variable, and we determine its fallback
87
+ // based solely on how it was created, not what other type
88
+ // variables it may have been unified with since then.
72
89
//
73
- // The reason this matters is that other attempts at fallback may
74
- // (in principle) conflict with this fallback, and we wish to generate
75
- // a type error in that case. (However, this actually isn't true right now,
76
- // because we're only using the builtin fallback rules. This would be
77
- // true if we were using user-supplied fallbacks. But it's still useful
78
- // to write the code to detect bugs.)
90
+ // The reason this matters is that other attempts at fallback
91
+ // may (in principle) conflict with this fallback, and we wish
92
+ // to generate a type error in that case. (However, this
93
+ // actually isn't true right now, because we're only using the
94
+ // builtin fallback rules. This would be true if we were using
95
+ // user-supplied fallbacks. But it's still useful to write the
96
+ // code to detect bugs.)
79
97
//
80
- // (Note though that if we have a general type variable `?T` that is then unified
81
- // with an integer type variable `?I` that ultimately never gets
82
- // resolved to a special integral type, `?T` is not considered unsolved,
83
- // but `?I` is. The same is true for float variables.)
98
+ // (Note though that if we have a general type variable `?T`
99
+ // that is then unified with an integer type variable `?I`
100
+ // that ultimately never gets resolved to a special integral
101
+ // type, `?T` is not considered unsolved, but `?I` is. The
102
+ // same is true for float variables.)
84
103
let fallback = match ty. kind ( ) {
85
104
_ if self . is_tainted_by_errors ( ) => self . tcx . ty_error ( ) ,
86
105
ty:: Infer ( ty:: IntVar ( _) ) => self . tcx . types . i32 ,
87
106
ty:: Infer ( ty:: FloatVar ( _) ) => self . tcx . types . f64 ,
88
- _ => match self . type_var_diverges ( ty) {
89
- Diverging :: Diverges => self . tcx . mk_diverging_default ( ) ,
90
- Diverging :: NotDiverging => return false ,
107
+ _ => match diverging_fallback . get ( & ty) {
108
+ Some ( & fallback_ty ) => fallback_ty ,
109
+ None => return false ,
91
110
} ,
92
111
} ;
93
112
debug ! ( "fallback_if_possible(ty={:?}): defaulting to `{:?}`" , ty, fallback) ;
@@ -101,11 +120,10 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
101
120
true
102
121
}
103
122
104
- /// Second round of fallback: Unconstrained type variables
105
- /// created from the instantiation of an opaque
106
- /// type fall back to the opaque type itself. This is a
107
- /// somewhat incomplete attempt to manage "identity passthrough"
108
- /// for `impl Trait` types.
123
+ /// Second round of fallback: Unconstrained type variables created
124
+ /// from the instantiation of an opaque type fall back to the
125
+ /// opaque type itself. This is a somewhat incomplete attempt to
126
+ /// manage "identity passthrough" for `impl Trait` types.
109
127
///
110
128
/// For example, in this code:
111
129
///
@@ -153,4 +171,191 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
153
171
return false ;
154
172
}
155
173
}
174
+
175
+ /// The "diverging fallback" system is rather complicated. This is
176
+ /// a result of our need to balance 'do the right thing' with
177
+ /// backwards compatibility.
178
+ ///
179
+ /// "Diverging" type variables are variables created when we
180
+ /// coerce a `!` type into an unbound type variable `?X`. If they
181
+ /// never wind up being constrained, the "right and natural" thing
182
+ /// is that `?X` should "fallback" to `!`. This means that e.g. an
183
+ /// expression like `Some(return)` will ultimately wind up with a
184
+ /// type like `Option<!>` (presuming it is not assigned or
185
+ /// constrained to have some other type).
186
+ ///
187
+ /// However, the fallback used to be `()` (before the `!` type was
188
+ /// added). Moreover, there are cases where the `!` type 'leaks
189
+ /// out' from dead code into type variables that affect live
190
+ /// code. The most common case is something like this:
191
+ ///
192
+ /// ```rust
193
+ /// match foo() {
194
+ /// 22 => Default::default(), // call this type `?D`
195
+ /// _ => return, // return has type `!`
196
+ /// } // call the type of this match `?M`
197
+ /// ```
198
+ ///
199
+ /// Here, coercing the type `!` into `?M` will create a diverging
200
+ /// type variable `?X` where `?X <: ?M`. We also have that `?D <:
201
+ /// ?M`. If `?M` winds up unconstrained, then `?X` will
202
+ /// fallback. If it falls back to `!`, then all the type variables
203
+ /// will wind up equal to `!` -- this includes the type `?D`
204
+ /// (since `!` doesn't implement `Default`, we wind up a "trait
205
+ /// not implemented" error in code like this). But since the
206
+ /// original fallback was `()`, this code used to compile with `?D
207
+ /// = ()`. This is somewhat surprising, since `Default::default()`
208
+ /// on its own would give an error because the types are
209
+ /// insufficiently constrained.
210
+ ///
211
+ /// Our solution to this dilemma is to modify diverging variables
212
+ /// so that they can *either* fallback to `!` (the default) or to
213
+ /// `()` (the backwards compatibility case). We decide which
214
+ /// fallback to use based on whether there is a coercion pattern
215
+ /// like this:
216
+ ///
217
+ /// ```
218
+ /// ?Diverging -> ?V
219
+ /// ?NonDiverging -> ?V
220
+ /// ?V != ?NonDiverging
221
+ /// ```
222
+ ///
223
+ /// Here `?Diverging` represents some diverging type variable and
224
+ /// `?NonDiverging` represents some non-diverging type
225
+ /// variable. `?V` can be any type variable (diverging or not), so
226
+ /// long as it is not equal to `?NonDiverging`.
227
+ ///
228
+ /// Intuitively, what we are looking for is a case where a
229
+ /// "non-diverging" type variable (like `?M` in our example above)
230
+ /// is coerced *into* some variable `?V` that would otherwise
231
+ /// fallback to `!`. In that case, we make `?V` fallback to `!`,
232
+ /// along with anything that would flow into `?V`.
233
+ ///
234
+ /// The algorithm we use:
235
+ /// * Identify all variables that are coerced *into* by a
236
+ /// diverging variable. Do this by iterating over each
237
+ /// diverging, unsolved variable and finding all variables
238
+ /// reachable from there. Call that set `D`.
239
+ /// * Walk over all unsolved, non-diverging variables, and find
240
+ /// any variable that has an edge into `D`.
241
+ fn calculate_diverging_fallback (
242
+ & self ,
243
+ unsolved_variables : & [ Ty < ' tcx > ] ,
244
+ ) -> FxHashMap < Ty < ' tcx > , Ty < ' tcx > > {
245
+ debug ! ( "calculate_diverging_fallback({:?})" , unsolved_variables) ;
246
+
247
+ // Construct a coercion graph where an edge `A -> B` indicates
248
+ // a type variable is that is coerced
249
+ let coercion_graph = self . create_coercion_graph ( ) ;
250
+
251
+ // Extract the unsolved type inference variable vids; note that some
252
+ // unsolved variables are integer/float variables and are excluded.
253
+ let unsolved_vids: Vec < _ > =
254
+ unsolved_variables. iter ( ) . filter_map ( |ty| ty. ty_vid ( ) ) . collect ( ) ;
255
+
256
+ // Find all type variables that are reachable from a diverging
257
+ // type variable. These will typically default to `!`, unless
258
+ // we find later that they are *also* reachable from some
259
+ // other type variable outside this set.
260
+ let mut roots_reachable_from_diverging = FxHashSet :: default ( ) ;
261
+ let mut diverging_vids = vec ! [ ] ;
262
+ let mut non_diverging_vids = vec ! [ ] ;
263
+ for & unsolved_vid in & unsolved_vids {
264
+ debug ! (
265
+ "calculate_diverging_fallback: unsolved_vid={:?} diverges={:?}" ,
266
+ unsolved_vid,
267
+ self . infcx. ty_vid_diverges( unsolved_vid)
268
+ ) ;
269
+ match self . infcx . ty_vid_diverges ( unsolved_vid) {
270
+ Diverging :: Diverges => {
271
+ diverging_vids. push ( unsolved_vid) ;
272
+ let root_vid = self . infcx . root_var ( unsolved_vid) ;
273
+ debug ! (
274
+ "calculate_diverging_fallback: root_vid={:?} reaches {:?}" ,
275
+ root_vid,
276
+ coercion_graph. depth_first_search( root_vid) . collect:: <Vec <_>>( )
277
+ ) ;
278
+ roots_reachable_from_diverging
279
+ . extend ( coercion_graph. depth_first_search ( root_vid) ) ;
280
+ }
281
+ Diverging :: NotDiverging => {
282
+ non_diverging_vids. push ( unsolved_vid) ;
283
+ }
284
+ }
285
+ }
286
+ debug ! (
287
+ "calculate_diverging_fallback: roots_reachable_from_diverging={:?}" ,
288
+ roots_reachable_from_diverging,
289
+ ) ;
290
+
291
+ // Find all type variables N0 that are not reachable from a
292
+ // diverging variable, and then compute the set reachable from
293
+ // N0, which we call N. These are the *non-diverging* type
294
+ // variables. (Note that this set consists of "root variables".)
295
+ let mut roots_reachable_from_non_diverging = FxHashSet :: default ( ) ;
296
+ for & non_diverging_vid in & non_diverging_vids {
297
+ let root_vid = self . infcx . root_var ( non_diverging_vid) ;
298
+ if roots_reachable_from_diverging. contains ( & root_vid) {
299
+ continue ;
300
+ }
301
+ roots_reachable_from_non_diverging. extend ( coercion_graph. depth_first_search ( root_vid) ) ;
302
+ }
303
+ debug ! (
304
+ "calculate_diverging_fallback: roots_reachable_from_non_diverging={:?}" ,
305
+ roots_reachable_from_non_diverging,
306
+ ) ;
307
+
308
+ // For each diverging variable, figure out whether it can
309
+ // reach a member of N. If so, it falls back to `()`. Else
310
+ // `!`.
311
+ let mut diverging_fallback = FxHashMap :: default ( ) ;
312
+ for & diverging_vid in & diverging_vids {
313
+ let diverging_ty = self . tcx . mk_ty_var ( diverging_vid) ;
314
+ let root_vid = self . infcx . root_var ( diverging_vid) ;
315
+ let can_reach_non_diverging = coercion_graph
316
+ . depth_first_search ( root_vid)
317
+ . any ( |n| roots_reachable_from_non_diverging. contains ( & n) ) ;
318
+ if can_reach_non_diverging {
319
+ debug ! ( "fallback to (): {:?}" , diverging_vid) ;
320
+ diverging_fallback. insert ( diverging_ty, self . tcx . types . unit ) ;
321
+ } else {
322
+ debug ! ( "fallback to !: {:?}" , diverging_vid) ;
323
+ diverging_fallback. insert ( diverging_ty, self . tcx . mk_diverging_default ( ) ) ;
324
+ }
325
+ }
326
+
327
+ diverging_fallback
328
+ }
329
+
330
+ /// Returns a graph whose nodes are (unresolved) inference variables and where
331
+ /// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`.
332
+ fn create_coercion_graph ( & self ) -> VecGraph < ty:: TyVid > {
333
+ let pending_obligations = self . fulfillment_cx . borrow_mut ( ) . pending_obligations ( ) ;
334
+ debug ! ( "create_coercion_graph: pending_obligations={:?}" , pending_obligations) ;
335
+ let coercion_edges: Vec < ( ty:: TyVid , ty:: TyVid ) > = pending_obligations
336
+ . into_iter ( )
337
+ . filter_map ( |obligation| {
338
+ // The predicates we are looking for look like `Coerce(?A -> ?B)`.
339
+ // They will have no bound variables.
340
+ obligation. predicate . bound_atom ( ) . no_bound_vars ( )
341
+ } )
342
+ . filter_map ( |atom| {
343
+ if let ty:: PredicateAtom :: Coerce ( ty:: CoercePredicate { a, b } ) = atom {
344
+ let a_vid = self . root_vid ( a) ?;
345
+ let b_vid = self . root_vid ( b) ?;
346
+ Some ( ( a_vid, b_vid) )
347
+ } else {
348
+ None
349
+ }
350
+ } )
351
+ . collect ( ) ;
352
+ debug ! ( "create_coercion_graph: coercion_edges={:?}" , coercion_edges) ;
353
+ let num_ty_vars = self . infcx . num_ty_vars ( ) ;
354
+ VecGraph :: new ( num_ty_vars, coercion_edges)
355
+ }
356
+
357
+ /// If `ty` is an unresolved type variable, returns its root vid.
358
+ fn root_vid ( & self , ty : Ty < ' tcx > ) -> Option < ty:: TyVid > {
359
+ Some ( self . infcx . root_var ( self . infcx . shallow_resolve ( ty) . ty_vid ( ) ?) )
360
+ }
156
361
}
0 commit comments