Skip to content

Commit e2ee9f7

Browse files
Rollup merge of #136863 - lcnr:treat-as-rigid, r=compiler-errors
rework rigid alias handling Necessary for #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``
2 parents a567209 + 059288e commit e2ee9f7

File tree

17 files changed

+360
-245
lines changed

17 files changed

+360
-245
lines changed

compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs

+23-28
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use derive_where::derive_where;
66
use rustc_type_ir::fold::TypeFoldable;
77
use rustc_type_ir::inherent::*;
88
use rustc_type_ir::lang_items::TraitSolverLangItem;
9-
use rustc_type_ir::solve::inspect;
109
use rustc_type_ir::visit::TypeVisitableExt as _;
1110
use rustc_type_ir::{self as ty, Interner, TypingMode, Upcast as _, elaborate};
1211
use tracing::{debug, instrument};
@@ -297,25 +296,6 @@ where
297296
let Ok(normalized_self_ty) =
298297
self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty())
299298
else {
300-
// FIXME: We register a fake candidate when normalization fails so that
301-
// we can point at the reason for *why*. I'm tempted to say that this
302-
// is the wrong way to do this, though.
303-
let result =
304-
self.probe(|&result| inspect::ProbeKind::RigidAlias { result }).enter(|this| {
305-
let normalized_ty = this.next_ty_infer();
306-
let alias_relate_goal = Goal::new(
307-
this.cx(),
308-
goal.param_env,
309-
ty::PredicateKind::AliasRelate(
310-
goal.predicate.self_ty().into(),
311-
normalized_ty.into(),
312-
ty::AliasRelationDirection::Equate,
313-
),
314-
);
315-
this.add_goal(GoalSource::AliasWellFormed, alias_relate_goal);
316-
this.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
317-
});
318-
assert_eq!(result, Err(NoSolution));
319299
return vec![];
320300
};
321301

@@ -797,11 +777,12 @@ where
797777
/// treat the alias as rigid.
798778
///
799779
/// See trait-system-refactor-initiative#124 for more details.
800-
#[instrument(level = "debug", skip(self), ret)]
780+
#[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
801781
pub(super) fn merge_candidates(
802782
&mut self,
803783
proven_via: Option<TraitGoalProvenVia>,
804784
candidates: Vec<Candidate<I>>,
785+
inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
805786
) -> QueryResult<I> {
806787
let Some(proven_via) = proven_via else {
807788
// We don't care about overflow. If proving the trait goal overflowed, then
@@ -818,13 +799,27 @@ where
818799
// FIXME(const_trait_impl): should this behavior also be used by
819800
// constness checking. Doing so is *at least theoretically* breaking,
820801
// see github.com/rust-lang/rust/issues/133044#issuecomment-2500709754
821-
TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => candidates
822-
.iter()
823-
.filter(|c| {
824-
matches!(c.source, CandidateSource::AliasBound | CandidateSource::ParamEnv(_))
825-
})
826-
.map(|c| c.result)
827-
.collect(),
802+
TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => {
803+
let mut candidates_from_env: Vec<_> = candidates
804+
.iter()
805+
.filter(|c| {
806+
matches!(
807+
c.source,
808+
CandidateSource::AliasBound | CandidateSource::ParamEnv(_)
809+
)
810+
})
811+
.map(|c| c.result)
812+
.collect();
813+
814+
// If the trait goal has been proven by using the environment, we want to treat
815+
// aliases as rigid if there are no applicable projection bounds in the environment.
816+
if candidates_from_env.is_empty() {
817+
if let Ok(response) = inject_normalize_to_rigid_candidate(self) {
818+
candidates_from_env.push(response);
819+
}
820+
}
821+
candidates_from_env
822+
}
828823
TraitGoalProvenVia::Misc => candidates.iter().map(|c| c.result).collect(),
829824
};
830825

compiler/rustc_next_trait_solver/src/solve/effect_goals.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,6 @@ where
405405
goal.with(ecx.cx(), goal.predicate.trait_ref);
406406
ecx.compute_trait_goal(trait_goal)
407407
})?;
408-
self.merge_candidates(proven_via, candidates)
408+
self.merge_candidates(proven_via, candidates, |_ecx| Err(NoSolution))
409409
}
410410
}

compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs

+116-138
Original file line numberDiff line numberDiff line change
@@ -30,75 +30,26 @@ where
3030
) -> QueryResult<I> {
3131
self.set_is_normalizes_to_goal();
3232
debug_assert!(self.term_is_fully_unconstrained(goal));
33-
let normalize_result = self
34-
.probe(|&result| ProbeKind::TryNormalizeNonRigid { result })
35-
.enter(|this| this.normalize_at_least_one_step(goal));
36-
37-
match normalize_result {
38-
Ok(res) => Ok(res),
39-
Err(NoSolution) => {
40-
self.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| {
41-
let Goal { param_env, predicate: NormalizesTo { alias, term } } = goal;
42-
this.add_rigid_constraints(param_env, alias)?;
43-
this.relate_rigid_alias_non_alias(param_env, alias, ty::Invariant, term)?;
44-
this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
45-
})
46-
}
47-
}
48-
}
49-
50-
/// Register any obligations that are used to validate that an alias should be
51-
/// treated as rigid.
52-
///
53-
/// An alias may be considered rigid if it fails normalization, but we also don't
54-
/// want to consider aliases that are not well-formed to be rigid simply because
55-
/// they fail normalization.
56-
///
57-
/// For example, some `<T as Trait>::Assoc` where `T: Trait` does not hold, or an
58-
/// opaque type whose hidden type doesn't actually satisfy the opaque item bounds.
59-
fn add_rigid_constraints(
60-
&mut self,
61-
param_env: I::ParamEnv,
62-
rigid_alias: ty::AliasTerm<I>,
63-
) -> Result<(), NoSolution> {
64-
let cx = self.cx();
65-
match rigid_alias.kind(cx) {
66-
// Projections are rigid only if their trait ref holds,
67-
// and the GAT where-clauses hold.
68-
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
69-
let trait_ref = rigid_alias.trait_ref(cx);
70-
self.add_goal(GoalSource::AliasWellFormed, Goal::new(cx, param_env, trait_ref));
71-
Ok(())
72-
}
73-
ty::AliasTermKind::OpaqueTy => {
74-
if self.opaque_type_is_rigid(rigid_alias.def_id) {
75-
Ok(())
76-
} else {
77-
Err(NoSolution)
78-
}
79-
}
80-
// FIXME(generic_const_exprs): we would need to support generic consts here
81-
ty::AliasTermKind::UnevaluatedConst => Err(NoSolution),
82-
// Inherent and weak types are never rigid. This type must not be well-formed.
83-
ty::AliasTermKind::WeakTy | ty::AliasTermKind::InherentTy => Err(NoSolution),
84-
}
85-
}
86-
87-
/// Normalize the given alias by at least one step. If the alias is rigid, this
88-
/// returns `NoSolution`.
89-
#[instrument(level = "trace", skip(self), ret)]
90-
fn normalize_at_least_one_step(&mut self, goal: Goal<I, NormalizesTo<I>>) -> QueryResult<I> {
9133
let cx = self.cx();
9234
match goal.predicate.alias.kind(cx) {
9335
ty::AliasTermKind::ProjectionTy | ty::AliasTermKind::ProjectionConst => {
9436
let candidates = self.assemble_and_evaluate_candidates(goal);
37+
let trait_ref = goal.predicate.alias.trait_ref(cx);
9538
let (_, proven_via) =
9639
self.probe(|_| ProbeKind::ShadowedEnvProbing).enter(|ecx| {
97-
let trait_goal: Goal<I, ty::TraitPredicate<I>> =
98-
goal.with(cx, goal.predicate.alias.trait_ref(cx));
40+
let trait_goal: Goal<I, ty::TraitPredicate<I>> = goal.with(cx, trait_ref);
9941
ecx.compute_trait_goal(trait_goal)
10042
})?;
101-
self.merge_candidates(proven_via, candidates)
43+
self.merge_candidates(proven_via, candidates, |ecx| {
44+
ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| {
45+
this.structurally_instantiate_normalizes_to_term(
46+
goal,
47+
goal.predicate.alias,
48+
);
49+
this.add_goal(GoalSource::AliasWellFormed, goal.with(cx, trait_ref));
50+
this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
51+
})
52+
})
10253
}
10354
ty::AliasTermKind::InherentTy => self.normalize_inherent_associated_type(goal),
10455
ty::AliasTermKind::OpaqueTy => self.normalize_opaque_type(goal),
@@ -120,6 +71,17 @@ where
12071
self.eq(goal.param_env, goal.predicate.term, term)
12172
.expect("expected goal term to be fully unconstrained");
12273
}
74+
75+
/// Unlike `instantiate_normalizes_to_term` this instantiates the expected term
76+
/// with a rigid alias. Using this is pretty much always wrong.
77+
pub fn structurally_instantiate_normalizes_to_term(
78+
&mut self,
79+
goal: Goal<I, NormalizesTo<I>>,
80+
term: ty::AliasTerm<I>,
81+
) {
82+
self.relate_rigid_alias_non_alias(goal.param_env, term, ty::Invariant, goal.predicate.term)
83+
.expect("expected goal term to be fully unconstrained");
84+
}
12385
}
12486

