Skip to content

Commit cd30ccf

Browse files
authored
Rollup merge of #95292 - BGR360:const-trait-specialize, r=lcnr
Allow specialized const trait impls. Fixes #95186. Fixes #95187. I've done my best to create a comprehensive test suite for the interaction between `min_specialization` and `const_trait_impls`. I wouldn't be surprised if there are interesting cases I haven't tested, please let me know.
2 parents 742d3f0 + 94f67e6 commit cd30ccf

12 files changed

+382
-25
lines changed

compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs

+98-22
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ use crate::constrained_generic_params as cgp;
6969
use crate::errors::SubstsOnOverriddenImpl;
7070

7171
use rustc_data_structures::fx::FxHashSet;
72+
use rustc_hir as hir;
7273
use rustc_hir::def_id::{DefId, LocalDefId};
7374
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
7475
use rustc_infer::infer::TyCtxtInferExt;
@@ -80,6 +81,7 @@ use rustc_span::Span;
8081
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
8182
use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _;
8283
use rustc_trait_selection::traits::{self, translate_substs, wf, ObligationCtxt};
84+
use tracing::instrument;
8385

8486
pub(super) fn check_min_specialization(tcx: TyCtxt<'_>, impl_def_id: LocalDefId) {
8587
if let Some(node) = parent_specialization_node(tcx, impl_def_id) {
@@ -103,13 +105,11 @@ fn parent_specialization_node(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId) -> Opti
103105
}
104106

105107
/// Check that `impl1` is a sound specialization
108+
#[instrument(level = "debug", skip(tcx))]
106109
fn check_always_applicable(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId, impl2_node: Node) {
107110
if let Some((impl1_substs, impl2_substs)) = get_impl_substs(tcx, impl1_def_id, impl2_node) {
108111
let impl2_def_id = impl2_node.def_id();
109-
debug!(
110-
"check_always_applicable(\nimpl1_def_id={:?},\nimpl2_def_id={:?},\nimpl2_substs={:?}\n)",
111-
impl1_def_id, impl2_def_id, impl2_substs
112-
);
112+
debug!(?impl2_def_id, ?impl2_substs);
113113

114114
let parent_substs = if impl2_node.is_from_trait() {
115115
impl2_substs.to_vec()
@@ -118,12 +118,33 @@ fn check_always_applicable(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId, impl2_node
118118
};
119119

120120
let span = tcx.def_span(impl1_def_id);
121+
check_constness(tcx, impl1_def_id, impl2_node, span);
121122
check_static_lifetimes(tcx, &parent_substs, span);
122123
check_duplicate_params(tcx, impl1_substs, &parent_substs, span);
123124
check_predicates(tcx, impl1_def_id, impl1_substs, impl2_node, impl2_substs, span);
124125
}
125126
}
126127

128+
/// Check that the specializing impl `impl1` is at least as const as the base
129+
/// impl `impl2`
130+
fn check_constness(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId, impl2_node: Node, span: Span) {
131+
if impl2_node.is_from_trait() {
132+
// This isn't a specialization
133+
return;
134+
}
135+
136+
let impl1_constness = tcx.constness(impl1_def_id.to_def_id());
137+
let impl2_constness = tcx.constness(impl2_node.def_id());
138+
139+
if let hir::Constness::Const = impl2_constness {
140+
if let hir::Constness::NotConst = impl1_constness {
141+
tcx.sess
142+
.struct_span_err(span, "cannot specialize on const impl with non-const impl")
143+
.emit();
144+
}
145+
}
146+
}
147+
127148
/// Given a specializing impl `impl1`, and the base impl `impl2`, returns two
128149
/// substitutions `(S1, S2)` that equate their trait references. The returned
129150
/// types are expressed in terms of the generics of `impl1`.
@@ -278,15 +299,15 @@ fn check_static_lifetimes<'tcx>(
278299

279300
/// Check whether predicates on the specializing impl (`impl1`) are allowed.
280301
///
281-
/// Each predicate `P` must be:
302+
/// Each predicate `P` must be one of:
282303
///
283-
/// * global (not reference any parameters)
284-
/// * `T: Tr` predicate where `Tr` is an always-applicable trait
285-
/// * on the base `impl impl2`
286-
/// * Currently this check is done using syntactic equality, which is
287-
/// conservative but generally sufficient.
288-
/// * a well-formed predicate of a type argument of the trait being implemented,
304+
/// * Global (not reference any parameters).
305+
/// * A `T: Tr` predicate where `Tr` is an always-applicable trait.
306+
/// * Present on the base impl `impl2`.
307+
/// * This check is done using the `trait_predicates_eq` function below.
308+
/// * A well-formed predicate of a type argument of the trait being implemented,
289309
/// including the `Self`-type.
310+
#[instrument(level = "debug", skip(tcx))]
290311
fn check_predicates<'tcx>(
291312
tcx: TyCtxt<'tcx>,
292313
impl1_def_id: LocalDefId,
@@ -322,10 +343,7 @@ fn check_predicates<'tcx>(
322343
.map(|obligation| obligation.predicate)
323344
.collect()
324345
};
325-
debug!(
326-
"check_always_applicable(\nimpl1_predicates={:?},\nimpl2_predicates={:?}\n)",
327-
impl1_predicates, impl2_predicates,
328-
);
346+
debug!(?impl1_predicates, ?impl2_predicates);
329347

330348
// Since impls of always applicable traits don't get to assume anything, we
331349
// can also assume their supertraits apply.
@@ -373,25 +391,83 @@ fn check_predicates<'tcx>(
373391
);
374392

