Skip to content

handle specialization in the new trait solver #140306

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
Apr 26, 2025
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
4 changes: 4 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
self.defaultness(def_id).has_value()
}

fn impl_specializes(self, impl_def_id: Self::DefId, victim_def_id: Self::DefId) -> bool {
self.specializes((impl_def_id, victim_def_id))
}

fn impl_is_default(self, impl_def_id: DefId) -> bool {
self.defaultness(impl_def_id).is_default()
}
Expand Down
73 changes: 72 additions & 1 deletion compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use rustc_type_ir::{
};
use tracing::{debug, instrument};

use super::has_only_region_constraints;
use super::trait_goals::TraitGoalProvenVia;
use crate::delegate::SolverDelegate;
use crate::solve::inspect::ProbeKind;
Expand Down Expand Up @@ -771,6 +772,69 @@ where
}
})
}
}

pub(super) enum AllowInferenceConstraints {
Yes,
No,
}

impl<D, I> EvalCtxt<'_, D>
where
D: SolverDelegate<Interner = I>,
I: Interner,
{
/// Check whether we can ignore impl candidates due to specialization.
///
/// This is only necessary for `feature(specialization)` and seems quite ugly.
pub(super) fn filter_specialized_impls(
&mut self,
allow_inference_constraints: AllowInferenceConstraints,
candidates: &mut Vec<Candidate<I>>,
) {
match self.typing_mode() {
TypingMode::Coherence => return,
TypingMode::Analysis { .. }
| TypingMode::Borrowck { .. }
| TypingMode::PostBorrowckAnalysis { .. }
| TypingMode::PostAnalysis => {}
}

let mut i = 0;
'outer: while i < candidates.len() {
let CandidateSource::Impl(victim_def_id) = candidates[i].source else {
i += 1;
continue;
};

for (j, c) in candidates.iter().enumerate() {
if i == j {
continue;
}

let CandidateSource::Impl(other_def_id) = c.source else {
continue;
};

// See if we can toss out `victim` based on specialization.
//
// While this requires us to know *for sure* that the `lhs` impl applies
// we still use modulo regions here. This is fine as specialization currently
// assumes that specializing impls have to be always applicable, meaning that
// the only allowed region constraints may be constraints also present on the default impl.
if matches!(allow_inference_constraints, AllowInferenceConstraints::Yes)
|| has_only_region_constraints(c.result)
{
if self.cx().impl_specializes(other_def_id, victim_def_id) {
candidates.remove(i);
continue 'outer;
}
}
}

i += 1;
}
}

/// Assemble and merge candidates for goals which are related to an underlying trait
/// goal. Right now, this is normalizes-to and host effect goals.
Expand Down Expand Up @@ -857,7 +921,7 @@ where
}
}
TraitGoalProvenVia::Misc => {
let candidates =
let mut candidates =
self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);

// Prefer "orphaned" param-env normalization predicates, which are used
Expand All @@ -871,6 +935,13 @@ where
return Ok(response);
}

// We drop specialized impls to allow normalization via a final impl here. In case
// the specializing impl has different inference constraints from the specialized
// impl, proving the trait goal is already ambiguous, so we never get here. This
// means we can just ignore inference constraints and don't have to special-case
// constraining the normalized-to `term`.
self.filter_specialized_impls(AllowInferenceConstraints::Yes, &mut candidates);

let responses: Vec<_> = candidates.iter().map(|c| c.result).collect();
if let Some(response) = self.try_merge_responses(&responses) {
Ok(response)
Expand Down
10 changes: 3 additions & 7 deletions compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use rustc_type_ir::{
};
use tracing::{instrument, trace};

use super::has_only_region_constraints;
use crate::coherence;
use crate::delegate::SolverDelegate;
use crate::solve::inspect::{self, ProofTreeBuilder};
Expand Down Expand Up @@ -476,13 +477,8 @@ where
Ok(response) => response,
};

