Rollup merge of #124566 - lcnr:normalizes-to-proof-tree, r=compiler-errors

fix `NormalizesTo` proof tree issue

fixes #124422
cc #121848

r? ``@compiler-errors``
This commit is contained in:
Matthias Krüger 2024-05-01 20:05:26 +02:00 committed by GitHub
commit 0dbe07f201
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 179 additions and 52 deletions

View File

@ -14,6 +14,7 @@ use rustc_ast_ir::visit::VisitorResult;
use rustc_infer::infer::resolve::EagerResolver; use rustc_infer::infer::resolve::EagerResolver;
use rustc_infer::infer::type_variable::TypeVariableOrigin; use rustc_infer::infer::type_variable::TypeVariableOrigin;
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk}; use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk};
use rustc_infer::traits::{TraitEngine, TraitEngineExt};
use rustc_macros::extension; use rustc_macros::extension;
use rustc_middle::infer::unify_key::ConstVariableOrigin; use rustc_middle::infer::unify_key::ConstVariableOrigin;
use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::query::NoSolution;
@ -22,9 +23,10 @@ use rustc_middle::traits::solve::{Certainty, Goal};
use rustc_middle::traits::ObligationCause; use rustc_middle::traits::ObligationCause;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_middle::ty::TypeFoldable; use rustc_middle::ty::TypeFoldable;
use rustc_span::Span; use rustc_span::{Span, DUMMY_SP};
use crate::solve::eval_ctxt::canonical; use crate::solve::eval_ctxt::canonical;
use crate::solve::FulfillmentCtxt;
use crate::solve::{EvalCtxt, GoalEvaluationKind, GoalSource}; use crate::solve::{EvalCtxt, GoalEvaluationKind, GoalSource};
use crate::solve::{GenerateProofTree, InferCtxtEvalExt}; use crate::solve::{GenerateProofTree, InferCtxtEvalExt};
@ -37,7 +39,54 @@ pub struct InspectGoal<'a, 'tcx> {
depth: usize, depth: usize,
orig_values: Vec<ty::GenericArg<'tcx>>, orig_values: Vec<ty::GenericArg<'tcx>>,
goal: Goal<'tcx, ty::Predicate<'tcx>>, goal: Goal<'tcx, ty::Predicate<'tcx>>,
evaluation: inspect::CanonicalGoalEvaluation<'tcx>, result: Result<Certainty, NoSolution>,
evaluation_kind: inspect::CanonicalGoalEvaluationKind<'tcx>,
normalizes_to_term_hack: Option<NormalizesToTermHack<'tcx>>,
}
/// The expected term of a `NormalizesTo` goal gets replaced
/// with an unconstrained inference variable when computing
/// `NormalizesTo` goals and we return the nested goals to the
/// caller, who also equates the actual term with the expected.
///
/// This is an implementation detail of the trait solver and
/// not something we want to leak to users. We therefore
/// treat `NormalizesTo` goals as if they apply the expected
/// type at the end of each candidate.
#[derive(Copy, Clone)]
struct NormalizesToTermHack<'tcx> {
term: ty::Term<'tcx>,
unconstrained_term: ty::Term<'tcx>,
}
impl<'tcx> NormalizesToTermHack<'tcx> {
/// Relate the `term` with the new `unconstrained_term` created
/// when computing the proof tree for this `NormalizesTo` goals.
/// This handles nested obligations.
fn constrain(
self,
infcx: &InferCtxt<'tcx>,
span: Span,
param_env: ty::ParamEnv<'tcx>,
) -> Result<Certainty, NoSolution> {
infcx
.at(&ObligationCause::dummy_with_span(span), param_env)
.eq(DefineOpaqueTypes::Yes, self.term, self.unconstrained_term)
.map_err(|_| NoSolution)
.and_then(|InferOk { value: (), obligations }| {
let mut fulfill_cx = FulfillmentCtxt::new(infcx);
fulfill_cx.register_predicate_obligations(infcx, obligations);
if fulfill_cx.select_where_possible(infcx).is_empty() {
if fulfill_cx.pending_obligations().is_empty() {
Ok(Certainty::Yes)
} else {
Ok(Certainty::AMBIGUOUS)
}
} else {
Err(NoSolution)
}
})
}
} }
pub struct InspectCandidate<'a, 'tcx> { pub struct InspectCandidate<'a, 'tcx> {
@ -115,10 +164,16 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
self.final_state, self.final_state,
); );
if let Some(term_hack) = self.goal.normalizes_to_term_hack {
// FIXME: We ignore the expected term of `NormalizesTo` goals
// when computing the result of its candidates. This is
// scuffed.
let _ = term_hack.constrain(infcx, span, param_env);
}
instantiated_goals instantiated_goals
.into_iter() .into_iter()
.map(|goal| { .map(|goal| match goal.predicate.kind().no_bound_vars() {
let proof_tree = match goal.predicate.kind().no_bound_vars() {
Some(ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })) => { Some(ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })) => {
let unconstrained_term = match term.unpack() { let unconstrained_term = match term.unpack() {
ty::TermKind::Ty(_) => infcx ty::TermKind::Ty(_) => infcx
@ -131,26 +186,25 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
) )
.into(), .into(),
}; };
let goal = goal let goal =
.with(infcx.tcx, ty::NormalizesTo { alias, term: unconstrained_term }); goal.with(infcx.tcx, ty::NormalizesTo { alias, term: unconstrained_term });
let proof_tree = let proof_tree = EvalCtxt::enter_root(infcx, GenerateProofTree::Yes, |ecx| {
EvalCtxt::enter_root(infcx, GenerateProofTree::Yes, |ecx| { ecx.evaluate_goal_raw(GoalEvaluationKind::Root, GoalSource::Misc, goal)
ecx.evaluate_goal_raw(
GoalEvaluationKind::Root,
GoalSource::Misc,
goal,
)
}) })
.1; .1;
let InferOk { value: (), obligations: _ } = infcx InspectGoal::new(
.at(&ObligationCause::dummy_with_span(span), param_env) infcx,
.eq(DefineOpaqueTypes::Yes, term, unconstrained_term) self.goal.depth + 1,
.unwrap(); proof_tree.unwrap(),
proof_tree Some(NormalizesToTermHack { term, unconstrained_term }),
)
} }
_ => infcx.evaluate_root_goal(goal, GenerateProofTree::Yes).1, _ => InspectGoal::new(
}; infcx,
InspectGoal::new(infcx, self.goal.depth + 1, proof_tree.unwrap()) self.goal.depth + 1,
infcx.evaluate_root_goal(goal, GenerateProofTree::Yes).1.unwrap(),
None,
),
}) })
.collect() .collect()
} }
@ -172,7 +226,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
} }
pub fn result(&self) -> Result<Certainty, NoSolution> { pub fn result(&self) -> Result<Certainty, NoSolution> {
self.evaluation.result.map(|c| c.value.certainty) self.result
} }
fn candidates_recur( fn candidates_recur(
@ -229,11 +283,11 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
pub fn candidates(&'a self) -> Vec<InspectCandidate<'a, 'tcx>> { pub fn candidates(&'a self) -> Vec<InspectCandidate<'a, 'tcx>> {
let mut candidates = vec![]; let mut candidates = vec![];
let last_eval_step = match self.evaluation.kind { let last_eval_step = match self.evaluation_kind {
inspect::CanonicalGoalEvaluationKind::Overflow inspect::CanonicalGoalEvaluationKind::Overflow
| inspect::CanonicalGoalEvaluationKind::CycleInStack | inspect::CanonicalGoalEvaluationKind::CycleInStack
| inspect::CanonicalGoalEvaluationKind::ProvisionalCacheHit => { | inspect::CanonicalGoalEvaluationKind::ProvisionalCacheHit => {
warn!("unexpected root evaluation: {:?}", self.evaluation); warn!("unexpected root evaluation: {:?}", self.evaluation_kind);
return vec![]; return vec![];
} }
inspect::CanonicalGoalEvaluationKind::Evaluation { revisions } => { inspect::CanonicalGoalEvaluationKind::Evaluation { revisions } => {
@ -262,17 +316,33 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
candidates.pop().filter(|_| candidates.is_empty()) candidates.pop().filter(|_| candidates.is_empty())
} }
fn new(infcx: &'a InferCtxt<'tcx>, depth: usize, root: inspect::GoalEvaluation<'tcx>) -> Self { fn new(
infcx: &'a InferCtxt<'tcx>,
depth: usize,
root: inspect::GoalEvaluation<'tcx>,
normalizes_to_term_hack: Option<NormalizesToTermHack<'tcx>>,
) -> Self {
let inspect::GoalEvaluation { uncanonicalized_goal, kind, evaluation } = root; let inspect::GoalEvaluation { uncanonicalized_goal, kind, evaluation } = root;
match kind { let inspect::GoalEvaluationKind::Root { orig_values } = kind else { unreachable!() };
inspect::GoalEvaluationKind::Root { orig_values } => InspectGoal {
let result = evaluation.result.and_then(|ok| {
if let Some(term_hack) = normalizes_to_term_hack {
infcx
.probe(|_| term_hack.constrain(infcx, DUMMY_SP, uncanonicalized_goal.param_env))
.map(|certainty| ok.value.certainty.unify_with(certainty))
} else {
Ok(ok.value.certainty)
}
});
InspectGoal {
infcx, infcx,
depth, depth,
orig_values, orig_values,
goal: uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)), goal: uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)),
evaluation, result,
}, evaluation_kind: evaluation.kind,
inspect::GoalEvaluationKind::Nested { .. } => unreachable!(), normalizes_to_term_hack,
} }
} }
} }
@ -299,6 +369,6 @@ impl<'tcx> InferCtxt<'tcx> {
) -> V::Result { ) -> V::Result {
let (_, proof_tree) = self.evaluate_root_goal(goal, GenerateProofTree::Yes); let (_, proof_tree) = self.evaluate_root_goal(goal, GenerateProofTree::Yes);
let proof_tree = proof_tree.unwrap(); let proof_tree = proof_tree.unwrap();
visitor.visit_goal(&InspectGoal::new(self, 0, proof_tree)) visitor.visit_goal(&InspectGoal::new(self, 0, proof_tree, None))
} }
} }