375393
for (predicate, span) in impl1_predicates {
376-
if !impl2_predicates.contains(&predicate) {
394+
if !impl2_predicates.iter().any(|pred2| trait_predicates_eq(tcx, predicate, *pred2, span)) {
377395
check_specialization_on(tcx, predicate, span)
378396
}
379397
}
380398
}
381399

400+
/// Checks if some predicate on the specializing impl (`predicate1`) is the same
401+
/// as some predicate on the base impl (`predicate2`).
402+
///
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:
405+
///
406+
/// ```ignore (illustrative)
407+
/// #[rustc_specialization_trait]
408+
/// trait Specialize { }
409+
///
410+
/// impl<T: Bound> Tr for T { }
411+
/// impl<T: ~const Bound + Specialize> const Tr for T { }
412+
/// ```
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.
423+
fn trait_predicates_eq<'tcx>(
424+
tcx: TyCtxt<'tcx>,
425+
predicate1: ty::Predicate<'tcx>,
426+
predicate2: ty::Predicate<'tcx>,
427+
span: Span,
428+
) -> bool {
429+
let pred1_kind = predicate1.kind().skip_binder();
430+
let pred2_kind = predicate2.kind().skip_binder();
431+
let (trait_pred1, trait_pred2) = match (pred1_kind, pred2_kind) {
432+
(ty::PredicateKind::Trait(pred1), ty::PredicateKind::Trait(pred2)) => (pred1, pred2),
433+
// Just use plain syntactic equivalence if either of the predicates aren't
434+
// trait predicates or have bound vars.
435+
_ => return predicate1 == predicate2,
436+
};
437+
438+
let predicates_equal_modulo_constness = {
439+
let pred1_unconsted =
440+
ty::TraitPredicate { constness: ty::BoundConstness::NotConst, ..trait_pred1 };
441+
let pred2_unconsted =
442+
ty::TraitPredicate { constness: ty::BoundConstness::NotConst, ..trait_pred2 };
443+
pred1_unconsted == pred2_unconsted
444+
};
445+
446+
if !predicates_equal_modulo_constness {
447+
return false;
448+
}
449+
450+
// Check that the predicate on the specializing impl is at least as const as
451+
// the one on the base.
452+
match (trait_pred2.constness, trait_pred1.constness) {
453+
(ty::BoundConstness::ConstIfConst, ty::BoundConstness::NotConst) => {
454+
tcx.sess.struct_span_err(span, "missing `~const` qualifier for specialization").emit();
455+
}
456+
_ => {}
457+
}
458+
459+
true
460+
}
461+
462+
#[instrument(level = "debug", skip(tcx))]
382463
fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tcx>, span: Span) {
383-
debug!("can_specialize_on(predicate = {:?})", predicate);
384464
match predicate.kind().skip_binder() {
385465
// Global predicates are either always true or always false, so we
386466
// are fine to specialize on.
387467
_ if predicate.is_global() => (),
388468
// We allow specializing on explicitly marked traits with no associated
389469
// items.
390-
ty::PredicateKind::Trait(ty::TraitPredicate {
391-
trait_ref,
392-
constness: ty::BoundConstness::NotConst,
393-
polarity: _,
394-
}) => {
470+
ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity: _ }) => {
395471
if !matches!(
396472
trait_predicate_kind(tcx, predicate),
397473
Some(TraitSpecializationKind::Marker)
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 for specialization
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`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
#[const_trait]
10+
trait Value {
11+
fn value() -> u32;
12+
}
13+
14+
const fn get_value<T: ~const Value>() -> u32 {
15+
T::value()
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 const Value for FortyTwo {
27+
fn value() -> u32 {
28+
42
29+
}
30+
}
31+
32+
const ZERO: u32 = get_value::<()>();
33+
34+
const FORTY_TWO: u32 = get_value::<FortyTwo>();
35+
36+
fn main() {
37+
assert_eq!(ZERO, 0);
38+
assert_eq!(FORTY_TWO, 42);
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Tests that specializing trait impls must be at least as const as the default impl.
2+
3+
#![feature(const_trait_impl)]
4+
#![feature(min_specialization)]
5+
6+
#[const_trait]
7+
trait Value {
8+
fn value() -> u32;
9+
}
10+
11+
impl<T> const Value for T {
12+
default fn value() -> u32 {
13+
0
14+
}
15+
}
16+
17+
struct FortyTwo;
18+
19+
impl Value for FortyTwo { //~ ERROR cannot specialize on const impl with non-const impl
20+
fn value() -> u32 {
21+
println!("You can't do that (constly)");
22+
42
23+
}
24+
}
25+
26+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: cannot specialize on const impl with non-const impl
2+
--> $DIR/const-default-impl-non-const-specialized-impl.rs:19:1
3+
|
4+
LL | impl Value for FortyTwo {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: aborting due to previous error
8+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// check-pass
2+
3+
#![feature(const_trait_impl)]
4+
#![feature(min_specialization)]
5+
6+
#[const_trait]
7+
trait Foo {
8+
fn foo();
9+
}
10+
11+
impl const Foo for u32 {
12+
default fn foo() {}
13+
}
14+
15+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
#[const_trait]
10+
#[rustc_specialization_trait]
11+
trait Specialize {}
12+
13+
#[const_trait]
14+
trait Foo {}
15+
16+
impl<T> const Foo for T {}
17+
18+
impl<T> const Foo for T
19+
where
20+
T: ~const Specialize,
21+
{}
22+
23+
#[const_trait]
24+
trait Bar {}
25+
26+
impl<T> const Bar for T
27+
where
28+
T: ~const Foo,
29+
{}
30+
31+
impl<T> const Bar for T
32+
where
33+
T: ~const Foo,
34+
T: ~const Specialize,
35+
{}
36+
37+
fn main() {}

0 commit comments

Comments
 (0)