diff options
| author | Jubilee <workingjubilee@gmail.com> | 2025-02-13 17:46:07 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-13 17:46:07 -0800 |
| commit | e2ee9f73189de5b64012fd9ce2ae4e1411db0ed4 (patch) | |
| tree | 2e0f00d1dfdcbd71bdf1117e4abb25bd9aa70b7e | |
| parent | a567209daab72b7ea59eac533278064396bb0534 (diff) | |
| parent | 059288ed442f34c336c759486bfe4373b61288a0 (diff) | |
| download | rust-e2ee9f73189de5b64012fd9ce2ae4e1411db0ed4.tar.gz rust-e2ee9f73189de5b64012fd9ce2ae4e1411db0ed4.zip | |
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``
17 files changed, 360 insertions, 245 deletions
diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index a3274fb4011..b0f59ed1474 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -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(), }; diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs index eb398e8724d..0b61c368d8e 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -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)) } } diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs index 1d0ec7b45ca..88002e1a88a 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs @@ -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()); - 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())]) - } + 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::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::Error(e) => Ty::new_error(cx, e), - 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::Str | ty::Slice(_) => Ty::new_usize(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::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::UnsafeBinder(_) => { - // FIXME(unsafe_binder): Figure out how to handle pointee for unsafe binders. - todo!() + 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::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() - ), - }; + 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| { 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(_)) diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/opaque_types.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/opaque_types.rs index 26a8a22d77e..60c20762a30 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/opaque_types.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/opaque_types.rs @@ -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: diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 1b43820bac0..49fa21e50c0 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -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( diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs index 658fb4009d5..e4f250ca4f5 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs @@ -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, }); diff --git a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs index 7364b4aa343..982782bc57c 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs @@ -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))?; } diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index 9f4ee54bd4c..9fbc1d64d74 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -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 } => { diff --git a/compiler/rustc_trait_selection/src/solve/select.rs b/compiler/rustc_trait_selection/src/solve/select.rs index b0b6274907d..4437fc5b029 100644 --- a/compiler/rustc_trait_selection/src/solve/select.rs +++ b/compiler/rustc_trait_selection/src/solve/select.rs @@ -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: _ } diff --git a/compiler/rustc_type_ir/src/solve/inspect.rs b/compiler/rustc_type_ir/src/solve/inspect.rs index d0e618dc6f9..18fb71dd290 100644 --- a/compiler/rustc_type_ir/src/solve/inspect.rs +++ b/compiler/rustc_type_ir/src/solve/inspect.rs @@ -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. diff --git a/tests/crashes/134905.rs b/tests/crashes/134905.rs deleted file mode 100644 index 9f0f0f4b3f2..00000000000 --- a/tests/crashes/134905.rs +++ /dev/null @@ -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 {} diff --git a/tests/ui/auto-traits/assoc-ty.next.stderr b/tests/ui/auto-traits/assoc-ty.next.stderr index b9f56d6c99c..4ce00d17475 100644 --- a/tests/ui/auto-traits/assoc-ty.next.stderr +++ b/tests/ui/auto-traits/assoc-ty.next.stderr @@ -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 + | +LL | let _: <() as Trait>::Output = (); + | ^^^^^^^^^^^^^^^^^^^^^ types differ | - = 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 + = 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`. diff --git a/tests/ui/auto-traits/assoc-ty.rs b/tests/ui/auto-traits/assoc-ty.rs index ada75147f6e..efbfead9cd0 100644 --- a/tests/ui/auto-traits/assoc-ty.rs +++ b/tests/ui/auto-traits/assoc-ty.rs @@ -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 _` } diff --git a/tests/ui/impl-trait/ice-unexpected-param-type-whensubstituting-in-region-112823.next.stderr b/tests/ui/impl-trait/ice-unexpected-param-type-whensubstituting-in-region-112823.next.stderr index 3c24eb9adbe..ce64a022214 100644 --- a/tests/ui/impl-trait/ice-unexpected-param-type-whensubstituting-in-region-112823.next.stderr +++ b/tests/ui/impl-trait/ice-unexpected-param-type-whensubstituting-in-region-112823.next.stderr @@ -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`. diff --git a/tests/ui/impl-trait/ice-unexpected-param-type-whensubstituting-in-region-112823.rs b/tests/ui/impl-trait/ice-unexpected-param-type-whensubstituting-in-region-112823.rs index c97bd179943..cb32723b22d 100644 --- a/tests/ui/impl-trait/ice-unexpected-param-type-whensubstituting-in-region-112823.rs +++ b/tests/ui/impl-trait/ice-unexpected-param-type-whensubstituting-in-region-112823.rs @@ -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() {} diff --git a/tests/ui/specialization/fuzzed/fuzzing-ice-134905.rs b/tests/ui/specialization/fuzzed/fuzzing-ice-134905.rs new file mode 100644 index 00000000000..559c2345527 --- /dev/null +++ b/tests/ui/specialization/fuzzed/fuzzing-ice-134905.rs @@ -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() {} diff --git a/tests/ui/specialization/fuzzed/fuzzing-ice-134905.stderr b/tests/ui/specialization/fuzzed/fuzzing-ice-134905.stderr new file mode 100644 index 00000000000..611fef1df66 --- /dev/null +++ b/tests/ui/specialization/fuzzed/fuzzing-ice-134905.stderr @@ -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`. |