let has_changed = if !response.value.var_values.is_identity_modulo_regions()
|| !response.value.external_constraints.opaque_types.is_empty()
{
HasChanged::Yes
} else {
HasChanged::No
};
let has_changed =
if !has_only_region_constraints(response) { HasChanged::Yes } else { HasChanged::No };

let (normalization_nested_goals, certainty) =
self.instantiate_and_apply_query_response(goal.param_env, orig_values, response);
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_next_trait_solver/src/solve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ fn has_no_inference_or_external_constraints<I: Interner>(
&& normalization_nested_goals.is_empty()
}

fn has_only_region_constraints<I: Interner>(response: ty::Canonical<I, Response<I>>) -> bool {
let ExternalConstraintsData {
region_constraints: _,
ref opaque_types,
ref normalization_nested_goals,
} = *response.value.external_constraints;
response.value.var_values.is_identity_modulo_regions()
&& opaque_types.is_empty()
&& normalization_nested_goals.is_empty()
}

impl<'a, D, I> EvalCtxt<'a, D>
where
D: SolverDelegate<Interner = I>,
Expand Down
27 changes: 22 additions & 5 deletions compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,18 +213,35 @@ where
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
};

// In case the associated item is hidden due to specialization, we have to
// return ambiguity this would otherwise be incomplete, resulting in
// unsoundness during coherence (#105782).
let target_item_def_id = match ecx.fetch_eligible_assoc_item(
goal_trait_ref,
goal.predicate.def_id(),
impl_def_id,
) {
Ok(Some(target_item_def_id)) => target_item_def_id,
Ok(None) => {
return ecx
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
match ecx.typing_mode() {
// In case the associated item is hidden due to specialization, we have to
// return ambiguity this would otherwise be incomplete, resulting in
// unsoundness during coherence (#105782).
ty::TypingMode::Coherence => {
return ecx.evaluate_added_goals_and_make_canonical_response(
Certainty::AMBIGUOUS,
);
}
// Outside of coherence, we treat the associated item as rigid instead.
ty::TypingMode::Analysis { .. }
| ty::TypingMode::Borrowck { .. }
| ty::TypingMode::PostBorrowckAnalysis { .. }
| ty::TypingMode::PostAnalysis => {
ecx.structurally_instantiate_normalizes_to_term(
goal,
goal.predicate.alias,
);
return ecx
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
}
};
}
Err(guar) => return error_response(ecx, guar),
};
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_next_trait_solver/src/solve/trait_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use tracing::{instrument, trace};

use crate::delegate::SolverDelegate;
use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes};
use crate::solve::assembly::{self, AssembleCandidatesFrom, Candidate};
use crate::solve::assembly::{self, AllowInferenceConstraints, AssembleCandidatesFrom, Candidate};
use crate::solve::inspect::ProbeKind;
use crate::solve::{
BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause,
Expand Down Expand Up @@ -1338,6 +1338,8 @@ where
};
}

self.filter_specialized_impls(AllowInferenceConstraints::No, &mut candidates);

// If there are *only* global where bounds, then make sure to return that this
// is still reported as being proven-via the param-env so that rigid projections
// operate correctly. Otherwise, drop all global where-bounds before merging the
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_type_ir/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ pub trait Interner:

fn has_item_definition(self, def_id: Self::DefId) -> bool;

fn impl_specializes(self, impl_def_id: Self::DefId, victim_def_id: Self::DefId) -> bool;

fn impl_is_default(self, impl_def_id: Self::DefId) -> bool;

fn impl_trait_ref(self, impl_def_id: Self::DefId) -> ty::EarlyBinder<Self, ty::TraitRef<Self>>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/prefer-specializing-impl-over-default.rs:5: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

warning: 1 warning emitted

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/prefer-specializing-impl-over-default.rs:5: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

warning: 1 warning emitted