View File

@ -1,5 +1,5 @@
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/specialization-overlap-projection.rs:7:12 --> $DIR/specialization-overlap-projection.rs:10:12
| |
LL | #![feature(specialization)] LL | #![feature(specialization)]
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^

View File

@ -0,0 +1,49 @@
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/specialization-overlap-projection.rs:10: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[E0119]: conflicting implementations of trait `Foo` for type `u32`
--> $DIR/specialization-overlap-projection.rs:28:1
|
LL | impl Foo for u32 {}
| ---------------- first implementation here
LL | impl Foo for <u8 as Assoc>::Output {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `u32`
error[E0119]: conflicting implementations of trait `Foo` for type `u32`
--> $DIR/specialization-overlap-projection.rs:30:1
|
LL | impl Foo for u32 {}
| ---------------- first implementation here
...
LL | impl Foo for <u16 as Assoc>::Output {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `u32`
error[E0282]: type annotations needed
--> $DIR/specialization-overlap-projection.rs:17:27
|
LL | default type Output = bool;
| ^^^^ cannot infer type for associated type `<T as Assoc>::Output`
error[E0282]: type annotations needed
--> $DIR/specialization-overlap-projection.rs:21:35
|
LL | impl Assoc for u8 { type Output = u8; }
| ^^ cannot infer type for associated type `<u8 as Assoc>::Output`
error[E0282]: type annotations needed
--> $DIR/specialization-overlap-projection.rs:23:36
|
LL | impl Assoc for u16 { type Output = u16; }
| ^^^ cannot infer type for associated type `<u16 as Assoc>::Output`
error: aborting due to 5 previous errors; 1 warning emitted
Some errors have detailed explanations: E0119, E0282.
For more information about an error, try `rustc --explain E0119`.

View File

@ -1,4 +1,7 @@
//@ check-pass //@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
//@[current] check-pass
// Test that impls on projected self types can resolve overlap, even when the // Test that impls on projected self types can resolve overlap, even when the
// projections involve specialization, so long as the associated type is // projections involve specialization, so long as the associated type is
@ -12,14 +15,19 @@ trait Assoc {
impl<T> Assoc for T { impl<T> Assoc for T {
default type Output = bool; default type Output = bool;
//[next]~^ ERROR type annotations needed
} }
impl Assoc for u8 { type Output = u8; } impl Assoc for u8 { type Output = u8; }
//[next]~^ ERROR type annotations needed
impl Assoc for u16 { type Output = u16; } impl Assoc for u16 { type Output = u16; }
//[next]~^ ERROR type annotations needed
trait Foo {} trait Foo {}
impl Foo for u32 {} impl Foo for u32 {}
impl Foo for <u8 as Assoc>::Output {} impl Foo for <u8 as Assoc>::Output {}
//[next]~^ ERROR conflicting implementations of trait `Foo` for type `u32`
impl Foo for <u16 as Assoc>::Output {} impl Foo for <u16 as Assoc>::Output {}
//[next]~^ ERROR conflicting implementations of trait `Foo` for type `u32`
fn main() {} fn main() {}