Auto merge of #117278 - lcnr:try-normalize-ty, r=compiler-errors
new solver normalization improvements cool beans At the core of this PR is a `try_normalize_ty` which stops for rigid aliases by using `commit_if_ok`. Reworks alias-relate to fully normalize both the lhs and rhs and then equate the resulting rigid (or inference) types. This fixes https://github.com/rust-lang/trait-system-refactor-initiative/issues/68 by avoiding the exponential blowup. Also supersedes #116369 by only defining opaque types if the hidden type is rigid. I removed the stability check in `EvalCtxt::evaluate_goal` due to https://github.com/rust-lang/trait-system-refactor-initiative/issues/75. While I personally have opinions on how to fix it, that still requires further t-types/`@nikomatsakis` buy-in, so I removed that for now. Once we've decided on our approach there, we can revert this commit. r? `@compiler-errors`
This commit is contained in:
commit
78efca8845
30 changed files with 411 additions and 317 deletions
|
@ -122,6 +122,8 @@ pub enum ProbeStep<'tcx> {
|
||||||
/// used whenever there are multiple candidates to prove the
|
/// used whenever there are multiple candidates to prove the
|
||||||
/// current goalby .
|
/// current goalby .
|
||||||
NestedProbe(Probe<'tcx>),
|
NestedProbe(Probe<'tcx>),
|
||||||
|
CommitIfOkStart,
|
||||||
|
CommitIfOkSuccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What kind of probe we're in. In case the probe represents a candidate, or
|
/// What kind of probe we're in. In case the probe represents a candidate, or
|
||||||
|
@ -142,6 +144,9 @@ pub enum ProbeKind<'tcx> {
|
||||||
/// Used in the probe that wraps normalizing the non-self type for the unsize
|
/// Used in the probe that wraps normalizing the non-self type for the unsize
|
||||||
/// trait, which is also structurally matched on.
|
/// trait, which is also structurally matched on.
|
||||||
UnsizeAssembly,
|
UnsizeAssembly,
|
||||||
|
/// A call to `EvalCtxt::commit_if_ok` which failed, causing the work
|
||||||
|
/// to be discarded.
|
||||||
|
CommitIfOk,
|
||||||
/// During upcasting from some source object to target object type, used to
|
/// During upcasting from some source object to target object type, used to
|
||||||
/// do a probe to find out what projection type(s) may be used to prove that
|
/// do a probe to find out what projection type(s) may be used to prove that
|
||||||
/// the source type upholds all of the target type's object bounds.
|
/// the source type upholds all of the target type's object bounds.
|
||||||
|
|
|
@ -109,6 +109,9 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
|
||||||
ProbeKind::UpcastProjectionCompatibility => {
|
ProbeKind::UpcastProjectionCompatibility => {
|
||||||
writeln!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:")
|
writeln!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:")
|
||||||
}
|
}
|
||||||
|
ProbeKind::CommitIfOk => {
|
||||||
|
writeln!(self.f, "COMMIT_IF_OK:")
|
||||||
|
}
|
||||||
ProbeKind::MiscCandidate { name, result } => {
|
ProbeKind::MiscCandidate { name, result } => {
|
||||||
writeln!(self.f, "CANDIDATE {name}: {result:?}")
|
writeln!(self.f, "CANDIDATE {name}: {result:?}")
|
||||||
}
|
}
|
||||||
|
@ -123,6 +126,8 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
|
||||||
ProbeStep::AddGoal(goal) => writeln!(this.f, "ADDED GOAL: {goal:?}")?,
|
ProbeStep::AddGoal(goal) => writeln!(this.f, "ADDED GOAL: {goal:?}")?,
|
||||||
ProbeStep::EvaluateGoals(eval) => this.format_added_goals_evaluation(eval)?,
|
ProbeStep::EvaluateGoals(eval) => this.format_added_goals_evaluation(eval)?,
|
||||||
ProbeStep::NestedProbe(probe) => this.format_probe(probe)?,
|
ProbeStep::NestedProbe(probe) => this.format_probe(probe)?,
|
||||||
|
ProbeStep::CommitIfOkStart => writeln!(this.f, "COMMIT_IF_OK START")?,
|
||||||
|
ProbeStep::CommitIfOkSuccess => writeln!(this.f, "COMMIT_IF_OK SUCCESS")?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1245,6 +1245,28 @@ impl<'tcx> AliasTy<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this alias type is an opaque.
|
||||||
|
pub fn is_opaque(self, tcx: TyCtxt<'tcx>) -> bool {
|
||||||
|
matches!(self.opt_kind(tcx), Some(ty::AliasKind::Opaque))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: rename `AliasTy` to `AliasTerm` and always handle
|
||||||
|
/// constants. This function can then be removed.
|
||||||
|
pub fn opt_kind(self, tcx: TyCtxt<'tcx>) -> Option<ty::AliasKind> {
|
||||||
|
match tcx.def_kind(self.def_id) {
|
||||||
|
DefKind::AssocTy
|
||||||
|
if let DefKind::Impl { of_trait: false } =
|
||||||
|
tcx.def_kind(tcx.parent(self.def_id)) =>
|
||||||
|
{
|
||||||
|
Some(ty::Inherent)
|
||||||
|
}
|
||||||
|
DefKind::AssocTy => Some(ty::Projection),
|
||||||
|
DefKind::OpaqueTy => Some(ty::Opaque),
|
||||||
|
DefKind::TyAlias => Some(ty::Weak),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
|
pub fn to_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
|
||||||
Ty::new_alias(tcx, self.kind(tcx), self)
|
Ty::new_alias(tcx, self.kind(tcx), self)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,18 +11,12 @@
|
||||||
//! * bidirectional-normalizes-to: If `A` and `B` are both projections, and both
|
//! * bidirectional-normalizes-to: If `A` and `B` are both projections, and both
|
||||||
//! may apply, then we can compute the "intersection" of both normalizes-to by
|
//! may apply, then we can compute the "intersection" of both normalizes-to by
|
||||||
//! performing them together. This is used specifically to resolve ambiguities.
|
//! performing them together. This is used specifically to resolve ambiguities.
|
||||||
use super::{EvalCtxt, SolverMode};
|
use super::EvalCtxt;
|
||||||
|
use rustc_infer::infer::DefineOpaqueTypes;
|
||||||
use rustc_infer::traits::query::NoSolution;
|
use rustc_infer::traits::query::NoSolution;
|
||||||
use rustc_middle::traits::solve::{Certainty, Goal, QueryResult};
|
use rustc_middle::traits::solve::{Certainty, Goal, QueryResult};
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
|
|
||||||
/// We may need to invert the alias relation direction if dealing an alias on the RHS.
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Invert {
|
|
||||||
No,
|
|
||||||
Yes,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'tcx> EvalCtxt<'_, 'tcx> {
|
impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
#[instrument(level = "debug", skip(self), ret)]
|
#[instrument(level = "debug", skip(self), ret)]
|
||||||
pub(super) fn compute_alias_relate_goal(
|
pub(super) fn compute_alias_relate_goal(
|
||||||
|
@ -31,187 +25,130 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
) -> QueryResult<'tcx> {
|
) -> QueryResult<'tcx> {
|
||||||
let tcx = self.tcx();
|
let tcx = self.tcx();
|
||||||
let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;
|
let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;
|
||||||
if lhs.is_infer() || rhs.is_infer() {
|
|
||||||
bug!(
|
let Some(lhs) = self.try_normalize_term(param_env, lhs)? else {
|
||||||
"`AliasRelate` goal with an infer var on lhs or rhs which should have been instantiated"
|
return self.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
|
||||||
);
|
};
|
||||||
}
|
|
||||||
|
let Some(rhs) = self.try_normalize_term(param_env, rhs)? else {
|
||||||
|
return self.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
|
||||||
|
};
|
||||||
|
|
||||||
|
let variance = match direction {
|
||||||
|
ty::AliasRelationDirection::Equate => ty::Variance::Invariant,
|
||||||
|
ty::AliasRelationDirection::Subtype => ty::Variance::Covariant,
|
||||||
|
};
|
||||||
|
|
||||||
match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
|
match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
|
||||||
(None, None) => bug!("`AliasRelate` goal without an alias on either lhs or rhs"),
|
(None, None) => {
|
||||||
|
self.relate(param_env, lhs, variance, rhs)?;
|
||||||
|
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||||
|
}
|
||||||
|
|
||||||
// RHS is not a projection, only way this is true is if LHS normalizes-to RHS
|
(Some(alias), None) => {
|
||||||
(Some(alias_lhs), None) => self.assemble_normalizes_to_candidate(
|
if rhs.is_infer() {
|
||||||
param_env,
|
self.relate(param_env, lhs, variance, rhs)?;
|
||||||
alias_lhs,
|
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||||
rhs,
|
} else if alias.is_opaque(tcx) {
|
||||||
direction,
|
self.define_opaque(param_env, alias, rhs)
|
||||||
Invert::No,
|
} else {
|
||||||
),
|
Err(NoSolution)
|
||||||
|
}
|
||||||
// LHS is not a projection, only way this is true is if RHS normalizes-to LHS
|
}
|
||||||
(None, Some(alias_rhs)) => self.assemble_normalizes_to_candidate(
|
(None, Some(alias)) => {
|
||||||
param_env,
|
if lhs.is_infer() {
|
||||||
alias_rhs,
|
self.relate(param_env, lhs, variance, rhs)?;
|
||||||
lhs,
|
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||||
direction,
|
} else if alias.is_opaque(tcx) {
|
||||||
Invert::Yes,
|
self.define_opaque(param_env, alias, lhs)
|
||||||
),
|
} else {
|
||||||
|
Err(NoSolution)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(Some(alias_lhs), Some(alias_rhs)) => {
|
(Some(alias_lhs), Some(alias_rhs)) => {
|
||||||
debug!("both sides are aliases");
|
self.relate_rigid_alias_or_opaque(param_env, alias_lhs, variance, alias_rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut candidates = Vec::new();
|
/// Normalize the `term` to equate it later. This does not define opaque types.
|
||||||
// LHS normalizes-to RHS
|
#[instrument(level = "debug", skip(self, param_env), ret)]
|
||||||
candidates.extend(self.assemble_normalizes_to_candidate(
|
fn try_normalize_term(
|
||||||
param_env,
|
&mut self,
|
||||||
alias_lhs,
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
rhs,
|
term: ty::Term<'tcx>,
|
||||||
direction,
|
) -> Result<Option<ty::Term<'tcx>>, NoSolution> {
|
||||||
Invert::No,
|
match term.unpack() {
|
||||||
));
|
ty::TermKind::Ty(ty) => {
|
||||||
// RHS normalizes-to RHS
|
// We do no define opaque types here but instead do so in `relate_rigid_alias_or_opaque`.
|
||||||
candidates.extend(self.assemble_normalizes_to_candidate(
|
Ok(self
|
||||||
param_env,
|
.try_normalize_ty_recur(param_env, DefineOpaqueTypes::No, 0, ty)
|
||||||
alias_rhs,
|
.map(Into::into))
|
||||||
lhs,
|
}
|
||||||
direction,
|
ty::TermKind::Const(_) => {
|
||||||
Invert::Yes,
|
if let Some(alias) = term.to_alias_ty(self.tcx()) {
|
||||||
));
|
let term = self.next_term_infer_of_kind(term);
|
||||||
// Relate via args
|
self.add_goal(Goal::new(
|
||||||
candidates.extend(
|
self.tcx(),
|
||||||
self.assemble_subst_relate_candidate(
|
param_env,
|
||||||
param_env, alias_lhs, alias_rhs, direction,
|
ty::ProjectionPredicate { projection_ty: alias, term },
|
||||||
),
|
));
|
||||||
);
|
self.try_evaluate_added_goals()?;
|
||||||
debug!(?candidates);
|
Ok(Some(self.resolve_vars_if_possible(term)))
|
||||||
|
|
||||||
if let Some(merged) = self.try_merge_responses(&candidates) {
|
|
||||||
Ok(merged)
|
|
||||||
} else {
|
} else {
|
||||||
// When relating two aliases and we have ambiguity, if both
|
Ok(Some(term))
|
||||||
// aliases can be normalized to something, we prefer
|
|
||||||
// "bidirectionally normalizing" both of them within the same
|
|
||||||
// candidate.
|
|
||||||
//
|
|
||||||
// See <https://github.com/rust-lang/trait-system-refactor-initiative/issues/25>.
|
|
||||||
//
|
|
||||||
// As this is incomplete, we must not do so during coherence.
|
|
||||||
match self.solver_mode() {
|
|
||||||
SolverMode::Normal => {
|
|
||||||
if let Ok(bidirectional_normalizes_to_response) = self
|
|
||||||
.assemble_bidirectional_normalizes_to_candidate(
|
|
||||||
param_env, lhs, rhs, direction,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
Ok(bidirectional_normalizes_to_response)
|
|
||||||
} else {
|
|
||||||
self.flounder(&candidates)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SolverMode::Coherence => self.flounder(&candidates),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self), ret)]
|
fn define_opaque(
|
||||||
fn assemble_normalizes_to_candidate(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
param_env: ty::ParamEnv<'tcx>,
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
alias: ty::AliasTy<'tcx>,
|
opaque: ty::AliasTy<'tcx>,
|
||||||
other: ty::Term<'tcx>,
|
term: ty::Term<'tcx>,
|
||||||
direction: ty::AliasRelationDirection,
|
|
||||||
invert: Invert,
|
|
||||||
) -> QueryResult<'tcx> {
|
) -> QueryResult<'tcx> {
|
||||||
self.probe_misc_candidate("normalizes-to").enter(|ecx| {
|
|
||||||
ecx.normalizes_to_inner(param_env, alias, other, direction, invert)?;
|
|
||||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Computes the normalizes-to branch, with side-effects. This must be performed
|
|
||||||
// in a probe in order to not taint the evaluation context.
|
|
||||||
fn normalizes_to_inner(
|
|
||||||
&mut self,
|
|
||||||
param_env: ty::ParamEnv<'tcx>,
|
|
||||||
alias: ty::AliasTy<'tcx>,
|
|
||||||
other: ty::Term<'tcx>,
|
|
||||||
direction: ty::AliasRelationDirection,
|
|
||||||
invert: Invert,
|
|
||||||
) -> Result<(), NoSolution> {
|
|
||||||
let other = match direction {
|
|
||||||
// This is purely an optimization. No need to instantiate a new
|
|
||||||
// infer var and equate the RHS to it.
|
|
||||||
ty::AliasRelationDirection::Equate => other,
|
|
||||||
|
|
||||||
// Instantiate an infer var and subtype our RHS to it, so that we
|
|
||||||
// properly represent a subtype relation between the LHS and RHS
|
|
||||||
// of the goal.
|
|
||||||
ty::AliasRelationDirection::Subtype => {
|
|
||||||
let fresh = self.next_term_infer_of_kind(other);
|
|
||||||
let (sub, sup) = match invert {
|
|
||||||
Invert::No => (fresh, other),
|
|
||||||
Invert::Yes => (other, fresh),
|
|
||||||
};
|
|
||||||
self.sub(param_env, sub, sup)?;
|
|
||||||
fresh
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.add_goal(Goal::new(
|
self.add_goal(Goal::new(
|
||||||
self.tcx(),
|
self.tcx(),
|
||||||
param_env,
|
param_env,
|
||||||
ty::ProjectionPredicate { projection_ty: alias, term: other },
|
ty::ProjectionPredicate { projection_ty: opaque, term },
|
||||||
));
|
));
|
||||||
|
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assemble_subst_relate_candidate(
|
fn relate_rigid_alias_or_opaque(
|
||||||
&mut self,
|
&mut self,
|
||||||
param_env: ty::ParamEnv<'tcx>,
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
alias_lhs: ty::AliasTy<'tcx>,
|
lhs: ty::AliasTy<'tcx>,
|
||||||
alias_rhs: ty::AliasTy<'tcx>,
|
variance: ty::Variance,
|
||||||
direction: ty::AliasRelationDirection,
|
rhs: ty::AliasTy<'tcx>,
|
||||||
) -> QueryResult<'tcx> {
|
) -> QueryResult<'tcx> {
|
||||||
self.probe_misc_candidate("args relate").enter(|ecx| {
|
let tcx = self.tcx();
|
||||||
match direction {
|
let mut candidates = vec![];
|
||||||
ty::AliasRelationDirection::Equate => {
|
if lhs.is_opaque(tcx) {
|
||||||
ecx.eq(param_env, alias_lhs, alias_rhs)?;
|
candidates.extend(
|
||||||
}
|
self.probe_misc_candidate("define-lhs-opaque")
|
||||||
ty::AliasRelationDirection::Subtype => {
|
.enter(|ecx| ecx.define_opaque(param_env, lhs, rhs.to_ty(tcx).into())),
|
||||||
ecx.sub(param_env, alias_lhs, alias_rhs)?;
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
if rhs.is_opaque(tcx) {
|
||||||
})
|
candidates.extend(
|
||||||
}
|
self.probe_misc_candidate("define-rhs-opaque")
|
||||||
|
.enter(|ecx| ecx.define_opaque(param_env, rhs, lhs.to_ty(tcx).into())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn assemble_bidirectional_normalizes_to_candidate(
|
candidates.extend(self.probe_misc_candidate("args-relate").enter(|ecx| {
|
||||||
&mut self,
|
ecx.relate(param_env, lhs, variance, rhs)?;
|
||||||
param_env: ty::ParamEnv<'tcx>,
|
|
||||||
lhs: ty::Term<'tcx>,
|
|
||||||
rhs: ty::Term<'tcx>,
|
|
||||||
direction: ty::AliasRelationDirection,
|
|
||||||
) -> QueryResult<'tcx> {
|
|
||||||
self.probe_misc_candidate("bidir normalizes-to").enter(|ecx| {
|
|
||||||
ecx.normalizes_to_inner(
|
|
||||||
param_env,
|
|
||||||
lhs.to_alias_ty(ecx.tcx()).unwrap(),
|
|
||||||
rhs,
|
|
||||||
direction,
|
|
||||||
Invert::No,
|
|
||||||
)?;
|
|
||||||
ecx.normalizes_to_inner(
|
|
||||||
param_env,
|
|
||||||
rhs.to_alias_ty(ecx.tcx()).unwrap(),
|
|
||||||
lhs,
|
|
||||||
direction,
|
|
||||||
Invert::Yes,
|
|
||||||
)?;
|
|
||||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||||
})
|
}));
|
||||||
|
|
||||||
|
if let Some(result) = self.try_merge_responses(&candidates) {
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
self.flounder(&candidates)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,7 +352,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
let &ty::Alias(_, projection_ty) = goal.predicate.self_ty().kind() else { return };
|
let &ty::Alias(_, projection_ty) = goal.predicate.self_ty().kind() else { return };
|
||||||
|
|
||||||
candidates.extend(self.probe(|_| ProbeKind::NormalizedSelfTyAssembly).enter(|ecx| {
|
candidates.extend(self.probe(|_| ProbeKind::NormalizedSelfTyAssembly).enter(|ecx| {
|
||||||
if num_steps < ecx.local_overflow_limit() {
|
if tcx.recursion_limit().value_within_limit(num_steps) {
|
||||||
let normalized_ty = ecx.next_ty_infer();
|
let normalized_ty = ecx.next_ty_infer();
|
||||||
let normalizes_to_goal = goal.with(
|
let normalizes_to_goal = goal.with(
|
||||||
tcx,
|
tcx,
|
||||||
|
@ -864,23 +864,18 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
|
|
||||||
let result = self.probe_misc_candidate("coherence unknowable").enter(|ecx| {
|
let result = self.probe_misc_candidate("coherence unknowable").enter(|ecx| {
|
||||||
let trait_ref = goal.predicate.trait_ref(tcx);
|
let trait_ref = goal.predicate.trait_ref(tcx);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum FailureKind {
|
struct Overflow;
|
||||||
Overflow,
|
|
||||||
NoSolution(NoSolution),
|
|
||||||
}
|
|
||||||
let lazily_normalize_ty = |ty| match ecx.try_normalize_ty(goal.param_env, ty) {
|
let lazily_normalize_ty = |ty| match ecx.try_normalize_ty(goal.param_env, ty) {
|
||||||
Ok(Some(ty)) => Ok(ty),
|
Some(ty) => Ok(ty),
|
||||||
Ok(None) => Err(FailureKind::Overflow),
|
None => Err(Overflow),
|
||||||
Err(e) => Err(FailureKind::NoSolution(e)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match coherence::trait_ref_is_knowable(tcx, trait_ref, lazily_normalize_ty) {
|
match coherence::trait_ref_is_knowable(tcx, trait_ref, lazily_normalize_ty) {
|
||||||
Err(FailureKind::Overflow) => {
|
Err(Overflow) => {
|
||||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW)
|
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW)
|
||||||
}
|
}
|
||||||
Err(FailureKind::NoSolution(NoSolution)) | Ok(Ok(())) => Err(NoSolution),
|
Ok(Ok(())) => Err(NoSolution),
|
||||||
Ok(Err(_)) => {
|
Ok(Err(_)) => {
|
||||||
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
use super::EvalCtxt;
|
||||||
|
use crate::solve::inspect;
|
||||||
|
use rustc_middle::traits::query::NoSolution;
|
||||||
|
|
||||||
|
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
||||||
|
pub(in crate::solve) fn commit_if_ok<T>(
|
||||||
|
&mut self,
|
||||||
|
f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> Result<T, NoSolution>,
|
||||||
|
) -> Result<T, NoSolution> {
|
||||||
|
let mut nested_ecx = EvalCtxt {
|
||||||
|
infcx: self.infcx,
|
||||||
|
variables: self.variables,
|
||||||
|
var_values: self.var_values,
|
||||||
|
predefined_opaques_in_body: self.predefined_opaques_in_body,
|
||||||
|
max_input_universe: self.max_input_universe,
|
||||||
|
search_graph: self.search_graph,
|
||||||
|
nested_goals: self.nested_goals.clone(),
|
||||||
|
tainted: self.tainted,
|
||||||
|
inspect: self.inspect.new_probe(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = nested_ecx.infcx.commit_if_ok(|_| f(&mut nested_ecx));
|
||||||
|
if result.is_ok() {
|
||||||
|
let EvalCtxt {
|
||||||
|
infcx: _,
|
||||||
|
variables: _,
|
||||||
|
var_values: _,
|
||||||
|
predefined_opaques_in_body: _,
|
||||||
|
max_input_universe: _,
|
||||||
|
search_graph: _,
|
||||||
|
nested_goals,
|
||||||
|
tainted,
|
||||||
|
inspect,
|
||||||
|
} = nested_ecx;
|
||||||
|
self.nested_goals = nested_goals;
|
||||||
|
self.tainted = tainted;
|
||||||
|
self.inspect.integrate_snapshot(inspect);
|
||||||
|
} else {
|
||||||
|
nested_ecx.inspect.probe_kind(inspect::ProbeKind::CommitIfOk);
|
||||||
|
self.inspect.finish_probe(nested_ecx.inspect);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ use super::{search_graph::SearchGraph, Goal};
|
||||||
pub use select::InferCtxtSelectExt;
|
pub use select::InferCtxtSelectExt;
|
||||||
|
|
||||||
mod canonical;
|
mod canonical;
|
||||||
|
mod commit_if_ok;
|
||||||
mod probe;
|
mod probe;
|
||||||
mod select;
|
mod select;
|
||||||
|
|
||||||
|
@ -332,7 +333,6 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
||||||
let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
||||||
let mut goal_evaluation =
|
let mut goal_evaluation =
|
||||||
self.inspect.new_goal_evaluation(goal, &orig_values, goal_evaluation_kind);
|
self.inspect.new_goal_evaluation(goal, &orig_values, goal_evaluation_kind);
|
||||||
let encountered_overflow = self.search_graph.encountered_overflow();
|
|
||||||
let canonical_response = EvalCtxt::evaluate_canonical_goal(
|
let canonical_response = EvalCtxt::evaluate_canonical_goal(
|
||||||
self.tcx(),
|
self.tcx(),
|
||||||
self.search_graph,
|
self.search_graph,
|
||||||
|
@ -367,75 +367,19 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
||||||
bug!("an unchanged goal shouldn't have any side-effects on instantiation");
|
bug!("an unchanged goal shouldn't have any side-effects on instantiation");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that rerunning this query with its inference constraints applied
|
// FIXME: We previously had an assert here that checked that recomputing
|
||||||
// doesn't result in new inference constraints and has the same result.
|
// a goal after applying its constraints did not change its response.
|
||||||
//
|
//
|
||||||
// If we have projection goals like `<T as Trait>::Assoc == u32` we recursively
|
// This assert was removed as it did not hold for goals constraining
|
||||||
// call `exists<U> <T as Trait>::Assoc == U` to enable better caching. This goal
|
// an inference variable to a recursive alias, e.g. in
|
||||||
// could constrain `U` to `u32` which would cause this check to result in a
|
// tests/ui/traits/new-solver/overflow/recursive-self-normalization.rs.
|
||||||
// solver cycle.
|
//
|
||||||
if cfg!(debug_assertions)
|
// Once we have decided on how to handle trait-system-refactor-initiative#75,
|
||||||
&& has_changed
|
// we should re-add an assert here.
|
||||||
&& !matches!(
|
|
||||||
goal_evaluation_kind,
|
|
||||||
GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::Yes }
|
|
||||||
)
|
|
||||||
&& !self.search_graph.in_cycle()
|
|
||||||
{
|
|
||||||
// The nested evaluation has to happen with the original state
|
|
||||||
// of `encountered_overflow`.
|
|
||||||
let from_original_evaluation =
|
|
||||||
self.search_graph.reset_encountered_overflow(encountered_overflow);
|
|
||||||
self.check_evaluate_goal_stable_result(goal, canonical_goal, canonical_response);
|
|
||||||
// In case the evaluation was unstable, we manually make sure that this
|
|
||||||
// debug check does not influence the result of the parent goal.
|
|
||||||
self.search_graph.reset_encountered_overflow(from_original_evaluation);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((has_changed, certainty, nested_goals))
|
Ok((has_changed, certainty, nested_goals))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_evaluate_goal_stable_result(
|
|
||||||
&mut self,
|
|
||||||
goal: Goal<'tcx, ty::Predicate<'tcx>>,
|
|
||||||
original_input: CanonicalInput<'tcx>,
|
|
||||||
original_result: CanonicalResponse<'tcx>,
|
|
||||||
) {
|
|
||||||
let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
|
|
||||||
let result = EvalCtxt::evaluate_canonical_goal(
|
|
||||||
self.tcx(),
|
|
||||||
self.search_graph,
|
|
||||||
canonical_goal,
|
|
||||||
// FIXME(-Ztrait-solver=next): we do not track what happens in `evaluate_canonical_goal`
|
|
||||||
&mut ProofTreeBuilder::new_noop(),
|
|
||||||
);
|
|
||||||
|
|
||||||
macro_rules! fail {
|
|
||||||
($msg:expr) => {{
|
|
||||||
let msg = $msg;
|
|
||||||
warn!(
|
|
||||||
"unstable result: {msg}\n\
|
|
||||||
original goal: {original_input:?},\n\
|
|
||||||
original result: {original_result:?}\n\
|
|
||||||
re-canonicalized goal: {canonical_goal:?}\n\
|
|
||||||
second response: {result:?}"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ok(new_canonical_response) = result else { fail!("second response was error") };
|
|
||||||
// We only check for modulo regions as we convert all regions in
|
|
||||||
// the input to new existentials, even if they're expected to be
|
|
||||||
// `'static` or a placeholder region.
|
|
||||||
if !new_canonical_response.value.var_values.is_identity_modulo_regions() {
|
|
||||||
fail!("additional constraints from second response")
|
|
||||||
}
|
|
||||||
if original_result.value.certainty != new_canonical_response.value.certainty {
|
|
||||||
fail!("unstable certainty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
|
fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
|
||||||
let Goal { param_env, predicate } = goal;
|
let Goal { param_env, predicate } = goal;
|
||||||
let kind = predicate.kind();
|
let kind = predicate.kind();
|
||||||
|
@ -750,6 +694,26 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip(self, param_env), ret)]
|
||||||
|
pub(super) fn relate<T: ToTrace<'tcx>>(
|
||||||
|
&mut self,
|
||||||
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
|
lhs: T,
|
||||||
|
variance: ty::Variance,
|
||||||
|
rhs: T,
|
||||||
|
) -> Result<(), NoSolution> {
|
||||||
|
self.infcx
|
||||||
|
.at(&ObligationCause::dummy(), param_env)
|
||||||
|
.relate(DefineOpaqueTypes::No, lhs, variance, rhs)
|
||||||
|
.map(|InferOk { value: (), obligations }| {
|
||||||
|
self.add_goals(obligations.into_iter().map(|o| o.into()));
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
debug!(?e, "failed to relate");
|
||||||
|
NoSolution
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Equates two values returning the nested goals without adding them
|
/// Equates two values returning the nested goals without adding them
|
||||||
/// to the nested goals of the `EvalCtxt`.
|
/// to the nested goals of the `EvalCtxt`.
|
||||||
///
|
///
|
||||||
|
|
|
@ -120,7 +120,6 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
|
||||||
for step in &probe.steps {
|
for step in &probe.steps {
|
||||||
match step {
|
match step {
|
||||||
&inspect::ProbeStep::AddGoal(goal) => nested_goals.push(goal),
|
&inspect::ProbeStep::AddGoal(goal) => nested_goals.push(goal),
|
||||||
inspect::ProbeStep::EvaluateGoals(_) => (),
|
|
||||||
inspect::ProbeStep::NestedProbe(ref probe) => {
|
inspect::ProbeStep::NestedProbe(ref probe) => {
|
||||||
// Nested probes have to prove goals added in their parent
|
// Nested probes have to prove goals added in their parent
|
||||||
// but do not leak them, so we truncate the added goals
|
// but do not leak them, so we truncate the added goals
|
||||||
|
@ -129,13 +128,17 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
|
||||||
self.candidates_recur(candidates, nested_goals, probe);
|
self.candidates_recur(candidates, nested_goals, probe);
|
||||||
nested_goals.truncate(num_goals);
|
nested_goals.truncate(num_goals);
|
||||||
}
|
}
|
||||||
|
inspect::ProbeStep::EvaluateGoals(_)
|
||||||
|
| inspect::ProbeStep::CommitIfOkStart
|
||||||
|
| inspect::ProbeStep::CommitIfOkSuccess => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match probe.kind {
|
match probe.kind {
|
||||||
inspect::ProbeKind::NormalizedSelfTyAssembly
|
inspect::ProbeKind::NormalizedSelfTyAssembly
|
||||||
| inspect::ProbeKind::UnsizeAssembly
|
| inspect::ProbeKind::UnsizeAssembly
|
||||||
| inspect::ProbeKind::UpcastProjectionCompatibility => (),
|
| inspect::ProbeKind::UpcastProjectionCompatibility
|
||||||
|
| inspect::ProbeKind::CommitIfOk => (),
|
||||||
// We add a candidate for the root evaluation if there
|
// We add a candidate for the root evaluation if there
|
||||||
// is only one way to prove a given goal, e.g. for `WellFormed`.
|
// is only one way to prove a given goal, e.g. for `WellFormed`.
|
||||||
//
|
//
|
||||||
|
|
|
@ -219,6 +219,8 @@ enum WipProbeStep<'tcx> {
|
||||||
AddGoal(inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>),
|
AddGoal(inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>),
|
||||||
EvaluateGoals(WipAddedGoalsEvaluation<'tcx>),
|
EvaluateGoals(WipAddedGoalsEvaluation<'tcx>),
|
||||||
NestedProbe(WipProbe<'tcx>),
|
NestedProbe(WipProbe<'tcx>),
|
||||||
|
CommitIfOkStart,
|
||||||
|
CommitIfOkSuccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> WipProbeStep<'tcx> {
|
impl<'tcx> WipProbeStep<'tcx> {
|
||||||
|
@ -227,6 +229,8 @@ impl<'tcx> WipProbeStep<'tcx> {
|
||||||
WipProbeStep::AddGoal(goal) => inspect::ProbeStep::AddGoal(goal),
|
WipProbeStep::AddGoal(goal) => inspect::ProbeStep::AddGoal(goal),
|
||||||
WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()),
|
WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()),
|
||||||
WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()),
|
WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()),
|
||||||
|
WipProbeStep::CommitIfOkStart => inspect::ProbeStep::CommitIfOkStart,
|
||||||
|
WipProbeStep::CommitIfOkSuccess => inspect::ProbeStep::CommitIfOkSuccess,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -459,6 +463,29 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by `EvalCtxt::commit_if_ok` to flatten the work done inside
|
||||||
|
/// of the probe into the parent.
|
||||||
|
pub fn integrate_snapshot(&mut self, probe: ProofTreeBuilder<'tcx>) {
|
||||||
|
if let Some(this) = self.as_mut() {
|
||||||
|
match (this, *probe.state.unwrap()) {
|
||||||
|
(
|
||||||
|
DebugSolver::Probe(WipProbe { steps, .. })
|
||||||
|
| DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
|
||||||
|
evaluation: WipProbe { steps, .. },
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
DebugSolver::Probe(probe),
|
||||||
|
) => {
|
||||||
|
steps.push(WipProbeStep::CommitIfOkStart);
|
||||||
|
assert_eq!(probe.kind, None);
|
||||||
|
steps.extend(probe.steps);
|
||||||
|
steps.push(WipProbeStep::CommitIfOkSuccess);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> {
|
pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> {
|
||||||
self.nested(|| WipAddedGoalsEvaluation { evaluations: vec![], result: None })
|
self.nested(|| WipAddedGoalsEvaluation { evaluations: vec![], result: None })
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
//! about it on zulip.
|
//! about it on zulip.
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_infer::infer::canonical::{Canonical, CanonicalVarValues};
|
use rustc_infer::infer::canonical::{Canonical, CanonicalVarValues};
|
||||||
|
use rustc_infer::infer::DefineOpaqueTypes;
|
||||||
use rustc_infer::traits::query::NoSolution;
|
use rustc_infer::traits::query::NoSolution;
|
||||||
use rustc_middle::infer::canonical::CanonicalVarInfos;
|
use rustc_middle::infer::canonical::CanonicalVarInfos;
|
||||||
use rustc_middle::traits::solve::{
|
use rustc_middle::traits::solve::{
|
||||||
CanonicalResponse, Certainty, ExternalConstraintsData, Goal, IsNormalizesToHack, QueryResult,
|
CanonicalResponse, Certainty, ExternalConstraintsData, Goal, IsNormalizesToHack, QueryResult,
|
||||||
Response,
|
Response,
|
||||||
};
|
};
|
||||||
use rustc_middle::ty::{self, Ty, TyCtxt, UniverseIndex};
|
use rustc_middle::ty::{self, OpaqueTypeKey, Ty, TyCtxt, UniverseIndex};
|
||||||
use rustc_middle::ty::{
|
use rustc_middle::ty::{
|
||||||
CoercePredicate, RegionOutlivesPredicate, SubtypePredicate, TypeOutlivesPredicate,
|
CoercePredicate, RegionOutlivesPredicate, SubtypePredicate, TypeOutlivesPredicate,
|
||||||
};
|
};
|
||||||
|
@ -297,25 +298,62 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
fn try_normalize_ty(
|
fn try_normalize_ty(
|
||||||
&mut self,
|
&mut self,
|
||||||
param_env: ty::ParamEnv<'tcx>,
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
mut ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
) -> Result<Option<Ty<'tcx>>, NoSolution> {
|
) -> Option<Ty<'tcx>> {
|
||||||
for _ in 0..self.local_overflow_limit() {
|
self.try_normalize_ty_recur(param_env, DefineOpaqueTypes::Yes, 0, ty)
|
||||||
let ty::Alias(_, projection_ty) = *ty.kind() else {
|
}
|
||||||
return Ok(Some(ty));
|
|
||||||
};
|
|
||||||
|
|
||||||
let normalized_ty = self.next_ty_infer();
|
fn try_normalize_ty_recur(
|
||||||
|
&mut self,
|
||||||
|
param_env: ty::ParamEnv<'tcx>,
|
||||||
|
define_opaque_types: DefineOpaqueTypes,
|
||||||
|
depth: usize,
|
||||||
|
ty: Ty<'tcx>,
|
||||||
|
) -> Option<Ty<'tcx>> {
|
||||||
|
if !self.tcx().recursion_limit().value_within_limit(depth) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ty::Alias(kind, projection_ty) = *ty.kind() else {
|
||||||
|
return Some(ty);
|
||||||
|
};
|
||||||
|
|
||||||
|
// We do no always define opaque types eagerly to allow non-defining uses in the defining scope.
|
||||||
|
if let (DefineOpaqueTypes::No, ty::AliasKind::Opaque) = (define_opaque_types, kind) {
|
||||||
|
if let Some(def_id) = projection_ty.def_id.as_local() {
|
||||||
|
if self
|
||||||
|
.unify_existing_opaque_tys(
|
||||||
|
param_env,
|
||||||
|
OpaqueTypeKey { def_id, args: projection_ty.args },
|
||||||
|
self.next_ty_infer(),
|
||||||
|
)
|
||||||
|
.is_empty()
|
||||||
|
{
|
||||||
|
return Some(ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(@lcnr): If the normalization of the alias adds an inference constraint which
|
||||||
|
// causes a previously added goal to fail, then we treat the alias as rigid.
|
||||||
|
//
|
||||||
|
// These feels like a potential issue, I should look into writing some tests here
|
||||||
|
// and then probably changing `commit_if_ok` to not inherit the parent goals.
|
||||||
|
match self.commit_if_ok(|this| {
|
||||||
|
let normalized_ty = this.next_ty_infer();
|
||||||
let normalizes_to_goal = Goal::new(
|
let normalizes_to_goal = Goal::new(
|
||||||
self.tcx(),
|
this.tcx(),
|
||||||
param_env,
|
param_env,
|
||||||
ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() },
|
ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() },
|
||||||
);
|
);
|
||||||
self.add_goal(normalizes_to_goal);
|
this.add_goal(normalizes_to_goal);
|
||||||
self.try_evaluate_added_goals()?;
|
this.try_evaluate_added_goals()?;
|
||||||
ty = self.resolve_vars_if_possible(normalized_ty);
|
let ty = this.resolve_vars_if_possible(normalized_ty);
|
||||||
|
Ok(this.try_normalize_ty_recur(param_env, define_opaque_types, depth + 1, ty))
|
||||||
|
}) {
|
||||||
|
Ok(ty) => ty,
|
||||||
|
Err(NoSolution) => Some(ty),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,10 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
// Prefer opaques registered already.
|
// Prefer opaques registered already.
|
||||||
let opaque_type_key =
|
let opaque_type_key =
|
||||||
ty::OpaqueTypeKey { def_id: opaque_ty_def_id, args: opaque_ty.args };
|
ty::OpaqueTypeKey { def_id: opaque_ty_def_id, args: opaque_ty.args };
|
||||||
|
// FIXME: This also unifies the previous hidden type with the expected.
|
||||||
|
//
|
||||||
|
// If that fails, we insert `expected` as a new hidden type instead of
|
||||||
|
// eagerly emitting an error.
|
||||||
let matches =
|
let matches =
|
||||||
self.unify_existing_opaque_tys(goal.param_env, opaque_type_key, expected);
|
self.unify_existing_opaque_tys(goal.param_env, opaque_type_key, expected);
|
||||||
if !matches.is_empty() {
|
if !matches.is_empty() {
|
||||||
|
@ -53,6 +57,23 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
||||||
return self.flounder(&matches);
|
return self.flounder(&matches);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let expected = match self.try_normalize_ty(goal.param_env, expected) {
|
||||||
|
Some(ty) => {
|
||||||
|
if ty.is_ty_var() {
|
||||||
|
return self.evaluate_added_goals_and_make_canonical_response(
|
||||||
|
Certainty::AMBIGUOUS,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return self
|
||||||
|
.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Otherwise, define a new opaque type
|
// Otherwise, define a new opaque type
|
||||||
self.insert_hidden_type(opaque_type_key, goal.param_env, expected)?;
|
self.insert_hidden_type(opaque_type_key, goal.param_env, expected)?;
|
||||||
self.add_item_bounds_for_hidden_type(
|
self.add_item_bounds_for_hidden_type(
|
||||||
|
|
|
@ -110,39 +110,6 @@ impl<'tcx> SearchGraph<'tcx> {
|
||||||
self.stack.is_empty()
|
self.stack.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether we're currently in a cycle. This should only be used
|
|
||||||
/// for debug assertions.
|
|
||||||
pub(super) fn in_cycle(&self) -> bool {
|
|
||||||
if let Some(stack_depth) = self.stack.last_index() {
|
|
||||||
// Either the current goal on the stack is the root of a cycle
|
|
||||||
// or it depends on a goal with a lower depth.
|
|
||||||
self.stack[stack_depth].has_been_used
|
|
||||||
|| self.stack[stack_depth].cycle_root_depth != stack_depth
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetches whether the current goal encountered overflow.
|
|
||||||
///
|
|
||||||
/// This should only be used for the check in `evaluate_goal`.
|
|
||||||
pub(super) fn encountered_overflow(&self) -> bool {
|
|
||||||
if let Some(last) = self.stack.raw.last() { last.encountered_overflow } else { false }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resets `encountered_overflow` of the current goal.
|
|
||||||
///
|
|
||||||
/// This should only be used for the check in `evaluate_goal`.
|
|
||||||
pub(super) fn reset_encountered_overflow(&mut self, encountered_overflow: bool) -> bool {
|
|
||||||
if let Some(last) = self.stack.raw.last_mut() {
|
|
||||||
let prev = last.encountered_overflow;
|
|
||||||
last.encountered_overflow = encountered_overflow;
|
|
||||||
prev
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the remaining depth allowed for nested goals.
|
/// Returns the remaining depth allowed for nested goals.
|
||||||
///
|
///
|
||||||
/// This is generally simply one less than the current depth.
|
/// This is generally simply one less than the current depth.
|
||||||
|
|
|
@ -469,7 +469,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
||||||
let a_ty = goal.predicate.self_ty();
|
let a_ty = goal.predicate.self_ty();
|
||||||
// We need to normalize the b_ty since it's destructured as a `dyn Trait`.
|
// We need to normalize the b_ty since it's destructured as a `dyn Trait`.
|
||||||
let Some(b_ty) =
|
let Some(b_ty) =
|
||||||
ecx.try_normalize_ty(goal.param_env, goal.predicate.trait_ref.args.type_at(1))?
|
ecx.try_normalize_ty(goal.param_env, goal.predicate.trait_ref.args.type_at(1))
|
||||||
else {
|
else {
|
||||||
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
|
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
|
||||||
};
|
};
|
||||||
|
@ -536,9 +536,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
|
||||||
let b_ty = match ecx
|
let b_ty = match ecx
|
||||||
.try_normalize_ty(goal.param_env, goal.predicate.trait_ref.args.type_at(1))
|
.try_normalize_ty(goal.param_env, goal.predicate.trait_ref.args.type_at(1))
|
||||||
{
|
{
|
||||||
Ok(Some(b_ty)) => b_ty,
|
Some(b_ty) => b_ty,
|
||||||
Ok(None) => return vec![misc_candidate(ecx, Certainty::OVERFLOW)],
|
None => return vec![misc_candidate(ecx, Certainty::OVERFLOW)],
|
||||||
Err(_) => return vec![],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let goal = goal.with(ecx.tcx(), (a_ty, b_ty));
|
let goal = goal.with(ecx.tcx(), (a_ty, b_ty));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error[E0720]: cannot resolve opaque type
|
error[E0720]: cannot resolve opaque type
|
||||||
--> $DIR/recursive-coroutine.rs:5:13
|
--> $DIR/recursive-coroutine.rs:7:13
|
||||||
|
|
|
|
||||||
LL | fn foo() -> impl Coroutine<Yield = (), Return = ()> {
|
LL | fn foo() -> impl Coroutine<Yield = (), Return = ()> {
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ recursive opaque type
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ recursive opaque type
|
12
tests/ui/impl-trait/recursive-coroutine.next.stderr
Normal file
12
tests/ui/impl-trait/recursive-coroutine.next.stderr
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
error[E0720]: cannot resolve opaque type
|
||||||
|
--> $DIR/recursive-coroutine.rs:7:13
|
||||||
|
|
|
||||||
|
LL | fn foo() -> impl Coroutine<Yield = (), Return = ()> {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ recursive opaque type
|
||||||
|
...
|
||||||
|
LL | let mut gen = Box::pin(foo());
|
||||||
|
| ------- coroutine captures itself here
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0720`.
|
|
@ -1,3 +1,5 @@
|
||||||
|
// revisions: current next
|
||||||
|
//[next] compile-flags: -Ztrait-solver=next
|
||||||
#![feature(coroutines, coroutine_trait)]
|
#![feature(coroutines, coroutine_trait)]
|
||||||
|
|
||||||
use std::ops::{Coroutine, CoroutineState};
|
use std::ops::{Coroutine, CoroutineState};
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
error: opaque type's hidden type cannot be another opaque type from the same scope
|
error: opaque type's hidden type cannot be another opaque type from the same scope
|
||||||
--> $DIR/two_tait_defining_each_other.rs:12:5
|
--> $DIR/two_tait_defining_each_other.rs:16:5
|
||||||
|
|
|
|
||||||
LL | x // A's hidden type is `Bar`, because all the hidden types of `B` are compared with each other
|
LL | x // A's hidden type is `Bar`, because all the hidden types of `B` are compared with each other
|
||||||
| ^ one of the two opaque types used here has to be outside its defining scope
|
| ^ one of the two opaque types used here has to be outside its defining scope
|
||||||
|
|
|
|
||||||
note: opaque type whose hidden type is being assigned
|
note: opaque type whose hidden type is being assigned
|
||||||
--> $DIR/two_tait_defining_each_other.rs:4:10
|
--> $DIR/two_tait_defining_each_other.rs:8:10
|
||||||
|
|
|
|
||||||
LL | type B = impl Foo;
|
LL | type B = impl Foo;
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
note: opaque type being used as hidden type
|
note: opaque type being used as hidden type
|
||||||
--> $DIR/two_tait_defining_each_other.rs:3:10
|
--> $DIR/two_tait_defining_each_other.rs:7:10
|
||||||
|
|
|
|
||||||
LL | type A = impl Foo;
|
LL | type A = impl Foo;
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
|
@ -1,3 +1,7 @@
|
||||||
|
// revisions: current next
|
||||||
|
//[next] compile-flags: -Ztrait-solver=next
|
||||||
|
//[next] check-pass
|
||||||
|
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
type A = impl Foo;
|
type A = impl Foo;
|
||||||
|
@ -10,7 +14,7 @@ fn muh(x: A) -> B {
|
||||||
return Bar; // B's hidden type is Bar
|
return Bar; // B's hidden type is Bar
|
||||||
}
|
}
|
||||||
x // A's hidden type is `Bar`, because all the hidden types of `B` are compared with each other
|
x // A's hidden type is `Bar`, because all the hidden types of `B` are compared with each other
|
||||||
//~^ ERROR opaque type's hidden type cannot be another opaque type
|
//[current]~^ ERROR opaque type's hidden type cannot be another opaque type
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Bar;
|
struct Bar;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error: unconstrained opaque type
|
error: unconstrained opaque type
|
||||||
--> $DIR/two_tait_defining_each_other2.rs:3:10
|
--> $DIR/two_tait_defining_each_other2.rs:5:10
|
||||||
|
|
|
|
||||||
LL | type A = impl Foo;
|
LL | type A = impl Foo;
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
|
@ -7,18 +7,18 @@ LL | type A = impl Foo;
|
||||||
= note: `A` must be used in combination with a concrete type within the same module
|
= note: `A` must be used in combination with a concrete type within the same module
|
||||||
|
|
||||||
error: opaque type's hidden type cannot be another opaque type from the same scope
|
error: opaque type's hidden type cannot be another opaque type from the same scope
|
||||||
--> $DIR/two_tait_defining_each_other2.rs:9:5
|
--> $DIR/two_tait_defining_each_other2.rs:11:5
|
||||||
|
|
|
|
||||||
LL | x // B's hidden type is A (opaquely)
|
LL | x // B's hidden type is A (opaquely)
|
||||||
| ^ one of the two opaque types used here has to be outside its defining scope
|
| ^ one of the two opaque types used here has to be outside its defining scope
|
||||||
|
|
|
|
||||||
note: opaque type whose hidden type is being assigned
|
note: opaque type whose hidden type is being assigned
|
||||||
--> $DIR/two_tait_defining_each_other2.rs:4:10
|
--> $DIR/two_tait_defining_each_other2.rs:6:10
|
||||||
|
|
|
|
||||||
LL | type B = impl Foo;
|
LL | type B = impl Foo;
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
note: opaque type being used as hidden type
|
note: opaque type being used as hidden type
|
||||||
--> $DIR/two_tait_defining_each_other2.rs:3:10
|
--> $DIR/two_tait_defining_each_other2.rs:5:10
|
||||||
|
|
|
|
||||||
LL | type A = impl Foo;
|
LL | type A = impl Foo;
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
|
@ -0,0 +1,9 @@
|
||||||
|
error[E0284]: type annotations needed: cannot satisfy `A <: B`
|
||||||
|
--> $DIR/two_tait_defining_each_other2.rs:11:5
|
||||||
|
|
|
||||||
|
LL | x // B's hidden type is A (opaquely)
|
||||||
|
| ^ cannot satisfy `A <: B`
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0284`.
|
|
@ -1,13 +1,16 @@
|
||||||
|
// revisions: current next
|
||||||
|
//[next] compile-flags: -Ztrait-solver=next
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
type A = impl Foo; //~ ERROR unconstrained opaque type
|
type A = impl Foo; //[current]~ ERROR unconstrained opaque type
|
||||||
type B = impl Foo;
|
type B = impl Foo;
|
||||||
|
|
||||||
trait Foo {}
|
trait Foo {}
|
||||||
|
|
||||||
fn muh(x: A) -> B {
|
fn muh(x: A) -> B {
|
||||||
x // B's hidden type is A (opaquely)
|
x // B's hidden type is A (opaquely)
|
||||||
//~^ ERROR opaque type's hidden type cannot be another opaque type
|
//[current]~^ ERROR opaque type's hidden type cannot be another opaque type
|
||||||
|
//[next]~^^ ERROR type annotations needed: cannot satisfy `A <: B`
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Bar;
|
struct Bar;
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
error: opaque type's hidden type cannot be another opaque type from the same scope
|
error: opaque type's hidden type cannot be another opaque type from the same scope
|
||||||
--> $DIR/two_tait_defining_each_other3.rs:10:16
|
--> $DIR/two_tait_defining_each_other3.rs:13:16
|
||||||
|
|
|
|
||||||
LL | return x; // B's hidden type is A (opaquely)
|
LL | return x; // B's hidden type is A (opaquely)
|
||||||
| ^ one of the two opaque types used here has to be outside its defining scope
|
| ^ one of the two opaque types used here has to be outside its defining scope
|
||||||
|
|
|
|
||||||
note: opaque type whose hidden type is being assigned
|
note: opaque type whose hidden type is being assigned
|
||||||
--> $DIR/two_tait_defining_each_other3.rs:4:10
|
--> $DIR/two_tait_defining_each_other3.rs:7:10
|
||||||
|
|
|
|
||||||
LL | type B = impl Foo;
|
LL | type B = impl Foo;
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
note: opaque type being used as hidden type
|
note: opaque type being used as hidden type
|
||||||
--> $DIR/two_tait_defining_each_other3.rs:3:10
|
--> $DIR/two_tait_defining_each_other3.rs:6:10
|
||||||
|
|
|
|
||||||
LL | type A = impl Foo;
|
LL | type A = impl Foo;
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
|
@ -1,3 +1,6 @@
|
||||||
|
// revisions: current next
|
||||||
|
//[next] compile-flags: -Ztrait-solver=next
|
||||||
|
//[next] check-pass
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
type A = impl Foo;
|
type A = impl Foo;
|
||||||
|
@ -8,7 +11,7 @@ trait Foo {}
|
||||||
fn muh(x: A) -> B {
|
fn muh(x: A) -> B {
|
||||||
if false {
|
if false {
|
||||||
return x; // B's hidden type is A (opaquely)
|
return x; // B's hidden type is A (opaquely)
|
||||||
//~^ ERROR opaque type's hidden type cannot be another opaque type
|
//[current]~^ ERROR opaque type's hidden type cannot be another opaque type
|
||||||
}
|
}
|
||||||
Bar // A's hidden type is `Bar`, because all the return types are compared with each other
|
Bar // A's hidden type is `Bar`, because all the return types are compared with each other
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
// check-pass
|
||||||
|
// compile-flags: -Ztrait-solver=next
|
||||||
|
// regression test for trait-system-refactor-initiative#68
|
||||||
|
trait Identity {
|
||||||
|
type Assoc: ?Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Identity for T {
|
||||||
|
type Assoc = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Id<T> = <T as Identity>::Assoc;
|
||||||
|
|
||||||
|
type Five<T> = Id<Id<Id<Id<Id<T>>>>>;
|
||||||
|
type Ty<T> = Five<Five<Five<Five<Five<T>>>>>;
|
||||||
|
|
||||||
|
trait Trait<T> {}
|
||||||
|
|
||||||
|
impl<T> Trait<T> for Ty<T> {}
|
||||||
|
impl Trait<u32> for Ty<i32> {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,8 @@
|
||||||
|
// check-pass
|
||||||
|
// compile-flags: -Ztrait-solver=next
|
||||||
|
|
||||||
|
fn test<T: Iterator>(x: T::Item) -> impl Sized {
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -1,7 +1,3 @@
|
||||||
WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <<LocalTy as Overflow>::Assoc as std::marker::Sized>
|
|
||||||
WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <<LocalTy as Overflow>::Assoc as std::marker::Sized>
|
|
||||||
WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <<LocalTy as Overflow>::Assoc as std::marker::Sized>
|
|
||||||
WARN rustc_trait_selection::traits::coherence expected an unknowable trait ref: <<LocalTy as Overflow>::Assoc as std::marker::Sized>
|
|
||||||
error[E0119]: conflicting implementations of trait `Trait` for type `<LocalTy as Overflow>::Assoc`
|
error[E0119]: conflicting implementations of trait `Trait` for type `<LocalTy as Overflow>::Assoc`
|
||||||
--> $DIR/trait_ref_is_knowable-norm-overflow.rs:17:1
|
--> $DIR/trait_ref_is_knowable-norm-overflow.rs:17:1
|
||||||
|
|
|
|
||||||
|
@ -11,7 +7,8 @@ LL | struct LocalTy;
|
||||||
LL | impl Trait for <LocalTy as Overflow>::Assoc {}
|
LL | impl Trait for <LocalTy as Overflow>::Assoc {}
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `<LocalTy as Overflow>::Assoc`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `<LocalTy as Overflow>::Assoc`
|
||||||
|
|
|
|
||||||
= note: upstream crates may add a new impl of trait `std::marker::Copy` for type `<LocalTy as Overflow>::Assoc` in future versions
|
= note: downstream crates may implement trait `std::marker::Sized` for type `<LocalTy as Overflow>::Assoc`
|
||||||
|
= note: downstream crates may implement trait `std::marker::Copy` for type `<LocalTy as Overflow>::Assoc`
|
||||||
|
|
||||||
error: aborting due to previous error
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// Tests that we properly detect defining usages when using
|
// Tests that we properly detect defining usages when using
|
||||||
// const generics in an associated opaque type
|
// const generics in an associated opaque type
|
||||||
// check-pass
|
|
||||||
|
|
||||||
|
// check-pass
|
||||||
|
// revisions: current next
|
||||||
|
//[next] compile-flags: -Ztrait-solver=next
|
||||||
#![feature(impl_trait_in_assoc_type)]
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
|
|
||||||
trait UnwrapItemsExt<'a, const C: usize> {
|
trait UnwrapItemsExt<'a, const C: usize> {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// check-pass
|
// check-pass
|
||||||
|
// revisions: current next
|
||||||
|
//[next] compile-flags: -Ztrait-solver=next
|
||||||
|
|
||||||
#![feature(impl_trait_in_assoc_type)]
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error[E0309]: the parameter type `T` may not live long enough
|
error[E0309]: the parameter type `T` may not live long enough
|
||||||
--> $DIR/wf-in-associated-type.rs:36:23
|
--> $DIR/wf-in-associated-type.rs:38:23
|
||||||
|
|
|
|
||||||
LL | impl<'a, T> Trait<'a, T> for () {
|
LL | impl<'a, T> Trait<'a, T> for () {
|
||||||
| -- the parameter type `T` must be valid for the lifetime `'a` as defined here...
|
| -- the parameter type `T` must be valid for the lifetime `'a` as defined here...
|
||||||
|
@ -12,7 +12,7 @@ LL | impl<'a, T: 'a> Trait<'a, T> for () {
|
||||||
| ++++
|
| ++++
|
||||||
|
|
||||||
error[E0309]: the parameter type `T` may not live long enough
|
error[E0309]: the parameter type `T` may not live long enough
|
||||||
--> $DIR/wf-in-associated-type.rs:36:23
|
--> $DIR/wf-in-associated-type.rs:38:23
|
||||||
|
|
|
|
||||||
LL | impl<'a, T> Trait<'a, T> for () {
|
LL | impl<'a, T> Trait<'a, T> for () {
|
||||||
| -- the parameter type `T` must be valid for the lifetime `'a` as defined here...
|
| -- the parameter type `T` must be valid for the lifetime `'a` as defined here...
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
// WF check for impl Trait in associated type position.
|
// WF check for impl Trait in associated type position.
|
||||||
//
|
//
|
||||||
// revisions: pass fail
|
// revisions: pass pass_next fail
|
||||||
// [pass] check-pass
|
// [pass] check-pass
|
||||||
|
// [pass_next] compile-flags: -Ztrait-solver=next
|
||||||
|
// [pass_next] check-pass
|
||||||
// [fail] check-fail
|
// [fail] check-fail
|
||||||
|
|
||||||
#![feature(impl_trait_in_assoc_type)]
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
|
|
||||||
// The hidden type here (`&'a T`) requires proving `T: 'a`.
|
// The hidden type here (`&'a T`) requires proving `T: 'a`.
|
||||||
// We know it holds because of implied bounds from the impl header.
|
// We know it holds because of implied bounds from the impl header.
|
||||||
#[cfg(pass)]
|
#[cfg(any(pass, pass_next))]
|
||||||
mod pass {
|
mod pass {
|
||||||
trait Trait<Req> {
|
trait Trait<Req> {
|
||||||
type Opaque1;
|
type Opaque1;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue