Skip to content

Commit 946bf54

Browse files
committed
Allow specialized const trait impls.
Fixes rust-lang#95186. Fixes rust-lang#95187.
1 parent 4b133a7 commit 946bf54

9 files changed

+270
-24
lines changed

compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs

+46-24
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ use rustc_middle::ty::trait_def::TraitSpecializationKind;
7777
use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
7878
use rustc_span::Span;
7979
use rustc_trait_selection::traits::{self, translate_substs, wf};
80+
use tracing::instrument;
8081

8182
pub(super) fn check_min_specialization(tcx: TyCtxt<'_>, impl_def_id: DefId, span: Span) {
8283
if let Some(node) = parent_specialization_node(tcx, impl_def_id) {
@@ -102,6 +103,7 @@ fn parent_specialization_node(tcx: TyCtxt<'_>, impl1_def_id: DefId) -> Option<No
102103
}
103104

104105
/// Check that `impl1` is a sound specialization
106+
#[instrument(level = "debug", skip(infcx))]
105107
fn check_always_applicable(
106108
infcx: &InferCtxt<'_, '_>,
107109
impl1_def_id: DefId,
@@ -112,10 +114,8 @@ fn check_always_applicable(
112114
get_impl_substs(infcx, impl1_def_id, impl2_node, span)
113115
{
114116
let impl2_def_id = impl2_node.def_id();
115-
debug!(
116-
"check_always_applicable(\nimpl1_def_id={:?},\nimpl2_def_id={:?},\nimpl2_substs={:?}\n)",
117-
impl1_def_id, impl2_def_id, impl2_substs
118-
);
117+
debug!("impl2_def_id={impl2_def_id:?}");
118+
debug!("impl2_substs={impl2_substs:?}");
119119

120120
let tcx = infcx.tcx;
121121

@@ -278,10 +278,10 @@ fn check_static_lifetimes<'tcx>(
278278
/// * global (not reference any parameters)
279279
/// * `T: Tr` predicate where `Tr` is an always-applicable trait
280280
/// * on the base `impl impl2`
281-
/// * Currently this check is done using syntactic equality, which is
282-
/// conservative but generally sufficient.
281+
/// * This check is done using the `trait_predicates_eq` function below.
283282
/// * a well-formed predicate of a type argument of the trait being implemented,
284283
/// including the `Self`-type.
284+
#[instrument(level = "debug", skip(infcx))]
285285
fn check_predicates<'tcx>(
286286
infcx: &InferCtxt<'_, 'tcx>,
287287
impl1_def_id: LocalDefId,
@@ -313,10 +313,8 @@ fn check_predicates<'tcx>(
313313
.map(|obligation| obligation.predicate)
314314
.collect()
315315
};
316-
debug!(
317-
"check_always_applicable(\nimpl1_predicates={:?},\nimpl2_predicates={:?}\n)",
318-
impl1_predicates, impl2_predicates,
319-
);
316+
debug!("impl1_predicates={impl1_predicates:?}");
317+
debug!("impl2_predicates={impl2_predicates:?}");
320318

321319
// Since impls of always applicable traits don't get to assume anything, we
322320
// can also assume their supertraits apply.
@@ -362,25 +360,52 @@ fn check_predicates<'tcx>(
362360
);
363361

364362
for predicate in impl1_predicates {
365-
if !impl2_predicates.contains(&predicate) {
363+
if !impl2_predicates.iter().any(|pred2| trait_predicates_eq(predicate, *pred2)) {
366364
check_specialization_on(tcx, predicate, span)
367365
}
368366
}
369367
}
370368

369+
/// Checks whether two predicates are the same for the purposes of specialization.
370+
///
371+
/// This is slightly more complicated than simple syntactic equivalence, since
372+
/// we want to equate `T: Tr` with `T: ~const Tr` so this can work:
373+
///
374+
/// #[rustc_specialization_trait]
375+
/// trait Specialize { }
376+
///
377+
/// impl<T: ~const Bound> const Tr for T { }
378+
/// impl<T: Bound + Specialize> Tr for T { }
379+
fn trait_predicates_eq<'tcx>(
380+
predicate1: ty::Predicate<'tcx>,
381+
predicate2: ty::Predicate<'tcx>,
382+
) -> bool {
383+
let predicate_kind_without_constness = |kind: ty::PredicateKind<'tcx>| match kind {
384+
ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity }) => {
385+
ty::PredicateKind::Trait(ty::TraitPredicate {
386+
trait_ref,
387+
constness: ty::BoundConstness::NotConst,
388+
polarity,
389+
})
390+
}
391+
_ => kind,
392+
};
393+
394+
let pred1_kind_not_const = predicate1.kind().map_bound(predicate_kind_without_constness);
395+
let pred2_kind_not_const = predicate2.kind().map_bound(predicate_kind_without_constness);
396+
397+
pred1_kind_not_const == pred2_kind_not_const
398+
}
399+
400+
#[instrument(level = "debug", skip(tcx))]
371401
fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tcx>, span: Span) {
372-
debug!("can_specialize_on(predicate = {:?})", predicate);
373402
match predicate.kind().skip_binder() {
374403
// Global predicates are either always true or always false, so we
375404
// are fine to specialize on.
376405
_ if predicate.is_global() => (),
377406
// We allow specializing on explicitly marked traits with no associated
378407
// items.
379-
ty::PredicateKind::Trait(ty::TraitPredicate {
380-
trait_ref,
381-
constness: ty::BoundConstness::NotConst,
382-
polarity: _,
383-
}) => {
408+
ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity: _ }) => {
384409
if !matches!(
385410
trait_predicate_kind(tcx, predicate),
386411
Some(TraitSpecializationKind::Marker)
@@ -409,13 +434,10 @@ fn trait_predicate_kind<'tcx>(
409434
predicate: ty::Predicate<'tcx>,
410435
) -> Option<TraitSpecializationKind> {
411436
match predicate.kind().skip_binder() {
412-
ty::PredicateKind::Trait(ty::TraitPredicate {
413-
trait_ref,
414-
constness: ty::BoundConstness::NotConst,
415-
polarity: _,
416-
}) => Some(tcx.trait_def(trait_ref.def_id).specialization_kind),
417-
ty::PredicateKind::Trait(_)
418-
| ty::PredicateKind::RegionOutlives(_)
437+
ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity: _ }) => {
438+
Some(tcx.trait_def(trait_ref.def_id).specialization_kind)
439+
}
440+
ty::PredicateKind::RegionOutlives(_)
419441
| ty::PredicateKind::TypeOutlives(_)
420442
| ty::PredicateKind::Projection(_)
421443
| ty::PredicateKind::WellFormed(_)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Tests that a const default trait impl can be specialized by another const
2+
// trait impl and that the specializing impl will be used during const-eval.
3+
4+
// run-pass
5+
6+
#![feature(const_trait_impl)]
7+
#![feature(min_specialization)]
8+
9+
trait Value {
10+
fn value() -> u32;
11+
}
12+
13+
const fn get_value<T: ~const Value>() -> u32 {
14+
T::value()
15+
}
16+
17+
impl<T> const Value for T {
18+
default fn value() -> u32 {
19+
0
20+
}
21+
}
22+
23+
struct FortyTwo;
24+
25+
impl const Value for FortyTwo {
26+
fn value() -> u32 {
27+
42
28+
}
29+
}
30+
31+
const ZERO: u32 = get_value::<()>();
32+
33+
const FORTY_TWO: u32 = get_value::<FortyTwo>();
34+
35+
fn main() {
36+
assert_eq!(ZERO, 0);
37+
assert_eq!(FORTY_TWO, 42);
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Tests that a const default trait impl can be specialized by a non-const trait
2+
// impl, but that the specializing impl cannot be used in a const context.
3+
4+
#![feature(const_trait_impl)]
5+
#![feature(min_specialization)]
6+
7+
trait Value {
8+
fn value() -> u32;
9+
}
10+
11+
const fn get_value<T: ~const Value>() -> u32 {
12+
// Ideally this error would show up at the call to `get_value`, not here.
13+
T::value()
14+
//~^ ERROR any use of this value will cause an error
15+
//~| WARNING this was previously accepted
16+
}
17+
18+
impl<T> const Value for T {
19+
default fn value() -> u32 {
20+
0
21+
}
22+
}
23+
24+
struct FortyTwo;
25+
26+
impl Value for FortyTwo {
27+
fn value() -> u32 {
28+
println!("You can't do that (constly)");
29+
42
30+
}
31+
}
32+
33+
const ZERO: u32 = get_value::<FortyTwo>();
34+
35+
const FORTY_TWO: u32 = get_value::<FortyTwo>();
36+
37+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: any use of this value will cause an error
2+
--> $DIR/const-default-non-const-specialized.rs:13:5
3+
|
4+
LL | T::value()
5+
| ^^^^^^^^^^
6+
| |
7+
| calling non-const function `<FortyTwo as Value>::value`
8+
| inside `get_value::<FortyTwo>` at $DIR/const-default-non-const-specialized.rs:13:5
9+
| inside `FORTY_TWO` at $DIR/const-default-non-const-specialized.rs:34:24
10+
...
11+
LL | const FORTY_TWO: u32 = get_value::<FortyTwo>();
12+
| -----------------------------------------------
13+
|
14+
= note: `#[deny(const_err)]` on by default
15+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
16+
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
17+
18+
error: aborting due to previous error
19+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// check-pass
2+
3+
#![feature(const_trait_impl)]
4+
#![feature(min_specialization)]
5+
6+
trait Foo {
7+
fn foo();
8+
}
9+
10+
impl const Foo for u32 {
11+
default fn foo() {}
12+
}
13+
14+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Tests that `~const` trait bounds can be used to specialize const trait impls.
2+
3+
// check-pass
4+
5+
#![feature(const_trait_impl)]
6+
#![feature(rustc_attrs)]
7+
#![feature(min_specialization)]
8+
9+
#[rustc_specialization_trait]
10+
trait Specialize {}
11+
12+
trait Foo {}
13+
14+
impl<T> const Foo for T {}
15+
16+
impl<T> const Foo for T
17+
where
18+
T: ~const Specialize,
19+
{}
20+
21+
trait Bar {}
22+
23+
impl<T> const Bar for T
24+
where
25+
T: ~const Foo,
26+
{}
27+
28+
impl<T> const Bar for T
29+
where
30+
T: ~const Foo,
31+
T: ~const Specialize,
32+
{}
33+
34+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Tests that `T: ~const Foo` and `T: Foo` are treated as equivalent for the
2+
// purposes of min_specialization.
3+
4+
// check-pass
5+
6+
#![feature(rustc_attrs)]
7+
#![feature(min_specialization)]
8+
#![feature(const_trait_impl)]
9+
10+
#[rustc_specialization_trait]
11+
trait Specialize {}
12+
13+
trait Foo {}
14+
15+
trait Bar {}
16+
17+
impl<T> const Bar for T
18+
where
19+
T: ~const Foo,
20+
{}
21+
22+
impl<T> Bar for T
23+
where
24+
T: Foo,
25+
T: Specialize,
26+
{}
27+
28+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Tests that a non-const default impl can be specialized by a const trait impl,
2+
// but that the default impl cannot be used in a const context.
3+
4+
#![feature(const_trait_impl)]
5+
#![feature(min_specialization)]
6+
7+
trait Value {
8+
fn value() -> u32;
9+
}
10+
11+
const fn get_value<T: ~const Value>() -> u32 {
12+
T::value()
13+
}
14+
15+
impl<T> Value for T {
16+
default fn value() -> u32 {
17+
println!("You can't do that (constly)");
18+
0
19+
}
20+
}
21+
22+
struct FortyTwo;
23+
24+
impl const Value for FortyTwo {
25+
fn value() -> u32 {
26+
42
27+
}
28+
}
29+
30+
const ZERO: u32 = get_value::<()>(); //~ ERROR the trait bound `(): ~const Value` is not satisfied
31+
32+
const FORTY_TWO: u32 = get_value::<FortyTwo>();
33+
34+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0277]: the trait bound `(): ~const Value` is not satisfied
2+
--> $DIR/non-const-default-const-specialized.rs:30:31
3+
|
4+
LL | const ZERO: u32 = get_value::<()>();
5+
| ^^ the trait `~const Value` is not implemented for `()`
6+
|
7+
note: the trait `Value` is implemented for `()`, but that implementation is not `const`
8+
--> $DIR/non-const-default-const-specialized.rs:30:31
9+
|
10+
LL | const ZERO: u32 = get_value::<()>();
11+
| ^^
12+
note: required by a bound in `get_value`
13+
--> $DIR/non-const-default-const-specialized.rs:11:23
14+
|
15+
LL | const fn get_value<T: ~const Value>() -> u32 {
16+
| ^^^^^^^^^^^^ required by this bound in `get_value`
17+
18+
error: aborting due to previous error
19+
20+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)