Skip to content

Detect cyclic subtype goals in new solver #110202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
39 changes: 30 additions & 9 deletions compiler/rustc_hir_analysis/src/autoderef.rs
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ use crate::errors::AutoDerefReachedRecursionLimit;
use crate::traits::query::evaluate_obligation::InferCtxtExt;
use crate::traits::NormalizeExt;
use crate::traits::{self, TraitEngine, TraitEngineExt};
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_infer::infer::InferCtxt;
use rustc_middle::ty::TypeVisitableExt;
use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -138,22 +139,42 @@ impl<'a, 'tcx> Autoderef<'a, 'tcx> {
return None;
}

let normalized_ty = self
.infcx
.at(&cause, self.param_env)
.normalize(tcx.mk_projection(tcx.lang_items().deref_target()?, trait_ref.substs));
let mut fulfillcx = <dyn TraitEngine<'tcx>>::new_in_snapshot(tcx);
let normalized_ty =
normalized_ty.into_value_registering_obligations(self.infcx, &mut *fulfillcx);
let errors = fulfillcx.select_where_possible(&self.infcx);
let mut fulfill_cx = <dyn TraitEngine<'tcx>>::new_in_snapshot(tcx);
let normalized_ty = if tcx.trait_solver_next() {
let ty_var = self.infcx.next_ty_var(TypeVariableOrigin {
span: self.span,
kind: TypeVariableOriginKind::NormalizeProjectionType,
});
fulfill_cx.register_predicate_obligation(
self.infcx,
traits::Obligation::new(
tcx,
cause.clone(),
self.param_env,
ty::Binder::dummy(ty::ProjectionPredicate {
projection_ty: tcx.mk_alias_ty(tcx.lang_items().deref_target()?, [ty]),
term: ty_var.into(),
}),
),
);
ty_var
} else {
let normalized_ty = self
.infcx
.at(&cause, self.param_env)
.normalize(tcx.mk_projection(tcx.lang_items().deref_target()?, trait_ref.substs));
normalized_ty.into_value_registering_obligations(self.infcx, &mut *fulfill_cx)
};

let errors = fulfill_cx.select_where_possible(&self.infcx);
if !errors.is_empty() {
// This shouldn't happen, except for evaluate/fulfill mismatches,
// but that's not a reason for an ICE (`predicate_may_hold` is conservative
// by design).
debug!("overloaded_deref_ty: encountered errors {:?} while fulfilling", errors);
return None;
}
let obligations = fulfillcx.pending_obligations();
let obligations = fulfill_cx.pending_obligations();
debug!("overloaded_deref_ty({:?}) = ({:?}, {:?})", ty, normalized_ty, obligations);
self.state.obligations.extend(obligations);

2 changes: 1 addition & 1 deletion compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
@@ -199,7 +199,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
}

#[inline]
fn type_variables(&mut self) -> type_variable::TypeVariableTable<'_, 'tcx> {
pub fn type_variables(&mut self) -> type_variable::TypeVariableTable<'_, 'tcx> {
self.type_variable_storage.with_log(&mut self.undo_log)
}

12 changes: 12 additions & 0 deletions compiler/rustc_middle/src/traits/solve.rs
Original file line number Diff line number Diff line change
@@ -125,6 +125,7 @@ pub struct ExternalConstraintsData<'tcx> {
// FIXME: implement this.
pub region_constraints: QueryRegionConstraints<'tcx>,
pub opaque_types: Vec<(Ty<'tcx>, Ty<'tcx>)>,
pub stalled_subtypes: Vec<(Ty<'tcx>, Ty<'tcx>)>,
}

// FIXME: Having to clone `region_constraints` for folding feels bad and
@@ -144,13 +145,23 @@ impl<'tcx> TypeFoldable<TyCtxt<'tcx>> for ExternalConstraints<'tcx> {
.iter()
.map(|opaque| opaque.try_fold_with(folder))
.collect::<Result<_, F::Error>>()?,
stalled_subtypes: self
.stalled_subtypes
.iter()
.map(|subtype| subtype.try_fold_with(folder))
.collect::<Result<_, F::Error>>()?,
}))
}