12587
impl<D, I> assembly::GoalKind<D> for NormalizesTo<I>
@@ -576,80 +538,92 @@ where
576538
let cx = ecx.cx();
577539
let metadata_def_id = cx.require_lang_item(TraitSolverLangItem::Metadata);
578540
assert_eq!(metadata_def_id, goal.predicate.def_id());
579-
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
580-
let metadata_ty = match goal.predicate.self_ty().kind() {
581-
ty::Bool
582-
| ty::Char
583-
| ty::Int(..)
584-
| ty::Uint(..)
585-
| ty::Float(..)
586-
| ty::Array(..)
587-
| ty::Pat(..)
588-
| ty::RawPtr(..)
589-
| ty::Ref(..)
590-
| ty::FnDef(..)
591-
| ty::FnPtr(..)
592-
| ty::Closure(..)
593-
| ty::CoroutineClosure(..)
594-
| ty::Infer(ty::IntVar(..) | ty::FloatVar(..))
595-
| ty::Coroutine(..)
596-
| ty::CoroutineWitness(..)
597-
| ty::Never
598-
| ty::Foreign(..)
599-
| ty::Dynamic(_, _, ty::DynStar) => Ty::new_unit(cx),
600-
601-
ty::Error(e) => Ty::new_error(cx, e),
602-
603-
ty::Str | ty::Slice(_) => Ty::new_usize(cx),
604-
605-
ty::Dynamic(_, _, ty::Dyn) => {
606-
let dyn_metadata = cx.require_lang_item(TraitSolverLangItem::DynMetadata);
607-
cx.type_of(dyn_metadata)
608-
.instantiate(cx, &[I::GenericArg::from(goal.predicate.self_ty())])
609-
}
541+
let metadata_ty = match goal.predicate.self_ty().kind() {
542+
ty::Bool
543+
| ty::Char
544+
| ty::Int(..)
545+
| ty::Uint(..)
546+
| ty::Float(..)
547+
| ty::Array(..)
548+
| ty::Pat(..)
549+
| ty::RawPtr(..)
550+
| ty::Ref(..)
551+
| ty::FnDef(..)
552+
| ty::FnPtr(..)
553+
| ty::Closure(..)
554+
| ty::CoroutineClosure(..)
555+
| ty::Infer(ty::IntVar(..) | ty::FloatVar(..))
556+
| ty::Coroutine(..)
557+
| ty::CoroutineWitness(..)
558+
| ty::Never
559+
| ty::Foreign(..)
560+
| ty::Dynamic(_, _, ty::DynStar) => Ty::new_unit(cx),
610561

611-
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
612-
// This is the "fallback impl" for type parameters, unnormalizable projections
613-
// and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
614-
// FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
615-
// exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
616-
let sized_predicate = ty::TraitRef::new(
617-
cx,
618-
cx.require_lang_item(TraitSolverLangItem::Sized),
619-
[I::GenericArg::from(goal.predicate.self_ty())],
620-
);
621-
// FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`?
622-
ecx.add_goal(GoalSource::Misc, goal.with(cx, sized_predicate));
623-
Ty::new_unit(cx)
624-
}
562+
ty::Error(e) => Ty::new_error(cx, e),
625563

626-
ty::Adt(def, args) if def.is_struct() => match def.struct_tail_ty(cx) {
627-
None => Ty::new_unit(cx),
628-
Some(tail_ty) => {
629-
Ty::new_projection(cx, metadata_def_id, [tail_ty.instantiate(cx, args)])
630-
}
631-
},
632-
ty::Adt(_, _) => Ty::new_unit(cx),
564+
ty::Str | ty::Slice(_) => Ty::new_usize(cx),
633565

634-
ty::Tuple(elements) => match elements.last() {
635-
None => Ty::new_unit(cx),
636-
Some(tail_ty) => Ty::new_projection(cx, metadata_def_id, [tail_ty]),
637-
},
566+
ty::Dynamic(_, _, ty::Dyn) => {
567+
let dyn_metadata = cx.require_lang_item(TraitSolverLangItem::DynMetadata);
568+
cx.type_of(dyn_metadata)
569+
.instantiate(cx, &[I::GenericArg::from(goal.predicate.self_ty())])
570+
}
571+
572+
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
573+
// This is the "fallback impl" for type parameters, unnormalizable projections
574+
// and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`.
575+
// FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't
576+
// exist. Instead, `Pointee<Metadata = ()>` should be a supertrait of `Sized`.
577+
let alias_bound_result =
578+
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
579+
let sized_predicate = ty::TraitRef::new(
580+
cx,
581+
cx.require_lang_item(TraitSolverLangItem::Sized),
582+
[I::GenericArg::from(goal.predicate.self_ty())],
583+
);
584+
ecx.add_goal(GoalSource::Misc, goal.with(cx, sized_predicate));
585+
ecx.instantiate_normalizes_to_term(goal, Ty::new_unit(cx).into());
586+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
587+
});
588+
// In case the dummy alias-bound candidate does not apply, we instead treat this projection
589+
// as rigid.
590+
return alias_bound_result.or_else(|NoSolution| {
591+
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|this| {
592+
this.structurally_instantiate_normalizes_to_term(
593+
goal,
594+
goal.predicate.alias,
595+
);
596+
this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
597+
})
598+
});
599+
}
638600

