Skip to content

Commit

Permalink
Rollup merge of #124566 - lcnr:normalizes-to-proof-tree, r=compiler-e…
Browse files Browse the repository at this point in the history
…rrors

fix `NormalizesTo` proof tree issue

fixes #124422
cc #121848

r? ``@compiler-errors``
  • Loading branch information
matthiaskrgr committed May 1, 2024
2 parents 38cbad9 + f323f9d commit 0dbe07f
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 52 deletions.
170 changes: 120 additions & 50 deletions compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
Expand Up @@ -14,6 +14,7 @@ use rustc_ast_ir::visit::VisitorResult;
use rustc_infer::infer::resolve::EagerResolver;
use rustc_infer::infer::type_variable::TypeVariableOrigin;
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk};
use rustc_infer::traits::{TraitEngine, TraitEngineExt};
use rustc_macros::extension;
use rustc_middle::infer::unify_key::ConstVariableOrigin;
use rustc_middle::traits::query::NoSolution;
Expand All @@ -22,9 +23,10 @@ use rustc_middle::traits::solve::{Certainty, Goal};
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty;
use rustc_middle::ty::TypeFoldable;
use rustc_span::Span;
use rustc_span::{Span, DUMMY_SP};

use crate::solve::eval_ctxt::canonical;
use crate::solve::FulfillmentCtxt;
use crate::solve::{EvalCtxt, GoalEvaluationKind, GoalSource};
use crate::solve::{GenerateProofTree, InferCtxtEvalExt};

Expand All @@ -37,7 +39,54 @@ pub struct InspectGoal<'a, 'tcx> {
depth: usize,
orig_values: Vec<ty::GenericArg<'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> {
Expand Down Expand Up @@ -115,42 +164,47 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
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
.into_iter()
.map(|goal| {
let proof_tree = match goal.predicate.kind().no_bound_vars() {
Some(ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })) => {
let unconstrained_term = match term.unpack() {
ty::TermKind::Ty(_) => infcx
.next_ty_var(TypeVariableOrigin { param_def_id: None, span })
.into(),
ty::TermKind::Const(ct) => infcx
.next_const_var(
ct.ty(),
ConstVariableOrigin { param_def_id: None, span },
)
.into(),
};
let goal = goal
.with(infcx.tcx, ty::NormalizesTo { alias, term: unconstrained_term });
let proof_tree =
EvalCtxt::enter_root(infcx, GenerateProofTree::Yes, |ecx| {
ecx.evaluate_goal_raw(
GoalEvaluationKind::Root,
GoalSource::Misc,
goal,
)
})
.1;
let InferOk { value: (), obligations: _ } = infcx
.at(&ObligationCause::dummy_with_span(span), param_env)
.eq(DefineOpaqueTypes::Yes, term, unconstrained_term)
.unwrap();
proof_tree
}
_ => infcx.evaluate_root_goal(goal, GenerateProofTree::Yes).1,
};
InspectGoal::new(infcx, self.goal.depth + 1, proof_tree.unwrap())
.map(|goal| match goal.predicate.kind().no_bound_vars() {
Some(ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })) => {
let unconstrained_term = match term.unpack() {
ty::TermKind::Ty(_) => infcx
.next_ty_var(TypeVariableOrigin { param_def_id: None, span })
.into(),
ty::TermKind::Const(ct) => infcx
.next_const_var(
ct.ty(),
ConstVariableOrigin { param_def_id: None, span },
)
.into(),
};
let goal =
goal.with(infcx.tcx, ty::NormalizesTo { alias, term: unconstrained_term });
let proof_tree = EvalCtxt::enter_root(infcx, GenerateProofTree::Yes, |ecx| {
ecx.evaluate_goal_raw(GoalEvaluationKind::Root, GoalSource::Misc, goal)
})
.1;
InspectGoal::new(
infcx,
self.goal.depth + 1,
proof_tree.unwrap(),
Some(NormalizesToTermHack { term, unconstrained_term }),
)
}
_ => InspectGoal::new(
infcx,
self.goal.depth + 1,
infcx.evaluate_root_goal(goal, GenerateProofTree::Yes).1.unwrap(),
None,
),
})
.collect()
}
Expand All @@ -172,7 +226,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
}

pub fn result(&self) -> Result<Certainty, NoSolution> {
self.evaluation.result.map(|c| c.value.certainty)
self.result
}

fn candidates_recur(
Expand Down Expand Up @@ -229,11 +283,11 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {

pub fn candidates(&'a self) -> Vec<InspectCandidate<'a, 'tcx>> {
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::CycleInStack
| inspect::CanonicalGoalEvaluationKind::ProvisionalCacheHit => {
warn!("unexpected root evaluation: {:?}", self.evaluation);
warn!("unexpected root evaluation: {:?}", self.evaluation_kind);
return vec![];
}
inspect::CanonicalGoalEvaluationKind::Evaluation { revisions } => {
Expand Down Expand Up @@ -262,17 +316,33 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
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;
match kind {
inspect::GoalEvaluationKind::Root { orig_values } => InspectGoal {
infcx,
depth,
orig_values,
goal: uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)),
evaluation,
},
inspect::GoalEvaluationKind::Nested { .. } => unreachable!(),
let inspect::GoalEvaluationKind::Root { orig_values } = kind else { unreachable!() };

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,
depth,
orig_values,
goal: uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)),
result,
evaluation_kind: evaluation.kind,
normalizes_to_term_hack,
}
}
}
Expand All @@ -299,6 +369,6 @@ impl<'tcx> InferCtxt<'tcx> {
) -> V::Result {
let (_, proof_tree) = self.evaluate_root_goal(goal, GenerateProofTree::Yes);
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))
}
}
@@ -1,5 +1,5 @@
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)]
| ^^^^^^^^^^^^^^
Expand Down
@@ -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`.
10 changes: 9 additions & 1 deletion tests/ui/specialization/specialization-overlap-projection.rs
@@ -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
// projections involve specialization, so long as the associated type is
Expand All @@ -12,14 +15,19 @@ trait Assoc {

impl<T> Assoc for T {
default type Output = bool;
//[next]~^ ERROR type annotations needed
}

impl Assoc for u8 { type Output = u8; }
//[next]~^ ERROR type annotations needed
impl Assoc for u16 { type Output = u16; }
//[next]~^ ERROR type annotations needed

trait Foo {}
impl Foo for u32 {}
impl Foo for <u8 as Assoc>::Output {}
//[next]~^ ERROR conflicting implementations of trait `Foo` for type `u32`
impl Foo for <u16 as Assoc>::Output {}
//[next]~^ ERROR conflicting implementations of trait `Foo` for type `u32`

fn main() {}

0 comments on commit 0dbe07f

Please sign in to comment.