Skip to content

Fix effect predicates from item bounds in old solver #134638

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 137 additions & 28 deletions compiler/rustc_trait_selection/src/traits/effects.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use rustc_hir as hir;
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes, InferCtxt};
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes};
use rustc_infer::traits::{ImplSource, Obligation, PredicateObligation};
use rustc_middle::span_bug;
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
use rustc_middle::ty::{self, TypingMode};
use rustc_type_ir::elaborate::elaborate;
use rustc_type_ir::solve::NoSolution;
use thin_vec::ThinVec;
use thin_vec::{ThinVec, thin_vec};

use super::SelectionContext;
use super::normalize::normalize_with_depth_to;

pub type HostEffectObligation<'tcx> = Obligation<'tcx, ty::HostEffectPredicate<'tcx>>;

Expand Down Expand Up @@ -38,6 +40,12 @@ pub fn evaluate_host_effect_obligation<'tcx>(
Err(EvaluationFailure::NoSolution) => {}
}

match evaluate_host_effect_from_item_bounds(selcx, obligation) {
Ok(result) => return Ok(result),
Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
Err(EvaluationFailure::NoSolution) => {}
}

match evaluate_host_effect_from_selection_candiate(selcx, obligation) {
Ok(result) => return Ok(result),
Err(EvaluationFailure::Ambiguous) => return Err(EvaluationFailure::Ambiguous),
Expand All @@ -48,24 +56,45 @@ pub fn evaluate_host_effect_obligation<'tcx>(
}

fn match_candidate<'tcx>(
infcx: &InferCtxt<'tcx>,
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &HostEffectObligation<'tcx>,
candidate: ty::Binder<'tcx, ty::HostEffectPredicate<'tcx>>,
candidate_is_unnormalized: bool,
more_nested: impl FnOnce(&mut SelectionContext<'_, 'tcx>, &mut ThinVec<PredicateObligation<'tcx>>),
) -> Result<ThinVec<PredicateObligation<'tcx>>, NoSolution> {
if !candidate.skip_binder().constness.satisfies(obligation.predicate.constness) {
return Err(NoSolution);
}

