Rollup merge of #110614 - compiler-errors:new-solver-overflow-response, r=lcnr
Clear response values for overflow in new solver When we have an overflow, return a trivial query response. This fixes an ICE with the code described in #110544: ```rust trait Trait {} struct W<T>(T); impl<T, U> Trait for W<(W<T>, W<U>)> where W<T>: Trait, W<U>: Trait, {} fn impls<T: Trait>() {} fn main() { impls::<W<_>>() } ``` Where, while proving `W<?0>: Trait`, we overflow but still apply the query response of `?0 = (W<?1>, W<?2>)`. Then while re-processing the query to validate that our evaluation result was stable, we get a different query response that looks like `?1 = (W<?3>, W<?4>), ?2 = (W<?5>, W<?6>)`, and so we trigger the ICE. Also, by returning a trivial query response we also avoid the infinite-loop/OOM behavior of the old solver. r? ``@lcnr``
This commit is contained in:
commit
6da62a40f2
5 changed files with 121 additions and 19 deletions
|
@ -3,7 +3,8 @@ use rustc_infer::infer::at::ToTrace;
|
||||||
use rustc_infer::infer::canonical::CanonicalVarValues;
|
use rustc_infer::infer::canonical::CanonicalVarValues;
|
||||||
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||||
use rustc_infer::infer::{
|
use rustc_infer::infer::{
|
||||||
DefineOpaqueTypes, InferCtxt, InferOk, LateBoundRegionConversionTime, TyCtxtInferExt,
|
DefineOpaqueTypes, InferCtxt, InferOk, LateBoundRegionConversionTime, RegionVariableOrigin,
|
||||||
|
TyCtxtInferExt,
|
||||||
};
|
};
|
||||||
use rustc_infer::traits::query::NoSolution;
|
use rustc_infer::traits::query::NoSolution;
|
||||||
use rustc_infer::traits::ObligationCause;
|
use rustc_infer::traits::ObligationCause;
|
||||||
|
@ -223,18 +224,20 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
||||||
{
|
{
|
||||||
debug!("rerunning goal to check result is stable");
|
debug!("rerunning goal to check result is stable");
|
||||||
let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
||||||
let canonical_response =
|
let new_canonical_response =
|
||||||
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
|
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
|
||||||
if !canonical_response.value.var_values.is_identity() {
|
if !new_canonical_response.value.var_values.is_identity() {
|
||||||
bug!(
|
bug!(
|
||||||
"unstable result: re-canonicalized goal={canonical_goal:#?} \
|
"unstable result: re-canonicalized goal={canonical_goal:#?} \
|
||||||
response={canonical_response:#?}"
|
first_response={canonical_response:#?} \
|
||||||
|
second_response={new_canonical_response:#?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if certainty != canonical_response.value.certainty {
|
if certainty != new_canonical_response.value.certainty {
|
||||||
bug!(
|
bug!(
|
||||||
"unstable certainty: {certainty:#?} re-canonicalized goal={canonical_goal:#?} \
|
"unstable certainty: {certainty:#?} re-canonicalized goal={canonical_goal:#?} \
|
||||||
response={canonical_response:#?}"
|
first_response={canonical_response:#?} \
|
||||||
|
second_response={new_canonical_response:#?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -434,6 +437,10 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn next_region_infer(&self) -> ty::Region<'tcx> {
|
||||||
|
self.infcx.next_region_var(RegionVariableOrigin::MiscVariable(DUMMY_SP))
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> {
|
pub(super) fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> {
|
||||||
self.infcx.next_const_var(
|
self.infcx.next_const_var(
|
||||||
ty,
|
ty,
|
||||||
|
|
|
@ -16,7 +16,7 @@ use rustc_infer::infer::canonical::query_response::make_query_region_constraints
|
||||||
use rustc_infer::infer::canonical::CanonicalVarValues;
|
use rustc_infer::infer::canonical::CanonicalVarValues;
|
||||||
use rustc_infer::infer::canonical::{CanonicalExt, QueryRegionConstraints};
|
use rustc_infer::infer::canonical::{CanonicalExt, QueryRegionConstraints};
|
||||||
use rustc_middle::traits::query::NoSolution;
|
use rustc_middle::traits::query::NoSolution;
|
||||||
use rustc_middle::traits::solve::{ExternalConstraints, ExternalConstraintsData};
|
use rustc_middle::traits::solve::{ExternalConstraints, ExternalConstraintsData, MaybeCause};
|
||||||
use rustc_middle::ty::{self, BoundVar, GenericArgKind};
|
use rustc_middle::ty::{self, BoundVar, GenericArgKind};
|
||||||
use rustc_span::DUMMY_SP;
|
use rustc_span::DUMMY_SP;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
@ -60,9 +60,27 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
|
|
||||||
let certainty = certainty.unify_with(goals_certainty);
|
let certainty = certainty.unify_with(goals_certainty);
|
||||||
|
|
||||||
|
let response = match certainty {
|
||||||
|
Certainty::Yes | Certainty::Maybe(MaybeCause::Ambiguity) => {
|
||||||
let external_constraints = self.compute_external_query_constraints()?;
|
let external_constraints = self.compute_external_query_constraints()?;
|
||||||
|
Response { var_values: self.var_values, external_constraints, certainty }
|
||||||
|
}
|
||||||
|
Certainty::Maybe(MaybeCause::Overflow) => {
|
||||||
|
// If we have overflow, it's probable that we're substituting a type
|
||||||
|
// into itself infinitely and any partial substitutions in the query
|
||||||
|
// response are probably not useful anyways, so just return an empty
|
||||||
|
// query response.
|
||||||
|
//
|
||||||
|
// This may prevent us from potentially useful inference, e.g.
|
||||||
|
// 2 candidates, one ambiguous and one overflow, which both
|
||||||
|
// have the same inference constraints.
|
||||||
|
//
|
||||||
|
// Changing this to retain some constraints in the future
|
||||||
|
// won't be a breaking change, so this is good enough for now.
|
||||||
|
return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Overflow));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let response = Response { var_values: self.var_values, external_constraints, certainty };
|
|
||||||
let canonical = Canonicalizer::canonicalize(
|
let canonical = Canonicalizer::canonicalize(
|
||||||
self.infcx,
|
self.infcx,
|
||||||
CanonicalizeMode::Response { max_input_universe: self.max_input_universe },
|
CanonicalizeMode::Response { max_input_universe: self.max_input_universe },
|
||||||
|
@ -72,6 +90,40 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
Ok(canonical)
|
Ok(canonical)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a totally unconstrained, ambiguous response to a goal.
|
||||||
|
///
|
||||||
|
/// Take care when using this, since often it's useful to respond with
|
||||||
|
/// ambiguity but return constrained variables to guide inference.
|
||||||
|
pub(in crate::solve) fn make_ambiguous_response_no_constraints(
|
||||||
|
&self,
|
||||||
|
maybe_cause: MaybeCause,
|
||||||
|
) -> CanonicalResponse<'tcx> {
|
||||||
|
let unconstrained_response = Response {
|
||||||
|
var_values: CanonicalVarValues {
|
||||||
|
var_values: self.tcx().mk_substs_from_iter(self.var_values.var_values.iter().map(
|
||||||
|
|arg| -> ty::GenericArg<'tcx> {
|
||||||
|
match arg.unpack() {
|
||||||
|
GenericArgKind::Lifetime(_) => self.next_region_infer().into(),
|
||||||
|
GenericArgKind::Type(_) => self.next_ty_infer().into(),
|
||||||
|
GenericArgKind::Const(ct) => self.next_const_infer(ct.ty()).into(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
external_constraints: self
|
||||||
|
.tcx()
|
||||||
|
.mk_external_constraints(ExternalConstraintsData::default()),
|
||||||
|
certainty: Certainty::Maybe(maybe_cause),
|
||||||
|
};
|
||||||
|
|
||||||
|
Canonicalizer::canonicalize(
|
||||||
|
self.infcx,
|
||||||
|
CanonicalizeMode::Response { max_input_universe: self.max_input_universe },
|
||||||
|
&mut Default::default(),
|
||||||
|
unconstrained_response,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self), ret)]
|
#[instrument(level = "debug", skip(self), ret)]
|
||||||
fn compute_external_query_constraints(&self) -> Result<ExternalConstraints<'tcx>, NoSolution> {
|
fn compute_external_query_constraints(&self) -> Result<ExternalConstraints<'tcx>, NoSolution> {
|
||||||
// Cannot use `take_registered_region_obligations` as we may compute the response
|
// Cannot use `take_registered_region_obligations` as we may compute the response
|
||||||
|
|
|
@ -340,17 +340,17 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
if responses.is_empty() {
|
if responses.is_empty() {
|
||||||
return Err(NoSolution);
|
return Err(NoSolution);
|
||||||
}
|
}
|
||||||
let certainty = responses.iter().fold(Certainty::AMBIGUOUS, |certainty, response| {
|
|
||||||
certainty.unify_with(response.value.certainty)
|
|
||||||
});
|
|
||||||
|
|
||||||
let response = self.evaluate_added_goals_and_make_canonical_response(certainty);
|
let Certainty::Maybe(maybe_cause) = responses.iter().fold(
|
||||||
if let Ok(response) = response {
|
Certainty::AMBIGUOUS,
|
||||||
assert!(response.has_no_inference_or_external_constraints());
|
|certainty, response| {
|
||||||
Ok(response)
|
certainty.unify_with(response.value.certainty)
|
||||||
} else {
|
},
|
||||||
bug!("failed to make floundered response: {responses:?}");
|
) else {
|
||||||
}
|
bug!("expected flounder response to be ambiguous")
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(self.make_ambiguous_response_no_constraints(maybe_cause))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
tests/ui/traits/new-solver/exponential-trait-goals.rs
Normal file
20
tests/ui/traits/new-solver/exponential-trait-goals.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// compile-flags: -Ztrait-solver=next
|
||||||
|
|
||||||
|
trait Trait {}
|
||||||
|
|
||||||
|
struct W<T>(T);
|
||||||
|
|
||||||
|
impl<T, U> Trait for W<(W<T>, W<U>)>
|
||||||
|
where
|
||||||
|
W<T>: Trait,
|
||||||
|
W<U>: Trait,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impls<T: Trait>() {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
impls::<W<_>>();
|
||||||
|
//~^ ERROR type annotations needed
|
||||||
|
//~| ERROR overflow evaluating the requirement `W<_>: Trait`
|
||||||
|
}
|
23
tests/ui/traits/new-solver/exponential-trait-goals.stderr
Normal file
23
tests/ui/traits/new-solver/exponential-trait-goals.stderr
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
error[E0282]: type annotations needed
|
||||||
|
--> $DIR/exponential-trait-goals.rs:17:5
|
||||||
|
|
|
||||||
|
LL | impls::<W<_>>();
|
||||||
|
| ^^^^^^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `impls`
|
||||||
|
|
||||||
|
error[E0275]: overflow evaluating the requirement `W<_>: Trait`
|
||||||
|
--> $DIR/exponential-trait-goals.rs:17:5
|
||||||
|
|
|
||||||
|
LL | impls::<W<_>>();
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`exponential_trait_goals`)
|
||||||
|
note: required by a bound in `impls`
|
||||||
|
--> $DIR/exponential-trait-goals.rs:14:13
|
||||||
|
|
|
||||||
|
LL | fn impls<T: Trait>() {}
|
||||||
|
| ^^^^^ required by this bound in `impls`
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
Some errors have detailed explanations: E0275, E0282.
|
||||||
|
For more information about an error, try `rustc --explain E0275`.
|
Loading…
Add table
Add a link
Reference in a new issue