Skip to content

Commit c0ae62e

Browse files
committed
Require ~const qualifier on trait bounds in specializing impls if present in base impl.
1 parent d492b9b commit c0ae62e

8 files changed

+137
-24
lines changed

compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs

+43-16
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ fn check_predicates<'tcx>(
391391
);
392392

393393
for (predicate, span) in impl1_predicates {
394-
if !impl2_predicates.iter().any(|pred2| trait_predicates_eq(predicate, *pred2)) {
394+
if !impl2_predicates.iter().any(|pred2| trait_predicates_eq(tcx, predicate, *pred2, span)) {
395395
check_specialization_on(tcx, predicate, span)
396396
}
397397
}
@@ -400,8 +400,8 @@ fn check_predicates<'tcx>(
400400
/// Checks if some predicate on the specializing impl (`predicate1`) is the same
401401
/// as some predicate on the base impl (`predicate2`).
402402
///
403-
/// This is slightly more complicated than simple syntactic equivalence, since
404-
/// we want to equate `T: Tr` with `T: ~const Tr` so this can work:
403+
/// This basically just checks syntactic equivalence, but is a little more
404+
/// forgiving since we want to equate `T: Tr` with `T: ~const Tr` so this can work:
405405
///
406406
/// ```ignore (illustrative)
407407
/// #[rustc_specialization_trait]
@@ -410,27 +410,54 @@ fn check_predicates<'tcx>(
410410
/// impl<T: Bound> Tr for T { }
411411
/// impl<T: ~const Bound + Specialize> const Tr for T { }
412412
/// ```
413+
///
414+
/// However, we *don't* want to allow the reverse, i.e., when the bound on the
415+
/// specializing impl is not as const as the bound on the base impl:
416+
///
417+
/// ```ignore (illustrative)
418+
/// impl<T: ~const Bound> const Tr for T { }
419+
/// impl<T: Bound + Specialize> const Tr for T { } // should be T: ~const Bound
420+
/// ```
421+
///
422+
/// So we make that check in this function and try to raise a helpful error message.
413423
fn trait_predicates_eq<'tcx>(
424+
tcx: TyCtxt<'tcx>,
414425
predicate1: ty::Predicate<'tcx>,
415426
predicate2: ty::Predicate<'tcx>,
427+
span: Span,
416428
) -> bool {
417-
let predicate_kind_without_constness = |kind: ty::PredicateKind<'tcx>| match kind {
418-
ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity }) => {
419-
ty::PredicateKind::Trait(ty::TraitPredicate {
420-
trait_ref,
421-
constness: ty::BoundConstness::NotConst,
422-
polarity,
423-
})
429+
let pred1_kind = predicate1.kind().no_bound_vars();
430+
let pred2_kind = predicate2.kind().no_bound_vars();
431+
let (trait_pred1, trait_pred2) = match (pred1_kind, pred2_kind) {
432+
(Some(ty::PredicateKind::Trait(pred1)), Some(ty::PredicateKind::Trait(pred2))) => {
433+
(pred1, pred2)
424434
}
425-
_ => kind,
435+
// Just use plain syntactic equivalence if either of the predicates aren't
436+
// trait predicates or have bound vars.
437+
_ => return pred1_kind == pred2_kind,
438+
};
439+
440+
let predicates_equal_modulo_constness = {
441+
let pred1_unconsted =
442+
ty::TraitPredicate { constness: ty::BoundConstness::NotConst, ..trait_pred1 };
443+
let pred2_unconsted =
444+
ty::TraitPredicate { constness: ty::BoundConstness::NotConst, ..trait_pred2 };
445+
pred1_unconsted == pred2_unconsted
426446
};
427447

428-
// We rely on `check_constness` above to ensure that pred1 is const if pred2
429-
// is const.
430-
let pred1_kind_not_const = predicate1.kind().map_bound(predicate_kind_without_constness);
431-
let pred2_kind_not_const = predicate2.kind().map_bound(predicate_kind_without_constness);
448+
if !predicates_equal_modulo_constness {
449+
return false;
450+
}
451+
452+
// Check that the predicate on the specializing impl is at least as const as
453+
// the one on the base.
454+
if trait_pred2.constness == ty::BoundConstness::ConstIfConst
455+
&& trait_pred1.constness == ty::BoundConstness::NotConst
456+
{
457+
tcx.sess.struct_span_err(span, "missing `~const` qualifier").emit();
458+
}
432459

433-
pred1_kind_not_const == pred2_kind_not_const
460+
true
434461
}
435462