29 changes: 29 additions & 0 deletions tests/ui/specialization/prefer-specializing-impl-over-default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver
//@ check-pass
#![feature(specialization)]
//~^ WARN the feature `specialization` is incomplete

trait WithAssoc: 'static {
type Assoc;
}
impl<T: 'static> WithAssoc for (T,) {
type Assoc = ();
}

struct GenericArray<U: WithAssoc>(U::Assoc);

trait AbiExample {
fn example();
}
impl<U: WithAssoc> AbiExample for GenericArray<U> {
fn example() {}
}
impl<T> AbiExample for T {
default fn example() {}
}

fn main() {
let _ = GenericArray::<((),)>::example();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/specialization-default-projection.rs:1:12
--> $DIR/specialization-default-projection.rs:5:12
|
LL | #![feature(specialization)]
| ^^^^^^^^^^^^^^
Expand All @@ -9,7 +9,7 @@ LL | #![feature(specialization)]
= note: `#[warn(incomplete_features)]` on by default

error[E0308]: mismatched types
--> $DIR/specialization-default-projection.rs:21:5
--> $DIR/specialization-default-projection.rs:25:5
|
LL | fn generic<T>() -> <T as Foo>::Assoc {
| ----------------- expected `<T as Foo>::Assoc` because of return type
Expand All @@ -23,7 +23,7 @@ LL | ()
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

error[E0308]: mismatched types
--> $DIR/specialization-default-projection.rs:28:5
--> $DIR/specialization-default-projection.rs:32:5
|
LL | fn monomorphic() -> () {
| -- expected `()` because of return type
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/specialization-default-projection.rs:5: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[E0308]: mismatched types
--> $DIR/specialization-default-projection.rs:25:5
|
LL | fn generic<T>() -> <T as Foo>::Assoc {
| ----------------- expected `<T as Foo>::Assoc` because of return type
...
LL | ()
| ^^ types differ
|
= note: expected associated type `<T as Foo>::Assoc`
found unit type `()`
= help: consider constraining the associated type `<T as Foo>::Assoc` to `()` or calling a method that returns `<T as Foo>::Assoc`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

error[E0308]: mismatched types
--> $DIR/specialization-default-projection.rs:32:5
|
LL | fn monomorphic() -> () {
| -- expected `()` because of return type
...
LL | generic::<()>()
| ^^^^^^^^^^^^^^^- help: consider using a semicolon here: `;`
| |
| types differ
|
= note: expected unit type `()`
found associated type `<() as Foo>::Assoc`
= help: consider constraining the associated type `<() as Foo>::Assoc` to `()`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

error: aborting due to 2 previous errors; 1 warning emitted

For more information about this error, try `rustc --explain E0308`.
4 changes: 4 additions & 0 deletions tests/ui/specialization/specialization-default-projection.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//@ revisions: current next
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[next] compile-flags: -Znext-solver

#![feature(specialization)] //~ WARN the feature `specialization` is incomplete

// Make sure we can't project defaulted associated types
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/specialization-default-types.rs:5:12
--> $DIR/specialization-default-types.rs:9:12
|
LL | #![feature(specialization)]
| ^^^^^^^^^^^^^^
Expand All @@ -9,7 +9,7 @@ LL | #![feature(specialization)]
= note: `#[warn(incomplete_features)]` on by default

error[E0308]: mismatched types
--> $DIR/specialization-default-types.rs:15:9
--> $DIR/specialization-default-types.rs:19:9
|
LL | default type Output = Box<T>;
| ----------------------------- associated type is `default` and may be overridden
Expand All @@ -22,7 +22,7 @@ LL | Box::new(self)
found struct `Box<T>`

error[E0308]: mismatched types
--> $DIR/specialization-default-types.rs:25:5
--> $DIR/specialization-default-types.rs:29:5
|
LL | fn trouble<T>(t: T) -> Box<T> {
| ------ expected `Box<T>` because of return type
Expand Down
Loading
Loading