Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 100 additions & 128 deletions compiler/rustc_hir_typeck/src/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<V>(
&self,
ty: Ty<'tcx>,
diverging_fallback: &UnordSet<Ty<'tcx>>,
diverging_fallback_ty: Ty<'tcx>,
fallback_to_f32: &UnordSet<FloatVid>,
vid: V,
fallback: impl FnOnce() -> Option<Ty<'tcx>>,
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
}
Expand All @@ -146,15 +157,19 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
///
/// foo(1.0);
/// ```
fn calculate_fallback_to_f32(&self, unresolved_variables: &[Ty<'tcx>]) -> UnordSet<FloatVid> {
fn calculate_fallback_to_f32(
&self,
unresolved_root_variables: &[ty::FloatVid],
) -> UnordSet<FloatVid> {
// 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<ty::FloatVid> = self.from_float_for_f32_root_vids();
if roots.is_empty() {
// Most functions have no `f32: From<{float}>` predicates, so short-circuit and return
Expand All @@ -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
Expand All @@ -193,12 +208,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
fallback_to_f32
}

fn calculate_diverging_fallback(
&self,
unresolved_variables: &[Ty<'tcx>],
) -> (UnordSet<Ty<'tcx>>, Ty<'tcx>) {
debug!("calculate_diverging_fallback({:?})", unresolved_variables);

fn calculate_diverging_fallback(&self) -> (UnordSet<ty::TyVid>, Ty<'tcx>) {
let diverging_fallback_ty = match self.diverging_fallback_behavior {
DivergingFallbackBehavior::ToUnit => self.tcx.types.unit,
DivergingFallbackBehavior::ToNever => self.tcx.types.never,
Expand All @@ -208,80 +218,42 @@ 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<ty::TyVid> = self
let diverging_root_vids: Vec<ty::TyVid> = self
.diverging_type_vars
.borrow()
.iter()
.map(|&ty_id| self.shallow_resolve(Ty::new_var(self.tcx, ty_id)))
.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::<Vec<_>>()
);
}
}

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::<UnordSet<_>>();

(diverging_fallback, diverging_fallback_ty)
}
Expand Down
55 changes: 36 additions & 19 deletions compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -752,27 +753,22 @@ impl<'tcx> InferCtxt<'tcx> {
}
}

pub fn unresolved_variables(&self) -> Vec<Ty<'tcx>> {
pub fn unresolved_root_variables(&self) -> (Vec<TyVid>, Vec<ty::IntVid>, Vec<ty::FloatVid>) {
let mut inner = self.inner.borrow_mut();
let mut vars: Vec<Ty<'_>> = 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")]
Expand Down Expand Up @@ -1874,3 +1870,24 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> {
}
}
}

/// Returns unresolved root variables from `table`, according to `is_unresolved`.
fn unresolved_root_variables_of<V: UnifyKey>(
mut table: UnificationTable<'_, '_, V>,
is_unresolved: impl Fn(V::Value) -> bool,
) -> Vec<V>
where
V: Eq,
V::Value: UnifyValue,
for<'a> UndoLog<'a>: From<sv::UndoLog<ut::Delegate<V>>>,
{
(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()
}
Loading
Loading