Rollup merge of #136863 - lcnr:treat-as-rigid, r=compiler-errors

rework rigid alias handling

Necessary for https://github.com/rust-lang/rust/pull/136824 if we treat coinductive cycles as errors as we otherwise don't emit an error for

```rust
trait Overflow {
    type Assoc;
}
impl<T> Overflow for T {
    type Assoc = <T as Overflow>::Assoc;
}
```

The important part is that we only add a `RigidAlias` candidate in cases where the alias is actually supposed to be rigid:
- its trait bound has been proven via a `ParamEnv` or `ItemBound` candidate
- it's one of the special builtin traits which have a blanket impl with a `default` assoc type

This means that we now more explicitly control which aliases should rigid to avoid accidentally accepting cyclic aliases. This requires changes to diagnostics as we no longer enter an explicit `RigidAlias` candidate for `NormalizesTo` goals whose trait bound doesn't hold.

To fix this I've modified the `BestObligation` visitor always ignore `RigidAlias` candidates and to instead manually check these requirements if there are no applicable candidates. I also removed the hack for handling `structurally_normalize_ty` failures. This fixes #134905 as we no longer continue to use the `EvalCtxt` even though a nested goal failed.

r? ``@compiler-errors``
This commit is contained in:
Jubilee 2025-02-13 17:46:07 -08:00 committed by GitHub
commit e2ee9f7318
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 366 additions and 251 deletions

View file

@ -6,7 +6,6 @@ use derive_where::derive_where;
use rustc_type_ir::fold::TypeFoldable;
use rustc_type_ir::inherent::*;
use rustc_type_ir::lang_items::TraitSolverLangItem;
use rustc_type_ir::solve::inspect;
use rustc_type_ir::visit::TypeVisitableExt as _;
use rustc_type_ir::{self as ty, Interner, TypingMode, Upcast as _, elaborate};
use tracing::{debug, instrument};
@ -297,25 +296,6 @@ where
let Ok(normalized_self_ty) =
self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty())
else {
// FIXME: We register a fake candidate when normalization fails so that
// we can point at the reason for *why*. I'm tempted to say that this
// is the wrong way to do this, though.
let result =
self.probe(|&result| inspect::ProbeKind::RigidAlias { result }).enter(|this| {
let normalized_ty = this.next_ty_infer();
let alias_relate_goal = Goal::new(
this.cx(),
goal.param_env,
ty::PredicateKind::AliasRelate(
goal.predicate.self_ty().into(),
normalized_ty.into(),
ty::AliasRelationDirection::Equate,
),
);
this.add_goal(GoalSource::AliasWellFormed, alias_relate_goal);
this.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
});
assert_eq!(result, Err(NoSolution));
return vec![];
};
@ -797,11 +777,12 @@ where
/// treat the alias as rigid.
///
/// See trait-system-refactor-initiative#124 for more details.
#[instrument(level = "debug", skip(self), ret)]
#[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
pub(super) fn merge_candidates(
&mut self,
proven_via: Option<TraitGoalProvenVia>,
candidates: Vec<Candidate<I>>,
inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I> {
let Some(proven_via) = proven_via else {
// We don't care about overflow. If proving the trait goal overflowed, then
@ -818,13 +799,27 @@ where
// FIXME(const_trait_impl): should this behavior also be used by
// constness checking. Doing so is *at least theoretically* breaking,
// see github.com/rust-lang/rust/issues/133044#issuecomment-2500709754
TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => candidates
.iter()
.filter(|c| {
matches!(c.source, CandidateSource::AliasBound | CandidateSource::ParamEnv(_))
})
.map(|c| c.result)
.collect(),
TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => {
let mut candidates_from_env: Vec<_> = candidates
.iter()
.filter(|c| {
matches!(
c.source,
CandidateSource::AliasBound | CandidateSource::ParamEnv(_)
)
})
.map(|c| c.result)
.collect();
// If the trait goal has been proven by using the environment, we want to treat
// aliases as rigid if there are no applicable projection bounds in the environment.
if candidates_from_env.is_empty() {
if let Ok(response) = inject_normalize_to_rigid_candidate(self) {
candidates_from_env.push(response);
}
}
candidates_from_env
}
TraitGoalProvenVia::Misc => candidates.iter().map(|c| c.result).collect(),
};

View file

@ -405,6 +405,6 @@ where
goal.with(ecx.cx(), goal.predicate.trait_ref);
ecx.compute_trait_goal(trait_goal)
})?;
self.merge_candidates(proven_via, candidates)
self.merge_candidates(proven_via, candidates, |_ecx| Err(NoSolution))
}
}