let candidate = infcx.instantiate_binder_with_fresh_vars(
let mut candidate = selcx.infcx.instantiate_binder_with_fresh_vars(
obligation.cause.span,
BoundRegionConversionTime::HigherRankedType,
candidate,
);

let mut nested = infcx
.at(&obligation.cause, obligation.param_env)
.eq(DefineOpaqueTypes::Yes, obligation.predicate.trait_ref, candidate.trait_ref)?
.into_obligations();
let mut nested = thin_vec![];

// Unlike param-env bounds, item bounds may not be normalized.
if candidate_is_unnormalized {
candidate = normalize_with_depth_to(
selcx,
obligation.param_env,
obligation.cause.clone(),
obligation.recursion_depth,
candidate,
&mut nested,
);
}

nested.extend(
selcx
.infcx
.at(&obligation.cause, obligation.param_env)
.eq(DefineOpaqueTypes::Yes, obligation.predicate.trait_ref, candidate.trait_ref)?
.into_obligations(),
);

more_nested(selcx, &mut nested);

for nested in &mut nested {
nested.set_depth_from_parent(obligation.recursion_depth);
Expand All @@ -82,36 +111,116 @@ fn evaluate_host_effect_from_bounds<'tcx>(
let drcx = DeepRejectCtxt::relate_rigid_rigid(selcx.tcx());
let mut candidate = None;

for predicate in obligation.param_env.caller_bounds() {
let bound_predicate = predicate.kind();
if let ty::ClauseKind::HostEffect(data) = predicate.kind().skip_binder() {
let data = bound_predicate.rebind(data);
if data.skip_binder().trait_ref.def_id != obligation.predicate.trait_ref.def_id {
continue;
for clause in obligation.param_env.caller_bounds() {
let bound_clause = clause.kind();
let ty::ClauseKind::HostEffect(data) = bound_clause.skip_binder() else {
continue;
};
let data = bound_clause.rebind(data);
if data.skip_binder().trait_ref.def_id != obligation.predicate.trait_ref.def_id {
continue;
}

if !drcx
.args_may_unify(obligation.predicate.trait_ref.args, data.skip_binder().trait_ref.args)
{
continue;
}

let is_match =
infcx.probe(|_| match_candidate(selcx, obligation, data, false, |_, _| {}).is_ok());

if is_match {
if candidate.is_some() {
return Err(EvaluationFailure::Ambiguous);
} else {
candidate = Some(data);
}
}
}

if !drcx.args_may_unify(
obligation.predicate.trait_ref.args,
data.skip_binder().trait_ref.args,
if let Some(data) = candidate {
Ok(match_candidate(selcx, obligation, data, false, |_, _| {})
.expect("candidate matched before, so it should match again"))
} else {
Err(EvaluationFailure::NoSolution)
}
}

fn evaluate_host_effect_from_item_bounds<'tcx>(
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &HostEffectObligation<'tcx>,
) -> Result<ThinVec<PredicateObligation<'tcx>>, EvaluationFailure> {
let infcx = selcx.infcx;
let tcx = infcx.tcx;
let drcx = DeepRejectCtxt::relate_rigid_rigid(selcx.tcx());
let mut candidate = None;

let mut consider_ty = obligation.predicate.self_ty();
while let ty::Alias(kind @ (ty::Projection | ty::Opaque), alias_ty) = *consider_ty.kind() {
if tcx.is_conditionally_const(alias_ty.def_id) {
for clause in elaborate(
tcx,
tcx.explicit_implied_const_bounds(alias_ty.def_id)
.iter_instantiated_copied(tcx, alias_ty.args)
.map(|(trait_ref, _)| {
trait_ref.to_host_effect_clause(tcx, obligation.predicate.constness)
}),
) {
continue;
}
let bound_clause = clause.kind();
let ty::ClauseKind::HostEffect(data) = bound_clause.skip_binder() else {
unreachable!("should not elaborate non-HostEffect from HostEffect")
};
let data = bound_clause.rebind(data);
if data.skip_binder().trait_ref.def_id != obligation.predicate.trait_ref.def_id {
continue;
}

let is_match = infcx.probe(|_| match_candidate(infcx, obligation, data).is_ok());
if !drcx.args_may_unify(
obligation.predicate.trait_ref.args,
data.skip_binder().trait_ref.args,
) {
continue;
}

if is_match {
if candidate.is_some() {
return Err(EvaluationFailure::Ambiguous);
} else {
candidate = Some(data);
let is_match = infcx
.probe(|_| match_candidate(selcx, obligation, data, true, |_, _| {}).is_ok());

if is_match {
if candidate.is_some() {
return Err(EvaluationFailure::Ambiguous);
} else {
candidate = Some((data, alias_ty));
}
}
}
}

if kind != ty::Projection {
break;
}

consider_ty = alias_ty.self_ty();
}

if let Some(data) = candidate {
Ok(match_candidate(infcx, obligation, data)
.expect("candidate matched before, so it should match again"))
if let Some((data, alias_ty)) = candidate {
Ok(match_candidate(selcx, obligation, data, true, |selcx, nested| {
// An alias bound only holds if we also check the const conditions
// of the alias, so we need to register those, too.
let const_conditions = normalize_with_depth_to(
selcx,
obligation.param_env,
obligation.cause.clone(),
obligation.recursion_depth,
tcx.const_conditions(alias_ty.def_id).instantiate(tcx, alias_ty.args),
nested,
);
nested.extend(const_conditions.into_iter().map(|(trait_ref, _)| {
obligation
.with(tcx, trait_ref.to_host_effect_clause(tcx, obligation.predicate.constness))
}));
})
.expect("candidate matched before, so it should match again"))
} else {
Err(EvaluationFailure::NoSolution)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//@ compile-flags: -Znext-solver
//@ revisions: current next
//@[next] compile-flags: -Znext-solver
//@ check-pass

#![feature(const_trait_impl)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//@ compile-flags: -Znext-solver
//@ known-bug: unknown
//@ check-pass

#![feature(const_trait_impl, generic_const_exprs)]
#![allow(incomplete_features)]
Expand Down
35 changes: 0 additions & 35 deletions tests/ui/traits/const-traits/assoc-type-const-bound-usage-1.stderr

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error[E0277]: the trait bound `U: ~const Other` is not satisfied
--> $DIR/assoc-type-const-bound-usage-fail-2.rs:24:5
|
LL | T::Assoc::<U>::func();
| ^^^^^^^^^^^^^

error[E0277]: the trait bound `U: ~const Other` is not satisfied
--> $DIR/assoc-type-const-bound-usage-fail-2.rs:27:5
|
LL | <T as Trait>::Assoc::<U>::func();
| ^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
error[E0277]: the trait bound `<T as Trait>::Assoc<U>: ~const Trait` is not satisfied
--> $DIR/assoc-type-const-bound-usage-fail-2.rs:23:5
--> $DIR/assoc-type-const-bound-usage-fail-2.rs:24:5
|
LL | T::Assoc::<U>::func();
| ^^^^^^^^^^^^^

error[E0277]: the trait bound `<T as Trait>::Assoc<U>: ~const Trait` is not satisfied
--> $DIR/assoc-type-const-bound-usage-fail-2.rs:25:5
--> $DIR/assoc-type-const-bound-usage-fail-2.rs:27:5
|
LL | <T as Trait>::Assoc::<U>::func();
| ^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//@ compile-flags: -Znext-solver
//@ revisions: current next
//@[next] compile-flags: -Znext-solver

// Check that `~const` item bounds only hold if the where clauses on the
// associated type are also const.
Expand All @@ -21,9 +22,11 @@ trait Other {}

const fn fails<T: ~const Trait, U: Other>() {
T::Assoc::<U>::func();
//~^ ERROR the trait bound `<T as Trait>::Assoc<U>: ~const Trait` is not satisfied
//[current]~^ ERROR the trait bound `U: ~const Other` is not satisfied
//[next]~^^ ERROR the trait bound `<T as Trait>::Assoc<U>: ~const Trait` is not satisfied
<T as Trait>::Assoc::<U>::func();
//~^ ERROR the trait bound `<T as Trait>::Assoc<U>: ~const Trait` is not satisfied
//[current]~^ ERROR the trait bound `U: ~const Other` is not satisfied
//[next]~^^ ERROR the trait bound `<T as Trait>::Assoc<U>: ~const Trait` is not satisfied
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deeply_normalize_for_diagnostics?

Copy link
Member Author

@compiler-errors compiler-errors Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<T as Trait>::Assoc<U> is not U. There's no normalization happening here -- that's a rigid alias. This is due to the fact that the proof tree looks like:

  • <T as Trait>::Assoc<U>: ~const Other proven via an item bound requires:
    • U: ~const Other

but we don't go deeper in the proof tree visitor b/c the nested obligation is not considered a where clause for the purposes of proof tree visitor. This may be fixed by #132345.


const fn works<T: ~const Trait, U: ~const Other>() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
error[E0277]: the trait bound `T: ~const Trait` is not satisfied
--> $DIR/assoc-type-const-bound-usage-fail.rs:16:5
--> $DIR/assoc-type-const-bound-usage-fail.rs:17:5
|
LL | T::Assoc::func();
| ^^^^^^^^

error[E0277]: the trait bound `T: ~const Trait` is not satisfied
--> $DIR/assoc-type-const-bound-usage-fail.rs:18:5
--> $DIR/assoc-type-const-bound-usage-fail.rs:19:5
|
LL | <T as Trait>::Assoc::func();
| ^^^^^^^^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error[E0277]: the trait bound `T: ~const Trait` is not satisfied
--> $DIR/assoc-type-const-bound-usage-fail.rs:17:5
|
LL | T::Assoc::func();
| ^^^^^^^^

error[E0277]: the trait bound `T: ~const Trait` is not satisfied
--> $DIR/assoc-type-const-bound-usage-fail.rs:19:5
|
LL | <T as Trait>::Assoc::func();
| ^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//@ compile-flags: -Znext-solver
//@ revisions: current next
//@[next] compile-flags: -Znext-solver

// Check that `~const` item bounds only hold if the parent trait is `~const`.
// i.e. check that we validate the const conditions for the associated type
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
error[E0277]: the trait bound `NonConstAdd: ~const Add` is not satisfied
--> $DIR/assoc-type.rs:36:16
--> $DIR/assoc-type.rs:37:16
|
LL | type Bar = NonConstAdd;
| ^^^^^^^^^^^
|
note: required by a bound in `Foo::Bar`
--> $DIR/assoc-type.rs:32:15
--> $DIR/assoc-type.rs:33:15
|
LL | type Bar: ~const Add;
| ^^^^^^ required by this bound in `Foo::Bar`
Expand Down
15 changes: 15 additions & 0 deletions tests/ui/traits/const-traits/assoc-type.next.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error[E0277]: the trait bound `NonConstAdd: ~const Add` is not satisfied
--> $DIR/assoc-type.rs:37:16
|
LL | type Bar = NonConstAdd;
| ^^^^^^^^^^^
|
note: required by a bound in `Foo::Bar`
--> $DIR/assoc-type.rs:33:15
|
LL | type Bar: ~const Add;
| ^^^^^^ required by this bound in `Foo::Bar`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
3 changes: 2 additions & 1 deletion tests/ui/traits/const-traits/assoc-type.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//@ compile-flags: -Znext-solver
//@ revisions: current next
//@[next] compile-flags: -Znext-solver

#![feature(const_trait_impl)]

Expand Down
Loading