| //! Fallback of infer vars to `!` and `i32`/`f64`. |
| |
| use intern::sym; |
| use petgraph::{ |
| Graph, |
| visit::{Dfs, Walker}, |
| }; |
| use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; |
| use rustc_type_ir::{ |
| TyVid, |
| inherent::{IntoKind, Ty as _}, |
| }; |
| use tracing::debug; |
| |
| use crate::{ |
| infer::InferenceContext, |
| next_solver::{CoercePredicate, PredicateKind, SubtypePredicate, Ty, TyKind}, |
| }; |
| |
| #[derive(Copy, Clone)] |
| pub(crate) enum DivergingFallbackBehavior { |
| /// Always fallback to `()` (aka "always spontaneous decay") |
| ToUnit, |
| /// Sometimes fallback to `!`, but mainly fallback to `()` so that most of the crates are not broken. |
| ContextDependent, |
| /// Always fallback to `!` (which should be equivalent to never falling back + not making |
| /// never-to-any coercions unless necessary) |
| ToNever, |
| } |
| |
| impl<'db> InferenceContext<'_, 'db> { |
| pub(super) fn type_inference_fallback(&mut self) { |
| debug!( |
| "type-inference-fallback start obligations: {:#?}", |
| self.table.fulfillment_cx.pending_obligations() |
| ); |
| |
| // All type checking constraints were added, try to fallback unsolved variables. |
| self.table.select_obligations_where_possible(); |
| |
| debug!( |
| "type-inference-fallback post selection obligations: {:#?}", |
| self.table.fulfillment_cx.pending_obligations() |
| ); |
| |
| let fallback_occurred = self.fallback_types(); |
| |
| if !fallback_occurred { |
| return; |
| } |
| |
| // We now see if we can make progress. This might cause us to |
| // unify inference variables for opaque types, since we may |
| // have unified some other type variables during the first |
| // phase of fallback. This means that we only replace |
| // inference variables with their underlying opaque types as a |
| // last resort. |
| // |
| // In code like this: |
| // |
| // ```rust |
| // type MyType = impl Copy; |
| // fn produce() -> MyType { true } |
| // fn bad_produce() -> MyType { panic!() } |
| // ``` |
| // |
| // we want to unify the opaque inference variable in `bad_produce` |
| // with the diverging fallback for `panic!` (e.g. `()` or `!`). |
| // This will produce a nice error message about conflicting concrete |
| // types for `MyType`. |
| // |
| // If we had tried to fallback the opaque inference variable to `MyType`, |
| // we will generate a confusing type-check error that does not explicitly |
| // refer to opaque types. |
| self.table.select_obligations_where_possible(); |
| } |
| |
| fn diverging_fallback_behavior(&self) -> DivergingFallbackBehavior { |
| if self.krate().data(self.db).edition.at_least_2024() { |
| return DivergingFallbackBehavior::ToNever; |
| } |
| |
| if self.resolver.def_map().is_unstable_feature_enabled(&sym::never_type_fallback) { |
| return DivergingFallbackBehavior::ContextDependent; |
| } |
| |
| DivergingFallbackBehavior::ToUnit |
| } |
| |
| fn fallback_types(&mut self) -> bool { |
| // Check if we have any unresolved variables. If not, no need for fallback. |
| let unresolved_variables = self.table.infer_ctxt.unresolved_variables(); |
| |
| if unresolved_variables.is_empty() { |
| return false; |
| } |
| |
| let diverging_fallback_behavior = self.diverging_fallback_behavior(); |
| |
| let diverging_fallback = |
| self.calculate_diverging_fallback(&unresolved_variables, diverging_fallback_behavior); |
| |
| // We do fallback in two passes, to try to generate |
| // better error messages. |
| // The first time, we do *not* replace opaque types. |
| let mut fallback_occurred = false; |
| for ty in unresolved_variables { |
| debug!("unsolved_variable = {:?}", ty); |
| fallback_occurred |= self.fallback_if_possible(ty, &diverging_fallback); |
| } |
| |
| fallback_occurred |
| } |
| |
| // Tries to apply a fallback to `ty` if it is an unsolved variable. |
| // |
| // - Unconstrained ints are replaced with `i32`. |
| // |
| // - Unconstrained floats are replaced with `f64`. |
| // |
| // - Non-numerics may get replaced with `()` or `!`, depending on |
| // how they were categorized by `calculate_diverging_fallback` |
| // (and the setting of `#![feature(never_type_fallback)]`). |
| // |
| // Fallback becomes very dubious if we have encountered |
| // type-checking errors. In that case, fallback to Error. |
| // |
| // Sets `FnCtxt::fallback_has_occurred` if fallback is performed |
| // during this call. |
| fn fallback_if_possible( |
| &mut self, |
| ty: Ty<'db>, |
| diverging_fallback: &FxHashMap<Ty<'db>, Ty<'db>>, |
| ) -> bool { |
| // Careful: we do NOT shallow-resolve `ty`. We know that `ty` |
| // is an unsolved variable, and we determine its fallback |
| // based solely on how it was created, not what other type |
| // variables it may have been unified with since then. |
| // |
| // The reason this matters is that other attempts at fallback |
| // may (in principle) conflict with this fallback, and we wish |
| // to generate a type error in that case. (However, this |
| // actually isn't true right now, because we're only using the |
| // builtin fallback rules. This would be true if we were using |
| // user-supplied fallbacks. But it's still useful to write the |
| // code to detect bugs.) |
| // |
| // (Note though that if we have a general type variable `?T` |
| // that is then unified with an integer type variable `?I` |
| // that ultimately never gets resolved to a special integral |
| // type, `?T` is not considered unsolved, but `?I` is. The |
| // same is true for float variables.) |
| let fallback = match ty.kind() { |
| TyKind::Infer(rustc_type_ir::IntVar(_)) => self.types.i32, |
| TyKind::Infer(rustc_type_ir::FloatVar(_)) => self.types.f64, |
| _ => match diverging_fallback.get(&ty) { |
| Some(&fallback_ty) => fallback_ty, |
| None => return false, |
| }, |
| }; |
| debug!("fallback_if_possible(ty={:?}): defaulting to `{:?}`", ty, fallback); |
| |
| self.demand_eqtype(ty, fallback); |
| true |
| } |
| |
| /// The "diverging fallback" system is rather complicated. This is |
| /// a result of our need to balance 'do the right thing' with |
| /// backwards compatibility. |
| /// |
| /// "Diverging" type variables are variables created when we |
| /// coerce a `!` type into an unbound type variable `?X`. If they |
| /// never wind up being constrained, the "right and natural" thing |
| /// is that `?X` should "fallback" to `!`. This means that e.g. an |
| /// expression like `Some(return)` will ultimately wind up with a |
| /// type like `Option<!>` (presuming it is not assigned or |
| /// constrained to have some other type). |
| /// |
| /// However, the fallback used to be `()` (before the `!` type was |
| /// added). Moreover, there are cases where the `!` type 'leaks |
| /// out' from dead code into type variables that affect live |
| /// code. The most common case is something like this: |
| /// |
| /// ```rust |
| /// # fn foo() -> i32 { 4 } |
| /// match foo() { |
| /// 22 => Default::default(), // call this type `?D` |
| /// _ => return, // return has type `!` |
| /// } // call the type of this match `?M` |
| /// ``` |
| /// |
| /// Here, coercing the type `!` into `?M` will create a diverging |
| /// type variable `?X` where `?X <: ?M`. We also have that `?D <: |
| /// ?M`. If `?M` winds up unconstrained, then `?X` will |
| /// fallback. If it falls back to `!`, then all the type variables |
| /// will wind up equal to `!` -- this includes the type `?D` |
| /// (since `!` doesn't implement `Default`, we wind up a "trait |
| /// not implemented" error in code like this). But since the |
| /// original fallback was `()`, this code used to compile with `?D |
| /// = ()`. This is somewhat surprising, since `Default::default()` |
| /// on its own would give an error because the types are |
| /// insufficiently constrained. |
| /// |
| /// Our solution to this dilemma is to modify diverging variables |
| /// so that they can *either* fallback to `!` (the default) or to |
| /// `()` (the backwards compatibility case). We decide which |
| /// fallback to use based on whether there is a coercion pattern |
| /// like this: |
| /// |
| /// ```ignore (not-rust) |
| /// ?Diverging -> ?V |
| /// ?NonDiverging -> ?V |
| /// ?V != ?NonDiverging |
| /// ``` |
| /// |
| /// Here `?Diverging` represents some diverging type variable and |
| /// `?NonDiverging` represents some non-diverging type |
| /// variable. `?V` can be any type variable (diverging or not), so |
| /// long as it is not equal to `?NonDiverging`. |
| /// |
| /// Intuitively, what we are looking for is a case where a |
| /// "non-diverging" type variable (like `?M` in our example above) |
| /// is coerced *into* some variable `?V` that would otherwise |
| /// fallback to `!`. In that case, we make `?V` fallback to `!`, |
| /// along with anything that would flow into `?V`. |
| /// |
| /// The algorithm we use: |
| /// * Identify all variables that are coerced *into* by a |
| /// diverging variable. Do this by iterating over each |
| /// diverging, unsolved variable and finding all variables |
| /// reachable from there. Call that set `D`. |
| /// * Walk over all unsolved, non-diverging variables, and find |
| /// any variable that has an edge into `D`. |
| fn calculate_diverging_fallback( |
| &self, |
| unresolved_variables: &[Ty<'db>], |
| behavior: DivergingFallbackBehavior, |
| ) -> FxHashMap<Ty<'db>, Ty<'db>> { |
| debug!("calculate_diverging_fallback({:?})", unresolved_variables); |
| |
| // Construct a coercion graph where an edge `A -> B` indicates |
| // a type variable is that is coerced |
| let coercion_graph = self.create_coercion_graph(); |
| |
| // Extract the unsolved type inference variable vids; note that some |
| // unsolved variables are integer/float variables and are excluded. |
| let unsolved_vids = unresolved_variables.iter().filter_map(|ty| ty.ty_vid()); |
| |
| // Compute the diverging root vids D -- that is, the root vid of |
| // those type variables that (a) are the target of a coercion from |
| // a `!` type and (b) have not yet been solved. |
| // |
| // These variables are the ones that are targets for fallback to |
| // either `!` or `()`. |
| let diverging_roots: FxHashSet<TyVid> = self |
| .table |
| .diverging_type_vars |
| .iter() |
| .map(|&ty| self.shallow_resolve(ty)) |
| .filter_map(|ty| ty.ty_vid()) |
| .map(|vid| self.table.infer_ctxt.root_var(vid)) |
| .collect(); |
| debug!( |
| "calculate_diverging_fallback: diverging_type_vars={:?}", |
| self.table.diverging_type_vars |
| ); |
| debug!("calculate_diverging_fallback: diverging_roots={:?}", diverging_roots); |
| |
| // Find all type variables that are reachable from a diverging |
| // type variable. These will typically default to `!`, unless |
| // we find later that they are *also* reachable from some |
| // other type variable outside this set. |
| let mut roots_reachable_from_diverging = Dfs::empty(&coercion_graph); |
| let mut diverging_vids = vec![]; |
| let mut non_diverging_vids = vec![]; |
| for unsolved_vid in unsolved_vids { |
| let root_vid = self.table.infer_ctxt.root_var(unsolved_vid); |
| debug!( |
| "calculate_diverging_fallback: unsolved_vid={:?} root_vid={:?} diverges={:?}", |
| unsolved_vid, |
| root_vid, |
| diverging_roots.contains(&root_vid), |
| ); |
| if diverging_roots.contains(&root_vid) { |
| diverging_vids.push(unsolved_vid); |
| roots_reachable_from_diverging.move_to(root_vid.as_u32().into()); |
| |
| // drain the iterator to visit all nodes reachable from this node |
| while roots_reachable_from_diverging.next(&coercion_graph).is_some() {} |
| } else { |
| non_diverging_vids.push(unsolved_vid); |
| } |
| } |
| |
| debug!( |
| "calculate_diverging_fallback: roots_reachable_from_diverging={:?}", |
| roots_reachable_from_diverging, |
| ); |
| |
| // Find all type variables N0 that are not reachable from a |
| // diverging variable, and then compute the set reachable from |
| // N0, which we call N. These are the *non-diverging* type |
| // variables. (Note that this set consists of "root variables".) |
| let mut roots_reachable_from_non_diverging = Dfs::empty(&coercion_graph); |
| for &non_diverging_vid in &non_diverging_vids { |
| let root_vid = self.table.infer_ctxt.root_var(non_diverging_vid); |
| if roots_reachable_from_diverging.discovered.contains(root_vid.as_usize()) { |
| continue; |
| } |
| roots_reachable_from_non_diverging.move_to(root_vid.as_u32().into()); |
| while roots_reachable_from_non_diverging.next(&coercion_graph).is_some() {} |
| } |
| debug!( |
| "calculate_diverging_fallback: roots_reachable_from_non_diverging={:?}", |
| roots_reachable_from_non_diverging, |
| ); |
| |
| debug!("obligations: {:#?}", self.table.fulfillment_cx.pending_obligations()); |
| |
| // For each diverging variable, figure out whether it can |
| // reach a member of N. If so, it falls back to `()`. Else |
| // `!`. |
| let mut diverging_fallback = |
| FxHashMap::with_capacity_and_hasher(diverging_vids.len(), FxBuildHasher); |
| |
| for &diverging_vid in &diverging_vids { |
| let diverging_ty = Ty::new_var(self.interner(), diverging_vid); |
| let root_vid = self.table.infer_ctxt.root_var(diverging_vid); |
| let can_reach_non_diverging = Dfs::new(&coercion_graph, root_vid.as_u32().into()) |
| .iter(&coercion_graph) |
| .any(|n| roots_reachable_from_non_diverging.discovered.contains(n.index())); |
| |
| let mut fallback_to = |ty| { |
| diverging_fallback.insert(diverging_ty, ty); |
| }; |
| |
| match behavior { |
| DivergingFallbackBehavior::ToUnit => { |
| debug!("fallback to () - legacy: {:?}", diverging_vid); |
| fallback_to(self.types.unit); |
| } |
| DivergingFallbackBehavior::ContextDependent => { |
| // FIXME: rustc does the following, but given this is only relevant when the unstable |
| // `never_type_fallback` feature is active, I chose to not port this. |
| // if found_infer_var_info.self_in_trait && found_infer_var_info.output { |
| // // This case falls back to () to ensure that the code pattern in |
| // // tests/ui/never_type/fallback-closure-ret.rs continues to |
| // // compile when never_type_fallback is enabled. |
| // // |
| // // This rule is not readily explainable from first principles, |
| // // but is rather intended as a patchwork fix to ensure code |
| // // which compiles before the stabilization of never type |
| // // fallback continues to work. |
| // // |
| // // Typically this pattern is encountered in a function taking a |
| // // closure as a parameter, where the return type of that closure |
| // // (checked by `relationship.output`) is expected to implement |
| // // some trait (checked by `relationship.self_in_trait`). This |
| // // can come up in non-closure cases too, so we do not limit this |
| // // rule to specifically `FnOnce`. |
| // // |
| // // When the closure's body is something like `panic!()`, the |
| // // return type would normally be inferred to `!`. However, it |
| // // needs to fall back to `()` in order to still compile, as the |
| // // trait is specifically implemented for `()` but not `!`. |
| // // |
| // // For details on the requirements for these relationships to be |
| // // set, see the relationship finding module in |
| // // compiler/rustc_trait_selection/src/traits/relationships.rs. |
| // debug!("fallback to () - found trait and projection: {:?}", diverging_vid); |
| // fallback_to(self.types.unit); |
| // } |
| if can_reach_non_diverging { |
| debug!("fallback to () - reached non-diverging: {:?}", diverging_vid); |
| fallback_to(self.types.unit); |
| } else { |
| debug!("fallback to ! - all diverging: {:?}", diverging_vid); |
| fallback_to(self.types.never); |
| } |
| } |
| DivergingFallbackBehavior::ToNever => { |
| debug!( |
| "fallback to ! - `rustc_never_type_mode = \"fallback_to_never\")`: {:?}", |
| diverging_vid |
| ); |
| fallback_to(self.types.never); |
| } |
| } |
| } |
| |
| diverging_fallback |
| } |
| |
| /// Returns a graph whose nodes are (unresolved) inference variables and where |
| /// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`. |
| fn create_coercion_graph(&self) -> Graph<(), ()> { |
| let pending_obligations = self.table.fulfillment_cx.pending_obligations(); |
| let pending_obligations_len = pending_obligations.len(); |
| debug!("create_coercion_graph: pending_obligations={:?}", pending_obligations); |
| let coercion_edges = pending_obligations |
| .into_iter() |
| .filter_map(|obligation| { |
| // The predicates we are looking for look like `Coerce(?A -> ?B)`. |
| // They will have no bound variables. |
| obligation.predicate.kind().no_bound_vars() |
| }) |
| .filter_map(|atom| { |
| // We consider both subtyping and coercion to imply 'flow' from |
| // some position in the code `a` to a different position `b`. |
| // This is then used to determine which variables interact with |
| // live code, and as such must fall back to `()` to preserve |
| // soundness. |
| // |
| // In practice currently the two ways that this happens is |
| // coercion and subtyping. |
| let (a, b) = match atom { |
| PredicateKind::Coerce(CoercePredicate { a, b }) => (a, b), |
| PredicateKind::Subtype(SubtypePredicate { a_is_expected: _, a, b }) => (a, b), |
| _ => return None, |
| }; |
| |
| let a_vid = self.root_vid(a)?; |
| let b_vid = self.root_vid(b)?; |
| Some((a_vid.as_u32(), b_vid.as_u32())) |
| }); |
| let num_ty_vars = self.table.infer_ctxt.num_ty_vars(); |
| let mut graph = Graph::with_capacity(num_ty_vars, pending_obligations_len); |
| for _ in 0..num_ty_vars { |
| graph.add_node(()); |
| } |
| graph.extend_with_edges(coercion_edges); |
| graph |
| } |
| |
| /// If `ty` is an unresolved type variable, returns its root vid. |
| fn root_vid(&self, ty: Ty<'db>) -> Option<TyVid> { |
| Some(self.table.infer_ctxt.root_var(self.shallow_resolve(ty).ty_vid()?)) |
| } |
| } |