View file

@ -30,75 +30,26 @@ where
) -> QueryResult<I> {
self.set_is_normalizes_to_goal();
debug_assert!(self.term_is_fully_unconstrained(goal));
let normalize_result = self
.probe(|&result| ProbeKind::TryNormalizeNonRigid { result })
.enter(|this| this.normalize_at_least_one_step(goal));
match normalize_result {
Ok(res) => Ok(res),
Err(NoSolution) => {
self.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| {
let Goal { param_env, predicate: NormalizesTo { alias, term } } = goal;
this.add_rigid_constraints(param_env, alias)?;
this.relate_rigid_alias_non_alias(param_env, alias, ty::Invariant, term)?;
this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
}
}
/// Register any obligations that are used to validate that an alias should be
/// treated as rigid.
///
/// An alias may be considered rigid if it fails normalization, but we also don't
/// want to consider aliases that are not well-formed to be rigid simply because
/// they fail normalization.
///
/// For example, some `<T as Trait>::Assoc` where `T: Trait` does not hold, or an
/// opaque type whose hidden type doesn't actually satisfy the opaque item bounds.
fn add_rigid_constraints(
&mut self,
param_env: I::ParamEnv,
rigid_alias: ty::AliasTerm<I>,
) -> Result<(), NoSolution> {
let cx = self.cx();
match rigid_alias.kind(cx) {
// Projections are rigid only if their trait ref holds,
// and the GAT where-clauses hold.
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
let trait_ref = rigid_alias.trait_ref(cx);
self.add_goal(GoalSource::AliasWellFormed, Goal::new(cx, param_env, trait_ref));
Ok(())
}
ty::AliasTermKind::OpaqueTy => {
if self.opaque_type_is_rigid(rigid_alias.def_id) {
Ok(())
} else {
Err(NoSolution)
}
}
// FIXME(generic_const_exprs): we would need to support generic consts here
ty::AliasTermKind::UnevaluatedConst => Err(NoSolution),
// Inherent and weak types are never rigid. This type must not be well-formed.
ty::AliasTermKind::WeakTy | ty::AliasTermKind::InherentTy => Err(NoSolution),
}
}
/// Normalize the given alias by at least one step. If the alias is rigid, this
/// returns `NoSolution`.
#[instrument(level = "trace", skip(self), ret)]
fn normalize_at_least_one_step(&mut self, goal: Goal<I, NormalizesTo<I>>) -> QueryResult<I> {
let cx = self.cx();
match goal.predicate.alias.kind(cx) {
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
let candidates = self.assemble_and_evaluate_candidates(goal);
let trait_ref = goal.predicate.alias.trait_ref(cx);
let (_, proven_via) =
self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
let trait_goal: Goal<I, ty::TraitPredicate<I>> =
goal.with(cx, goal.predicate.alias.trait_ref(cx));
let trait_goal: Goal<I, ty::TraitPredicate<I>> = goal.with(cx, trait_ref);
ecx.compute_trait_goal(trait_goal)
})?;
self.merge_candidates(proven_via, candidates)
self.merge_candidates(proven_via, candidates, |ecx| {
ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| {
this.structurally_instantiate_normalizes_to_term(
goal,
goal.predicate.alias,
);
this.add_goal(GoalSource::AliasWellFormed, goal.with(cx, trait_ref));
this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
})
}
ty::AliasTermKind::InherentTy => self.normalize_inherent_associated_type(goal),
ty::AliasTermKind::OpaqueTy => self.normalize_opaque_type(goal),
@ -120,6 +71,17 @@ where
self.eq(goal.param_env, goal.predicate.term, term)
.expect("expected goal term to be fully unconstrained");
}
/// Unlike `instantiate_normalizes_to_term` this instantiates the expected term
/// with a rigid alias. Using this is pretty much always wrong.
pub fn structurally_instantiate_normalizes_to_term(
&mut self,
goal: Goal<I, NormalizesTo<I>>,
term: ty::AliasTerm<I>,
) {
self.relate_rigid_alias_non_alias(goal.param_env, term, ty::Invariant, goal.predicate.term)
.expect("expected goal term to be fully unconstrained");
}
}
impl<D, I> assembly::GoalKind<D> for NormalizesTo<I>
@ -576,80 +538,92 @@ where
let cx = ecx.cx();
let metadata_def_id = cx.require_lang_item(TraitSolverLangItem::Metadata);
assert_eq!(metadata_def_id, goal.predicate.def_id());
let metadata_ty = match goal.predicate.self_ty().kind() {
ty::Bool
| ty::Char
| ty::Int(..)
| ty::Uint(..)
| ty::Float(..)
| ty::Array(..)
| ty::Pat(..)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Infer(ty::IntVar(..) | ty::FloatVar(..))
| ty::Coroutine(..)
| ty::CoroutineWitness(..)
| ty::Never
| ty::Foreign(..)
| ty::Dynamic(_, _, ty::DynStar) => Ty::new_unit(cx),
ty::Error(e) => Ty::new_error(cx, e),
ty::Str | ty::Slice(_) => Ty::new_usize(cx),
ty::Dynamic(_, _, ty::Dyn) => {
let dyn_metadata = cx.require_lang_item(TraitSolverLangItem::DynMetadata);
cx.type_of(dyn_metadata)
.instantiate(cx, &[I::GenericArg::from(goal.predicate.self_ty())])
}
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
// This is the "fallback impl" for type parameters, unnormalizable projections
// and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
// FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
// exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
let alias_bound_result =
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
let sized_predicate = ty::TraitRef::new(
cx,
cx.require_lang_item(TraitSolverLangItem::Sized),
[I::GenericArg::from(goal.predicate.self_ty())],
);
ecx.add_goal(GoalSource::Misc, goal.with(cx, sized_predicate));
ecx.instantiate_normalizes_to_term(goal, Ty::new_unit(cx).into());
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
});
// In case the dummy alias-bound candidate does not apply, we instead treat this projection
// as rigid.
return alias_bound_result.or_else(|NoSolution| {
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|this| {
this.structurally_instantiate_normalizes_to_term(
goal,
goal.predicate.alias,
);
this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
});
}
ty::Adt(def, args) if def.is_struct() => match def.struct_tail_ty(cx) {
None => Ty::new_unit(cx),
Some(tail_ty) => {
Ty::new_projection(cx, metadata_def_id, [tail_ty.instantiate(cx, args)])
}
},
ty::Adt(_, _) => Ty::new_unit(cx),
ty::Tuple(elements) => match elements.last() {
None => Ty::new_unit(cx),
Some(tail_ty) => Ty::new_projection(cx, metadata_def_id, [tail_ty]),
},
ty::UnsafeBinder(_) => {
// FIXME(unsafe_binder): Figure out how to handle pointee for unsafe binders.
todo!()
}
ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_))
| ty::Bound(..) => panic!(
"unexpected self ty `{:?}` when normalizing `<T as Pointee>::Metadata`",
goal.predicate.self_ty()
),
};
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
let metadata_ty = match goal.predicate.self_ty().kind() {
ty::Bool
| ty::Char
| ty::Int(..)
| ty::Uint(..)
| ty::Float(..)
| ty::Array(..)
| ty::Pat(..)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Infer(ty::IntVar(..) | ty::FloatVar(..))
| ty::Coroutine(..)
| ty::CoroutineWitness(..)
| ty::Never
| ty::Foreign(..)
| ty::Dynamic(_, _, ty::DynStar) => Ty::new_unit(cx),
ty::Error(e) => Ty::new_error(cx, e),
ty::Str | ty::Slice(_) => Ty::new_usize(cx),
ty::Dynamic(_, _, ty::Dyn) => {
let dyn_metadata = cx.require_lang_item(TraitSolverLangItem::DynMetadata);
cx.type_of(dyn_metadata)
.instantiate(cx, &[I::GenericArg::from(goal.predicate.self_ty())])
}
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
// This is the "fallback impl" for type parameters, unnormalizable projections
// and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
// FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
// exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
let sized_predicate = ty::TraitRef::new(
cx,
cx.require_lang_item(TraitSolverLangItem::Sized),
[I::GenericArg::from(goal.predicate.self_ty())],
);
// FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`?
ecx.add_goal(GoalSource::Misc, goal.with(cx, sized_predicate));
Ty::new_unit(cx)
}
ty::Adt(def, args) if def.is_struct() => match def.struct_tail_ty(cx) {
None => Ty::new_unit(cx),
Some(tail_ty) => {
Ty::new_projection(cx, metadata_def_id, [tail_ty.instantiate(cx, args)])
}
},
ty::Adt(_, _) => Ty::new_unit(cx),
ty::Tuple(elements) => match elements.last() {
None => Ty::new_unit(cx),
Some(tail_ty) => Ty::new_projection(cx, metadata_def_id, [tail_ty]),
},
ty::UnsafeBinder(_) => {
// FIXME(unsafe_binder): Figure out how to handle pointee for unsafe binders.
todo!()
}
ty::Infer(
ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_),
)
| ty::Bound(..) => panic!(
"unexpected self ty `{:?}` when normalizing `<T as Pointee>::Metadata`",
goal.predicate.self_ty()
),
};
ecx.instantiate_normalizes_to_term(goal, metadata_ty.into());
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
@ -850,12 +824,14 @@ where
todo!("discr subgoal...")
}
// We do not call `Ty::discriminant_ty` on alias, param, or placeholder
// types, which return `<self_ty as DiscriminantKind>::Discriminant`
// (or ICE in the case of placeholders). Projecting a type to itself
// is never really productive.
// Given an alias, parameter, or placeholder we add an impl candidate normalizing to a rigid
// alias. In case there's a where-bound further constraining this alias it is preferred over
// this impl candidate anyways. It's still a bit scuffed.
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
return Err(NoSolution);
return ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
ecx.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
});
}
ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_))
@ -902,12 +878,14 @@ where
todo!()
}
// We do not call `Ty::async_destructor_ty` on alias, param, or placeholder
// types, which return `<self_ty as AsyncDestruct>::AsyncDestructor`
// (or ICE in the case of placeholders). Projecting a type to itself
// is never really productive.
// Given an alias, parameter, or placeholder we add an impl candidate normalizing to a rigid
// alias. In case there's a where-bound further constraining this alias it is preferred over
// this impl candidate anyways. It's still a bit scuffed.
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
return Err(NoSolution);
return ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
ecx.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
});
}
ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_))