639-
ty::UnsafeBinder(_) => {
640-
// FIXME(unsafe_binder): Figure out how to handle pointee for unsafe binders.
641-
todo!()
601+
ty::Adt(def, args) if def.is_struct() => match def.struct_tail_ty(cx) {
602+
None => Ty::new_unit(cx),
603+
Some(tail_ty) => {
604+
Ty::new_projection(cx, metadata_def_id, [tail_ty.instantiate(cx, args)])
642605
}
606+
},
607+
ty::Adt(_, _) => Ty::new_unit(cx),
643608

644-
ty::Infer(
645-
ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_),
646-
)
647-
| ty::Bound(..) => panic!(
648-
"unexpected self ty `{:?}` when normalizing `<T as Pointee>::Metadata`",
649-
goal.predicate.self_ty()
650-
),
651-
};
609+
ty::Tuple(elements) => match elements.last() {
610+
None => Ty::new_unit(cx),
611+
Some(tail_ty) => Ty::new_projection(cx, metadata_def_id, [tail_ty]),
612+
},
652613

614+
ty::UnsafeBinder(_) => {
615+
// FIXME(unsafe_binder): Figure out how to handle pointee for unsafe binders.
616+
todo!()
617+
}
618+
619+
ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_))
620+
| ty::Bound(..) => panic!(
621+
"unexpected self ty `{:?}` when normalizing `<T as Pointee>::Metadata`",
622+
goal.predicate.self_ty()
623+
),
624+
};
625+
626+
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
653627
ecx.instantiate_normalizes_to_term(goal, metadata_ty.into());
654628
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
655629
})
@@ -850,12 +824,14 @@ where
850824
todo!("discr subgoal...")
851825
}
852826

853-
// We do not call `Ty::discriminant_ty` on alias, param, or placeholder
854-
// types, which return `<self_ty as DiscriminantKind>::Discriminant`
855-
// (or ICE in the case of placeholders). Projecting a type to itself
856-
// is never really productive.
827+
// Given an alias, parameter, or placeholder we add an impl candidate normalizing to a rigid
828+
// alias. In case there's a where-bound further constraining this alias it is preferred over
829+
// this impl candidate anyways. It's still a bit scuffed.
857830
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
858-
return Err(NoSolution);
831+
return ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
832+
ecx.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
833+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
834+
});
859835
}
860836

861837
ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_))
@@ -902,12 +878,14 @@ where
902878
todo!()
903879
}
904880

905-
// We do not call `Ty::async_destructor_ty` on alias, param, or placeholder
906-
// types, which return `<self_ty as AsyncDestruct>::AsyncDestructor`
907-
// (or ICE in the case of placeholders). Projecting a type to itself
908-
// is never really productive.
881+
// Given an alias, parameter, or placeholder we add an impl candidate normalizing to a rigid
882+
// alias. In case there's a where-bound further constraining this alias it is preferred over
883+
// this impl candidate anyways. It's still a bit scuffed.
909884
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
910-
return Err(NoSolution);
885+
return ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| {
886+
ecx.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias);
887+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
888+
});
911889
}
912890

913891
ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_))

0 commit comments

Comments
 (0)