436463
#[instrument(level = "debug", skip(tcx))]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Tests that trait bounds on specializing trait impls must be `~const` if the
2+
// same bound is present on the default impl and is `~const` there.
3+
4+
#![feature(const_trait_impl)]
5+
#![feature(rustc_attrs)]
6+
#![feature(min_specialization)]
7+
8+
#[rustc_specialization_trait]
9+
trait Specialize {}
10+
11+
#[const_trait]
12+
trait Foo {}
13+
14+
#[const_trait]
15+
trait Bar {}
16+
17+
// bgr360: I was only able to exercise the code path that raises the
18+
// "missing ~const qualifier" error by making this base impl non-const, even
19+
// though that doesn't really make sense to do. As seen below, if the base impl
20+
// is made const, rustc fails earlier with an overlapping impl failure.
21+
impl<T> Bar for T
22+
where
23+
T: ~const Foo,
24+
{}
25+
26+
impl<T> Bar for T
27+
where
28+
T: Foo, //~ ERROR missing `~const` qualifier
29+
T: Specialize,
30+
{}
31+
32+
#[const_trait]
33+
trait Baz {}
34+
35+
impl<T> const Baz for T
36+
where
37+
T: ~const Foo,
38+
{}
39+
40+
impl<T> const Baz for T //~ ERROR conflicting implementations of trait `Baz`
41+
where
42+
T: Foo,
43+
T: Specialize,
44+
{}
45+
46+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error: missing `~const` qualifier
2+
--> $DIR/const-default-bound-non-const-specialized-bound.rs:28:8
3+
|
4+
LL | T: Foo,
5+
| ^^^
6+
7+
error[E0119]: conflicting implementations of trait `Baz`
8+
--> $DIR/const-default-bound-non-const-specialized-bound.rs:40:1
9+
|
10+
LL | impl<T> const Baz for T
11+
| ----------------------- first implementation here
12+
...
13+
LL | impl<T> const Baz for T
14+
| ^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation
15+
16+
error: aborting due to 2 previous errors
17+
18+
For more information about this error, try `rustc --explain E0119`.

src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.rs renamed to src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-impl-non-const-specialized-impl.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// Tests that a const default trait impl cannot be specialized by a non-const
2-
// trait impl.
1+
// Tests that specializing trait impls must be at least as const as the default impl.
32

43
#![feature(const_trait_impl)]
54
#![feature(min_specialization)]

src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.stderr renamed to src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-impl-non-const-specialized-impl.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: cannot specialize on const impl with non-const impl
2-
--> $DIR/const-default-non-const-specialized.rs:20:1
2+
--> $DIR/const-default-impl-non-const-specialized-impl.rs:19:1
33
|
44
LL | impl Value for FortyTwo {
55
| ^^^^^^^^^^^^^^^^^^^^^^^

src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95187-same-trait-bound-different-constness.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
// Tests that `T: Foo` and `T: ~const Foo` are treated as equivalent for the
2-
// purposes of min_specialization.
1+
// Tests that `T: ~const Foo` in a specializing impl is treated as equivalent to
2+
// `T: Foo` in the default impl for the purposes of specialization (i.e., it
3+
// does not think that the user is attempting to specialize on trait `Foo`).
34

45
// check-pass
56

@@ -27,4 +28,18 @@ where
2728
T: Specialize,
2829
{}
2930

31+
#[const_trait]
32+
trait Baz {}
33+
34+
impl<T> const Baz for T
35+
where
36+
T: Foo,
37+
{}
38+
39+
impl<T> const Baz for T
40+
where
41+
T: ~const Foo,
42+
T: Specialize,
43+
{}
44+
3045
fn main() {}

src/test/ui/rfc-2632-const-trait-impl/specializing-constness.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ impl<T: ~const Default> const A for T {
1717
}
1818
}
1919

20-
impl<T: Default + Sup> A for T { //~ ERROR: cannot specialize
20+
impl<T: Default + Sup> A for T {
21+
//~^ ERROR: cannot specialize
22+
//~| ERROR: missing `~const` qualifier
2123
fn a() -> u32 {
2224
3
2325
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
error: cannot specialize on trait `Default`
1+
error: cannot specialize on const impl with non-const impl
2+
--> $DIR/specializing-constness.rs:20:1
3+
|
4+
LL | impl<T: Default + Sup> A for T {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: missing `~const` qualifier
28
--> $DIR/specializing-constness.rs:20:9
39
|
410
LL | impl<T: Default + Sup> A for T {
511
| ^^^^^^^
612

7-
error: aborting due to previous error
13+
error: aborting due to 2 previous errors
814

0 commit comments

Comments
 (0)