View file

@ -35,14 +35,15 @@ where
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
}
TypingMode::Analysis { defining_opaque_types } => {
let Some(def_id) = opaque_ty.def_id.as_local() else {
return Err(NoSolution);
let Some(def_id) = opaque_ty
.def_id
.as_local()
.filter(|&def_id| defining_opaque_types.contains(&def_id))
else {
self.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
return self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
};
if !defining_opaque_types.contains(&def_id) {
return Err(NoSolution);
}
// FIXME: This may have issues when the args contain aliases...
match uses_unique_placeholders_ignoring_regions(self.cx(), opaque_ty.args) {
Err(NotUniqueParam::NotParam(param)) if param.is_non_region_infer() => {
@ -97,15 +98,16 @@ where
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
TypingMode::PostBorrowckAnalysis { defined_opaque_types } => {
let Some(def_id) = opaque_ty.def_id.as_local() else {
return Err(NoSolution);
let Some(def_id) = opaque_ty
.def_id
.as_local()
.filter(|&def_id| defined_opaque_types.contains(&def_id))
else {
self.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
return self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
};
if !defined_opaque_types.contains(&def_id) {
return Err(NoSolution);
}
let actual = cx.type_of(opaque_ty.def_id).instantiate(cx, opaque_ty.args);
let actual = cx.type_of(def_id.into()).instantiate(cx, opaque_ty.args);
// FIXME: Actually use a proper binder here instead of relying on `ReErased`.
//
// This is also probably unsound or sth :shrug:

View file

@ -585,6 +585,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(ty)) => {
let ty = self.resolve_vars_if_possible(ty);
if self.next_trait_solver() {
if let Err(guar) = ty.error_reported() {
return guar;
}
// FIXME: we'll need a better message which takes into account
// which bounds actually failed to hold.
self.dcx().struct_span_err(

View file

@ -172,8 +172,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
{
1
}
ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(_)) => 3,
ty::PredicateKind::Coerce(_) => 2,
ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(_)) => 3,
_ => 0,
});

View file

@ -7,7 +7,7 @@ use rustc_infer::traits::{
PredicateObligation, SelectionError,
};
use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::{self, TyCtxt};
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_next_trait_solver::solve::{GenerateProofTree, SolverDelegateEvalExt as _};
use rustc_type_ir::solve::{Goal, NoSolution};
@ -139,6 +139,7 @@ pub(super) fn fulfillment_error_for_overflow<'tcx>(
}
}
#[instrument(level = "debug", skip(infcx), ret)]
fn find_best_leaf_obligation<'tcx>(
infcx: &InferCtxt<'tcx>,
obligation: &PredicateObligation<'tcx>,
@ -197,6 +198,9 @@ impl<'tcx> BestObligation<'tcx> {
candidates.retain(|candidate| candidate.result().is_ok());
}
false => {
// We always handle rigid alias candidates separately as we may not add them for
// aliases whose trait bound doesn't hold.
candidates.retain(|c| !matches!(c.kind(), inspect::ProbeKind::RigidAlias { .. }));
// If we have >1 candidate, one may still be due to "boring" reasons, like
// an alias-relate that failed to hold when deeply evaluated. We really
// don't care about reasons like this.
@ -211,23 +215,12 @@ impl<'tcx> BestObligation<'tcx> {
| GoalSource::AliasBoundConstCondition
| GoalSource::InstantiateHigherRanked
| GoalSource::AliasWellFormed
) && match (self.consider_ambiguities, nested_goal.result()) {
(true, Ok(Certainty::Maybe(MaybeCause::Ambiguity)))
| (false, Err(_)) => true,
_ => false,
}
) && nested_goal.result().is_err()
},
)
})
});
}
// Prefer a non-rigid candidate if there is one.
if candidates.len() > 1 {
candidates.retain(|candidate| {
!matches!(candidate.kind(), inspect::ProbeKind::RigidAlias { .. })
});
}
}
}
@ -266,6 +259,90 @@ impl<'tcx> BestObligation<'tcx> {
ControlFlow::Break(self.obligation.clone())
}
/// If a normalization of an associated item or a trait goal fails without trying any
/// candidates it's likely that normalizing its self type failed. We manually detect
/// such cases here.
fn detect_error_in_self_ty_normalization(
&mut self,
goal: &inspect::InspectGoal<'_, 'tcx>,
self_ty: Ty<'tcx>,
) -> ControlFlow<PredicateObligation<'tcx>> {
assert!(!self.consider_ambiguities);
let tcx = goal.infcx().tcx;
if let ty::Alias(..) = self_ty.kind() {
let infer_term = goal.infcx().next_ty_var(self.obligation.cause.span);
let pred = ty::PredicateKind::AliasRelate(
self_ty.into(),
infer_term.into(),
ty::AliasRelationDirection::Equate,
);
let obligation =
Obligation::new(tcx, self.obligation.cause.clone(), goal.goal().param_env, pred);
self.with_derived_obligation(obligation, |this| {
goal.infcx().visit_proof_tree_at_depth(
goal.goal().with(tcx, pred),
goal.depth() + 1,
this,
)
})
} else {
ControlFlow::Continue(())
}
}
/// It is likely that `NormalizesTo` failed without any applicable candidates
/// because the alias is not well-formed.
///
/// As we only enter `RigidAlias` candidates if the trait bound of the associated type
/// holds, we discard these candidates in `non_trivial_candidates` and always manually
/// check this here.
fn detect_non_well_formed_assoc_item(
&mut self,
goal: &inspect::InspectGoal<'_, 'tcx>,
alias: ty::AliasTerm<'tcx>,
) -> ControlFlow<PredicateObligation<'tcx>> {
let tcx = goal.infcx().tcx;
let obligation = Obligation::new(
tcx,
self.obligation.cause.clone(),
goal.goal().param_env,
alias.trait_ref(tcx),
);
self.with_derived_obligation(obligation, |this| {
goal.infcx().visit_proof_tree_at_depth(
goal.goal().with(tcx, alias.trait_ref(tcx)),
goal.depth() + 1,
this,
)
})
}
/// If we have no candidates, then it's likely that there is a
/// non-well-formed alias in the goal.
fn detect_error_from_empty_candidates(
&mut self,
goal: &inspect::InspectGoal<'_, 'tcx>,
) -> ControlFlow<PredicateObligation<'tcx>> {
let tcx = goal.infcx().tcx;
let pred_kind = goal.goal().predicate.kind();
match pred_kind.no_bound_vars() {
Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred))) => {
self.detect_error_in_self_ty_normalization(goal, pred.self_ty())?;
}
Some(ty::PredicateKind::NormalizesTo(pred))
if let ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst =
pred.alias.kind(tcx) =>
{
self.detect_error_in_self_ty_normalization(goal, pred.alias.self_ty())?;
self.detect_non_well_formed_assoc_item(goal, pred.alias)?;
}
Some(_) | None => {}
}
ControlFlow::Break(self.obligation.clone())
}
}
impl<'tcx> ProofTreeVisitor<'tcx> for BestObligation<'tcx> {
@ -277,11 +354,19 @@ impl<'tcx> ProofTreeVisitor<'tcx> for BestObligation<'tcx> {
#[instrument(level = "trace", skip(self, goal), fields(goal = ?goal.goal()))]
fn visit_goal(&mut self, goal: &inspect::InspectGoal<'_, 'tcx>) -> Self::Result {
let candidates = self.non_trivial_candidates(goal);
trace!(candidates = ?candidates.iter().map(|c| c.kind()).collect::<Vec<_>>());
let tcx = goal.infcx().tcx;
// Skip goals that aren't the *reason* for our goal's failure.
match (self.consider_ambiguities, goal.result()) {
(true, Ok(Certainty::Maybe(MaybeCause::Ambiguity))) | (false, Err(_)) => {}
_ => return ControlFlow::Continue(()),
}
let pred_kind = goal.goal().predicate.kind();
let [candidate] = candidates.as_slice() else {
return ControlFlow::Break(self.obligation.clone());
let candidates = self.non_trivial_candidates(goal);
let candidate = match candidates.as_slice() {
[candidate] => candidate,
[] => return self.detect_error_from_empty_candidates(goal),
_ => return ControlFlow::Break(self.obligation.clone()),
};
// Don't walk into impls that have `do_not_recommend`.
@ -291,13 +376,12 @@ impl<'tcx> ProofTreeVisitor<'tcx> for BestObligation<'tcx> {
} = candidate.kind()
&& goal.infcx().tcx.do_not_recommend_impl(impl_def_id)
{
trace!("#[do_not_recommend] -> exit");
return ControlFlow::Break(self.obligation.clone());
}
let tcx = goal.infcx().tcx;
// FIXME: Also, what about considering >1 layer up the stack? May be necessary
// for normalizes-to.
let pred_kind = goal.goal().predicate.kind();
let child_mode = match pred_kind.skip_binder() {
ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => {
ChildMode::Trait(pred_kind.rebind(pred))
@ -390,12 +474,6 @@ impl<'tcx> ProofTreeVisitor<'tcx> for BestObligation<'tcx> {
}
}
// Skip nested goals that aren't the *reason* for our goal's failure.
match (self.consider_ambiguities, nested_goal.result()) {
(true, Ok(Certainty::Maybe(MaybeCause::Ambiguity))) | (false, Err(_)) => {}
_ => continue,
}
self.with_derived_obligation(obligation, |this| nested_goal.visit_with(this))?;
}

View file

@ -300,7 +300,6 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
inspect::ProbeKind::NormalizedSelfTyAssembly
| inspect::ProbeKind::UnsizeAssembly
| inspect::ProbeKind::Root { .. }
| inspect::ProbeKind::TryNormalizeNonRigid { .. }
| inspect::ProbeKind::TraitCandidate { .. }
| inspect::ProbeKind::OpaqueTypeStorageLookup { .. }
| inspect::ProbeKind::RigidAlias { .. } => {
@ -325,7 +324,6 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
// We add a candidate even for the root evaluation if there
// is only one way to prove a given goal, e.g. for `WellFormed`.
inspect::ProbeKind::Root { result }
| inspect::ProbeKind::TryNormalizeNonRigid { result }
| inspect::ProbeKind::TraitCandidate { source: _, result }
| inspect::ProbeKind::OpaqueTypeStorageLookup { result }
| inspect::ProbeKind::RigidAlias { result } => {

View file

@ -175,8 +175,7 @@ fn to_selection<'tcx>(
span_bug!(span, "didn't expect to select an unknowable candidate")
}
},
ProbeKind::TryNormalizeNonRigid { result: _ }
| ProbeKind::NormalizedSelfTyAssembly
ProbeKind::NormalizedSelfTyAssembly
| ProbeKind::UnsizeAssembly
| ProbeKind::UpcastProjectionCompatibility
| ProbeKind::OpaqueTypeStorageLookup { result: _ }

View file

@ -111,8 +111,6 @@ pub enum ProbeStep<I: Interner> {
pub enum ProbeKind<I: Interner> {
/// The root inference context while proving a goal.
Root { result: QueryResult<I> },
/// Trying to normalize an alias by at least one step in `NormalizesTo`.
TryNormalizeNonRigid { result: QueryResult<I> },
/// Probe entered when normalizing the self ty during candidate assembly
NormalizedSelfTyAssembly,
/// A candidate for proving a trait or alias-relate goal.

View file

@ -1,16 +0,0 @@
//@ known-bug: #134905
trait Iterate<'a> {
type Ty: Valid;
}
impl<'a, T> Iterate<'a> for T
where
T: Check,
{
default type Ty = ();
}
trait Check {}
impl<'a, T> Eq for T where <T as Iterate<'a>>::Ty: Valid {}
trait Valid {}

View file

@ -21,20 +21,21 @@ LL | | }
= help: add `#![feature(auto_traits)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
error[E0308]: mismatched types
--> $DIR/assoc-ty.rs:15:36
error[E0271]: type mismatch resolving `<() as Trait>::Output normalizes-to _`
--> $DIR/assoc-ty.rs:15:12
|
LL | let _: <() as Trait>::Output = ();
| --------------------- ^^ types differ
| |
| expected due to this
| ^^^^^^^^^^^^^^^^^^^^^ types differ
error[E0271]: type mismatch resolving `<() as Trait>::Output normalizes-to _`
--> $DIR/assoc-ty.rs:15:12
|
= note: expected associated type `<() as Trait>::Output`
found unit type `()`
= help: consider constraining the associated type `<() as Trait>::Output` to `()` or calling a method that returns `<() as Trait>::Output`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
LL | let _: <() as Trait>::Output = ();
| ^^^^^^^^^^^^^^^^^^^^^ types differ
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error: aborting due to 3 previous errors
error: aborting due to 4 previous errors
Some errors have detailed explanations: E0308, E0380, E0658.
For more information about an error, try `rustc --explain E0308`.
Some errors have detailed explanations: E0271, E0380, E0658.
For more information about an error, try `rustc --explain E0271`.

View file

@ -13,5 +13,7 @@ auto trait Trait {
fn main() {
let _: <() as Trait>::Output = ();
//~^ ERROR mismatched types
//[current]~^ ERROR mismatched types
//[next]~^^ ERROR type mismatch resolving `<() as Trait>::Output normalizes-to _`
//[next]~| ERROR type mismatch resolving `<() as Trait>::Output normalizes-to _`
}

View file

@ -23,7 +23,19 @@ error[E0271]: type mismatch resolving `<Y as X>::LineStreamFut<'a, Repr> == ()`
LL | fn line_stream<'a, Repr>(&'a self) -> Self::LineStreamFut<'a, Repr> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ
error: aborting due to 3 previous errors
error[E0271]: type mismatch resolving `<Y as X>::LineStreamFut<'a, Repr> normalizes-to _`
--> $DIR/ice-unexpected-param-type-whensubstituting-in-region-112823.rs:28:73
|
LL | fn line_stream<'a, Repr>(&'a self) -> Self::LineStreamFut<'a, Repr> {}
| ^^ types differ
error[E0271]: type mismatch resolving `<Y as X>::LineStreamFut<'a, Repr> normalizes-to _`
--> $DIR/ice-unexpected-param-type-whensubstituting-in-region-112823.rs:28:5
|
LL | fn line_stream<'a, Repr>(&'a self) -> Self::LineStreamFut<'a, Repr> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ
error: aborting due to 5 previous errors
Some errors have detailed explanations: E0049, E0271, E0407.
For more information about an error, try `rustc --explain E0049`.

View file

@ -26,9 +26,11 @@ impl X for Y {
//~^ ERROR type `LineStream` has 0 type parameters but its trait declaration has 1 type parameter
type LineStreamFut<'a, Repr> = impl Future<Output = Self::LineStream<'a, Repr>>;
fn line_stream<'a, Repr>(&'a self) -> Self::LineStreamFut<'a, Repr> {}
//[current]~^ ERROR `()` is not a future
//[next]~^^ ERROR type mismatch resolving `<Y as X>::LineStreamFut<'a, Repr> == ()`
//~^^^ method `line_stream` is not a member of trait `X`
//~^ method `line_stream` is not a member of trait `X`
//[current]~^^ ERROR `()` is not a future
//[next]~^^^ ERROR type mismatch resolving `<Y as X>::LineStreamFut<'a, Repr> == ()`
//[next]~| ERROR type mismatch resolving `<Y as X>::LineStreamFut<'a, Repr> normalizes-to _`
//[next]~| ERROR type mismatch resolving `<Y as X>::LineStreamFut<'a, Repr> normalizes-to _`
}
pub fn main() {}

View file

@ -0,0 +1,22 @@
// This test previously tried to use a tainted `EvalCtxt` when emitting
// an error during coherence.
#![feature(specialization)]
//~^ WARN the feature `specialization` is incomplete
trait Iterate<'a> {
type Ty: Valid;
}
impl<'a, T> Iterate<'a> for T
where
T: Check,
{
default type Ty = ();
//~^ ERROR the trait bound `(): Valid` is not satisfied
}
trait Check {}
impl<'a, T> Eq for T where <T as Iterate<'a>>::Ty: Valid {}
//~^ ERROR type parameter `T` must be used as the type parameter for some local type
trait Valid {}
fn main() {}

View file

@ -0,0 +1,40 @@
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/fuzzing-ice-134905.rs:3:12
|
LL | #![feature(specialization)]
| ^^^^^^^^^^^^^^
|
= note: see issue #31844 <https://github.com/rust-lang/rust/issues/31844> for more information
= help: consider using `min_specialization` instead, which is more stable and complete
= note: `#[warn(incomplete_features)]` on by default
error[E0277]: the trait bound `(): Valid` is not satisfied
--> $DIR/fuzzing-ice-134905.rs:12:23
|
LL | default type Ty = ();
| ^^ the trait `Valid` is not implemented for `()`
|
help: this trait has no implementations, consider adding one
--> $DIR/fuzzing-ice-134905.rs:20:1
|
LL | trait Valid {}
| ^^^^^^^^^^^
note: required by a bound in `Iterate::Ty`
--> $DIR/fuzzing-ice-134905.rs:6:14
|
LL | type Ty: Valid;
| ^^^^^ required by this bound in `Iterate::Ty`
error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
--> $DIR/fuzzing-ice-134905.rs:17:10
|
LL | impl<'a, T> Eq for T where <T as Iterate<'a>>::Ty: Valid {}
| ^ type parameter `T` must be used as the type parameter for some local type
|
= note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local
= note: only traits defined in the current crate can be implemented for a type parameter
error: aborting due to 2 previous errors; 1 warning emitted
Some errors have detailed explanations: E0210, E0277.
For more information about an error, try `rustc --explain E0210`.