fn fold_with<F: TypeFolder<TyCtxt<'tcx>>>(self, folder: &mut F) -> Self {
TypeFolder::interner(folder).mk_external_constraints(ExternalConstraintsData {
region_constraints: self.region_constraints.clone().fold_with(folder),
opaque_types: self.opaque_types.iter().map(|opaque| opaque.fold_with(folder)).collect(),
stalled_subtypes: self
.stalled_subtypes
.iter()
.map(|subtype| subtype.fold_with(folder))
.collect(),
})
}
}
@@ -162,6 +173,7 @@ impl<'tcx> TypeVisitable<TyCtxt<'tcx>> for ExternalConstraints<'tcx> {
) -> std::ops::ControlFlow<V::BreakTy> {
self.region_constraints.visit_with(visitor)?;
self.opaque_types.visit_with(visitor)?;
self.stalled_subtypes.visit_with(visitor)?;
ControlFlow::Continue(())
}
}
8 changes: 8 additions & 0 deletions compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
Original file line number Diff line number Diff line change
@@ -57,6 +57,11 @@ pub struct EvalCtxt<'a, 'tcx> {
pub(super) search_graph: &'a mut SearchGraph<'tcx>,

pub(super) nested_goals: NestedGoals<'tcx>,

// Subtyping predicates which have stalled. These are passed down in query
// responses to help detect cycle errors in cases where we're computing a
// goal like `?0 <: Box<?1>` when we already know that `?0 <: ?1`.
pub(super) stalled_subtypes: Vec<(Ty<'tcx>, Ty<'tcx>)>,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -121,6 +126,7 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
max_input_universe: ty::UniverseIndex::ROOT,
var_values: CanonicalVarValues::dummy(),
nested_goals: NestedGoals::new(),
stalled_subtypes: vec![],
};
let result = ecx.evaluate_goal(IsNormalizesToHack::No, goal);

@@ -172,6 +178,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
max_input_universe: canonical_goal.max_universe,
search_graph,
nested_goals: NestedGoals::new(),
stalled_subtypes: vec![],
};
ecx.compute_goal(goal)
})
@@ -404,6 +411,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
max_input_universe: self.max_input_universe,
search_graph: self.search_graph,
nested_goals: self.nested_goals.clone(),
stalled_subtypes: self.stalled_subtypes.clone(),
};
self.infcx.probe(|_| f(&mut ecx))
}
31 changes: 24 additions & 7 deletions compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ use rustc_infer::infer::canonical::CanonicalVarValues;
use rustc_infer::infer::canonical::{CanonicalExt, QueryRegionConstraints};
use rustc_middle::traits::query::NoSolution;
use rustc_middle::traits::solve::{ExternalConstraints, ExternalConstraintsData};
use rustc_middle::ty::{self, GenericArgKind};
use rustc_middle::ty::{self, GenericArgKind, Ty};
use rustc_span::DUMMY_SP;
use std::iter;
use std::ops::Deref;
@@ -79,9 +79,11 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
)
});
let opaque_types = self.infcx.clone_opaque_types_for_query_response();
Ok(self
.tcx()
.mk_external_constraints(ExternalConstraintsData { region_constraints, opaque_types }))
Ok(self.tcx().mk_external_constraints(ExternalConstraintsData {
region_constraints,
opaque_types,
stalled_subtypes: self.stalled_subtypes.clone(),
}))
}

/// After calling a canonical query, we apply the constraints returned
@@ -102,12 +104,17 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
let Response { var_values, external_constraints, certainty } =
response.substitute(self.tcx(), &substitution);

let nested_goals = self.unify_query_var_values(param_env, &original_values, var_values)?;

// FIXME: implement external constraints.
let ExternalConstraintsData { region_constraints, opaque_types: _ } =
let ExternalConstraintsData { region_constraints, stalled_subtypes, opaque_types: _ } =
external_constraints.deref();
self.register_region_constraints(region_constraints);
assert!(
stalled_subtypes.is_empty() || certainty != Certainty::Yes,
"stalled_subtypes is non-empty: {stalled_subtypes:?}, but certainty is Yes"
);
self.register_stalled_subtypes(stalled_subtypes);

let nested_goals = self.unify_query_var_values(param_env, &original_values, var_values)?;

Ok((certainty, nested_goals))
}
@@ -227,4 +234,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
let _ = member_constraint;
}
}

fn register_stalled_subtypes(&mut self, stalled_subtypes: &[(Ty<'tcx>, Ty<'tcx>)]) {
for (sub, sup) in stalled_subtypes {
if let ty::Infer(ty::TyVar(sub_vid)) = *self.infcx.shallow_resolve(*sub).kind()
&& let ty::Infer(ty::TyVar(sup_vid)) = *self.infcx.shallow_resolve(*sup).kind()
{
self.infcx.inner.borrow_mut().type_variables().sub(sub_vid, sup_vid);
}
}
}
}
1 change: 1 addition & 0 deletions compiler/rustc_trait_selection/src/solve/mod.rs
Original file line number Diff line number Diff line change
@@ -105,6 +105,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
goal: Goal<'tcx, SubtypePredicate<'tcx>>,
) -> QueryResult<'tcx> {
if goal.predicate.a.is_ty_var() && goal.predicate.b.is_ty_var() {
self.stalled_subtypes.push((goal.predicate.a, goal.predicate.b));
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
} else {
self.sub(goal.param_env, goal.predicate.a, goal.predicate.b)?;
8 changes: 8 additions & 0 deletions tests/ui/traits/new-solver/autoderef-infinite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// compile-flags: -Ztrait-solver=next

fn main() {
let y = [Default::default()];
y[0].method();
//~^ ERROR type annotations needed
//~| ERROR no method named `method` found
}
16 changes: 16 additions & 0 deletions tests/ui/traits/new-solver/autoderef-infinite.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error[E0282]: type annotations needed
--> $DIR/autoderef-infinite.rs:5:10
|
LL | y[0].method();
| ^^^^^^ cannot infer type

error[E0599]: no method named `method` found for associated type `<usize as SliceIndex<[_]>>::Output` in the current scope
--> $DIR/autoderef-infinite.rs:5:10
|
LL | y[0].method();
| ^^^^^^ method not found in `<usize as SliceIndex<[_]>>::Output`

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0282, E0599.
For more information about an error, try `rustc --explain E0282`.
8 changes: 8 additions & 0 deletions tests/ui/traits/new-solver/cyclic-type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// compile-flags: -Ztrait-solver=next

fn main() {
let x;
x = Box::new(x);
//~^ ERROR mismatched types
//~| NOTE cyclic type of infinite size
}
14 changes: 14 additions & 0 deletions tests/ui/traits/new-solver/cyclic-type.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0308]: mismatched types
--> $DIR/cyclic-type.rs:5:9
|
LL | x = Box::new(x);
| ^^^^^^^^^^^ cyclic type of infinite size
|
help: consider unboxing the value
|
LL | x = *Box::new(x);
| +

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
11 changes: 4 additions & 7 deletions tests/ui/traits/new-solver/equating-projection-cyclically.stderr
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
error[E0308]: mismatched types
--> $DIR/equating-projection-cyclically.rs:22:19
--> $DIR/equating-projection-cyclically.rs:22:9
|
LL | x = transform(x);
| ^ expected inferred type, found associated type
|
= note: expected type `_`
found associated type `<_ as Test>::Assoc`
= help: consider constraining the associated type `<_ as Test>::Assoc` to `_`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
| ^^^^^^^^^^^^- help: try using a conversion method: `.to_string()`
| |
| cyclic type of infinite size

error: aborting due to previous error

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
error[E0282]: type annotations needed
--> $DIR/normalizes_to_ignores_unnormalizable_candidate.rs:36:5
--> $DIR/normalizes_to_ignores_unnormalizable_candidate.rs:36:9
|
LL | foo(unconstrained())
| ^^^ cannot infer type of the type parameter `T` declared on the function `foo`
| ^^^^^^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `unconstrained`
|
help: consider specifying the generic argument
|
LL | foo::<T>(unconstrained())
| +++++
LL | foo(unconstrained::<T>())
| +++++

error: aborting due to previous error