diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index 0e34a6120b609..db47945e9dbec 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -45,90 +45,101 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } } + /// Tries to apply a fallback to all unresolved variables. + /// + /// - Unconstrained ints are replaced with `i32`. + /// + /// - Unconstrained floats are replaced with `f64`, except when there is a trait predicate + /// `f32: From<{float}>`, in which case `f32` is used as the fallback instead. + /// + /// - Non-numerics may get replaced with `()` or `!`, depending on how they + /// were categorized by [`Self::calculate_diverging_fallback`], crate's + /// edition, and the setting of `#![rustc_never_type_options(fallback = ...)]`. + /// + /// Fallback becomes very dubious if we have encountered type-checking errors. + /// In that case, all variables fallback to Error. + /// + /// Sets [`FnCtxt::diverging_fallback_has_occurred`] if never type fallback + /// is performed during this call. + /// + /// Returns `true` if *any* kind of fallback has occurred during this call. fn fallback_types(&self) -> bool { - // Check if we have any unresolved variables. If not, no need for fallback. - let unresolved_variables = self.unresolved_variables(); + let (unresolved_ty, unresolved_int, unresolved_float) = self.unresolved_root_variables(); - if unresolved_variables.is_empty() { + // Check if we have any unresolved variables. If not, no need for fallback. + if unresolved_ty.is_empty() && unresolved_int.is_empty() && unresolved_float.is_empty() { return false; } - let (diverging_fallback, diverging_fallback_ty) = - self.calculate_diverging_fallback(&unresolved_variables); - let fallback_to_f32 = self.calculate_fallback_to_f32(&unresolved_variables); + let (diverging_fallback, diverging_fallback_ty) = self.calculate_diverging_fallback(); + let fallback_to_f32 = self.calculate_fallback_to_f32(&unresolved_float); // 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); + + for vid in unresolved_ty { + fallback_occurred |= self.fallback_if_possible( + vid, + || { + diverging_fallback.contains(&vid).then(|| { + self.diverging_fallback_has_occurred.set(true); + diverging_fallback_ty + }) + }, + |vid| (Ty::new_var(self.tcx, vid), self.type_var_origin(vid).span), + ); + } + + for vid in unresolved_int { + fallback_occurred |= self.fallback_if_possible( + vid, + || Some(self.tcx.types.i32), + // Int variables have no origin?.. + |vid| (Ty::new_int_var(self.tcx, vid), DUMMY_SP), + ); + } + + for vid in unresolved_float { fallback_occurred |= self.fallback_if_possible( - ty, - &diverging_fallback, - diverging_fallback_ty, - &fallback_to_f32, + vid, + || { + Some(if fallback_to_f32.contains(&vid) { + self.tcx.types.f32 + } else { + self.tcx.types.f64 + }) + }, + |vid| (Ty::new_float_var(self.tcx, vid), self.float_var_origin(vid).span), ); } 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`, except when there is a trait predicate - /// `f32: From<{float}>`, in which case `f32` is used as the fallback instead. + /// Applies fallback to `vid`, if possible. /// - /// - Non-numerics may get replaced with `()` or `!`, depending on how they - /// were categorized by [`Self::calculate_diverging_fallback`], crate's - /// edition, and the setting of `#![rustc_never_type_options(fallback = ...)]`. - /// - /// Fallback becomes very dubious if we have encountered - /// type-checking errors. In that case, fallback to Error. + /// - If `self.tainted_by_errors()` unifies the type represented by `vid` with error + /// - Otherwise, if `fallback` returns `Some`, unifies it with the output of `fallback` + /// - Otherwise, does nothing /// - /// Sets [`FnCtxt::diverging_fallback_has_occurred`] if never type fallback - /// is performed during this call. - fn fallback_if_possible( + /// Returns whatever fallback has been applied. + fn fallback_if_possible( &self, - ty: Ty<'tcx>, - diverging_fallback: &UnordSet>, - diverging_fallback_ty: Ty<'tcx>, - fallback_to_f32: &UnordSet, + vid: V, + fallback: impl FnOnce() -> Option>, + vid_to_ty_and_span: impl FnOnce(V) -> (Ty<'tcx>, Span), ) -> 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() { - _ if let Some(e) = self.tainted_by_errors() => Ty::new_error(self.tcx, e), - ty::Infer(ty::IntVar(_)) => self.tcx.types.i32, - ty::Infer(ty::FloatVar(vid)) if fallback_to_f32.contains(vid) => self.tcx.types.f32, - ty::Infer(ty::FloatVar(_)) => self.tcx.types.f64, - _ if diverging_fallback.contains(&ty) => { - self.diverging_fallback_has_occurred.set(true); - diverging_fallback_ty - } - _ => return false, + let fallback = if let Some(e) = self.tainted_by_errors() { + Ty::new_error(self.tcx, e) + } else if let Some(fallback) = fallback() { + fallback + } else { + return false; }; - debug!("fallback_if_possible(ty={:?}): defaulting to `{:?}`", ty, fallback); - let span = ty.ty_vid().map_or(DUMMY_SP, |vid| self.infcx.type_var_origin(vid).span); + let (ty, span) = vid_to_ty_and_span(vid); self.demand_eqtype(span, ty, fallback); true } @@ -146,15 +157,19 @@ impl<'tcx> FnCtxt<'_, 'tcx> { /// /// foo(1.0); /// ``` - fn calculate_fallback_to_f32(&self, unresolved_variables: &[Ty<'tcx>]) -> UnordSet { + fn calculate_fallback_to_f32( + &self, + unresolved_root_variables: &[ty::FloatVid], + ) -> UnordSet { // Short-circuit: if no unresolved variable is a float, no f32 fallback can apply, // so we can skip the (potentially very expensive) work in `from_float_for_f32_root_vids`. // Under the new solver, that function walks `visit_proof_tree` for every pending // obligation, which is O(N × proof_tree_size) and can dominate type-checking on crates // with many large pending obligations and no f32 involvement. - if unresolved_variables.iter().all(|ty| ty.float_vid().is_none()) { + if unresolved_root_variables.is_empty() { return UnordSet::new(); } + let roots: UnordSet = self.from_float_for_f32_root_vids(); if roots.is_empty() { // Most functions have no `f32: From<{float}>` predicates, so short-circuit and return @@ -164,12 +179,12 @@ impl<'tcx> FnCtxt<'_, 'tcx> { // Calculate all the unresolved variables that need to fallback to `f32` here. This ensures // we don't need to find root variables in `fallback_if_possible`: see the comment at the // top of that function for details. - let fallback_to_f32 = unresolved_variables + let fallback_to_f32 = unresolved_root_variables .iter() - .flat_map(|ty| ty.float_vid()) - .filter(|vid| roots.contains(&self.root_float_var(*vid))) - .inspect(|vid| { - let origin = self.float_var_origin(*vid); + .copied() + .filter(|&vid| roots.contains(&vid)) + .inspect(|&vid| { + let origin = self.float_var_origin(vid); // Show the entire literal in the suggestion to make it clearer. let mut literal = self.tcx.sess.source_map().span_to_snippet(origin.span).ok(); // A `.` at the end of the literal is no longer necessary if `f32` is explicitly specified @@ -193,12 +208,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { fallback_to_f32 } - fn calculate_diverging_fallback( - &self, - unresolved_variables: &[Ty<'tcx>], - ) -> (UnordSet>, Ty<'tcx>) { - debug!("calculate_diverging_fallback({:?})", unresolved_variables); - + fn calculate_diverging_fallback(&self) -> (UnordSet, Ty<'tcx>) { let diverging_fallback_ty = match self.diverging_fallback_behavior { DivergingFallbackBehavior::ToUnit => self.tcx.types.unit, DivergingFallbackBehavior::ToNever => self.tcx.types.never, @@ -208,21 +218,13 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } }; - // 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: UnordSet = self + let diverging_root_vids: Vec = self .diverging_type_vars .borrow() .iter() @@ -230,58 +232,28 @@ impl<'tcx> FnCtxt<'_, 'tcx> { .filter_map(|ty| ty.ty_vid()) .map(|vid| self.root_var(vid)) .collect(); - debug!( - "calculate_diverging_fallback: diverging_type_vars={:?}", - self.diverging_type_vars.borrow() - ); - 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 diverging_vids = vec![]; - for unsolved_vid in unsolved_vids { - let root_vid = self.root_var(unsolved_vid); - debug!( - "calculate_diverging_fallback: unsolved_vid={:?} root_vid={:?} diverges={:?}", - unsolved_vid, - root_vid, - diverging_roots.contains(&root_vid), + + { + // Construct a coercion graph where an edge `A -> B` indicates + // a type variable is that is coerced + let coercion_graph = self.create_coercion_graph(); + + self.lint_obligations_broken_by_never_type_fallback_change( + &diverging_root_vids, + &coercion_graph, ); - if diverging_roots.contains(&root_vid) { - diverging_vids.push(unsolved_vid); - debug!( - "calculate_diverging_fallback: root_vid={:?} reaches {:?}", + let unsafe_infer_vars = OnceCell::new(); + for &root_vid in &diverging_root_vids { + self.lint_never_type_fallback_flowing_into_unsafe_code( + &unsafe_infer_vars, + &coercion_graph, root_vid, - graph::depth_first_search(&coercion_graph, root_vid).collect::>() ); } } - debug!("obligations: {:#?}", self.fulfillment_cx.borrow_mut().pending_obligations()); - - let mut diverging_fallback = UnordSet::with_capacity(diverging_vids.len()); - let unsafe_infer_vars = OnceCell::new(); - - self.lint_obligations_broken_by_never_type_fallback_change( - &diverging_vids, - &coercion_graph, - ); - - for &diverging_vid in &diverging_vids { - let diverging_ty = Ty::new_var(self.tcx, diverging_vid); - let root_vid = self.root_var(diverging_vid); - - self.lint_never_type_fallback_flowing_into_unsafe_code( - &unsafe_infer_vars, - &coercion_graph, - root_vid, - ); - - diverging_fallback.insert(diverging_ty); - } + let diverging_fallback = diverging_root_vids.into_iter().collect::>(); (diverging_fallback, diverging_fallback_ty) } diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index aa2fe2ee21dfa..5400fbe78c428 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -13,8 +13,9 @@ use region_constraints::{ pub use relate::StructurallyRelateAliases; pub use relate::combine::PredicateEmittingRelation; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::snapshot_vec as sv; use rustc_data_structures::undo_log::{Rollback, UndoLogs}; -use rustc_data_structures::unify as ut; +use rustc_data_structures::unify::{self as ut, UnifyKey, UnifyValue}; use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{self as hir, HirId}; @@ -752,27 +753,22 @@ impl<'tcx> InferCtxt<'tcx> { } } - pub fn unresolved_variables(&self) -> Vec> { + pub fn unresolved_root_variables(&self) -> (Vec, Vec, Vec) { let mut inner = self.inner.borrow_mut(); - let mut vars: Vec> = inner - .type_variables() - .unresolved_variables() - .into_iter() - .map(|t| Ty::new_var(self.tcx, t)) - .collect(); - vars.extend( - (0..inner.int_unification_table().len()) - .map(|i| ty::IntVid::from_usize(i)) - .filter(|&vid| inner.int_unification_table().probe_value(vid).is_unknown()) - .map(|v| Ty::new_int_var(self.tcx, v)), + + let ty = inner.type_variables().unresolved_root_variables(); + + let int = unresolved_root_variables_of( + inner.int_unification_table(), + ty::IntVarValue::is_unknown, ); - vars.extend( - (0..inner.float_unification_table().len()) - .map(|i| ty::FloatVid::from_usize(i)) - .filter(|&vid| inner.float_unification_table().probe_value(vid).is_unknown()) - .map(|v| Ty::new_float_var(self.tcx, v)), + + let float = unresolved_root_variables_of( + inner.float_unification_table(), + ty::FloatVarValue::is_unknown, ); - vars + + (ty, int, float) } #[instrument(skip(self), level = "debug")] @@ -1874,3 +1870,24 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> { } } } + +/// Returns unresolved root variables from `table`, according to `is_unresolved`. +fn unresolved_root_variables_of( + mut table: UnificationTable<'_, '_, V>, + is_unresolved: impl Fn(V::Value) -> bool, +) -> Vec +where + V: Eq, + V::Value: UnifyValue, + for<'a> UndoLog<'a>: From>>, +{ + (0..table.len() as u32) + .map(V::from_index) + .filter(|&vid| { + // vid is a root variable + table.find(vid) == vid + // ...and it's currently unresolved + && is_unresolved(table.try_probe_value(vid).unwrap().clone()) + }) + .collect() +} diff --git a/compiler/rustc_infer/src/infer/type_variable.rs b/compiler/rustc_infer/src/infer/type_variable.rs index 21ba07428d9ec..8829e8bf4083a 100644 --- a/compiler/rustc_infer/src/infer/type_variable.rs +++ b/compiler/rustc_infer/src/infer/type_variable.rs @@ -286,17 +286,11 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { (range.clone(), range.map(|index| self.var_origin(index)).collect()) } - /// Returns indices of all variables that are not yet - /// instantiated. - pub(crate) fn unresolved_variables(&mut self) -> Vec { + /// Returns indices of all root variables that are not yet instantiated. + pub(crate) fn unresolved_root_variables(&mut self) -> Vec { (0..self.num_vars()) - .filter_map(|i| { - let vid = ty::TyVid::from_usize(i); - match self.probe(vid) { - TypeVariableValue::Unknown { .. } => Some(vid), - TypeVariableValue::Known { .. } => None, - } - }) + .map(ty::TyVid::from_usize) + .filter(|&vid| self.root_var(vid) == vid && self.probe(vid).is_unknown()) .collect() } }