Skip to content

Commit

Permalink
Allow specialized const trait impls.
Browse files Browse the repository at this point in the history
  • Loading branch information
BGR360 committed Mar 25, 2022
1 parent 4b133a7 commit 946bf54
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 24 deletions.
70 changes: 46 additions & 24 deletions compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ use rustc_middle::ty::trait_def::TraitSpecializationKind;
use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
use rustc_span::Span;
use rustc_trait_selection::traits::{self, translate_substs, wf};
use tracing::instrument;

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

/// Check that `impl1` is a sound specialization
#[instrument(level = "debug", skip(infcx))]
fn check_always_applicable(
infcx: &InferCtxt<'_, '_>,
impl1_def_id: DefId,
Expand All @@ -112,10 +114,8 @@ fn check_always_applicable(
get_impl_substs(infcx, impl1_def_id, impl2_node, span)
{
let impl2_def_id = impl2_node.def_id();
debug!(
"check_always_applicable(\nimpl1_def_id={:?},\nimpl2_def_id={:?},\nimpl2_substs={:?}\n)",
impl1_def_id, impl2_def_id, impl2_substs
);
debug!("impl2_def_id={impl2_def_id:?}");
debug!("impl2_substs={impl2_substs:?}");

let tcx = infcx.tcx;

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

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

for predicate in impl1_predicates {
if !impl2_predicates.contains(&predicate) {
if !impl2_predicates.iter().any(|pred2| trait_predicates_eq(predicate, *pred2)) {
check_specialization_on(tcx, predicate, span)
}
}
}

/// Checks whether two predicates are the same for the purposes of specialization.
///
/// This is slightly more complicated than simple syntactic equivalence, since
/// we want to equate `T: Tr` with `T: ~const Tr` so this can work:
///
/// #[rustc_specialization_trait]
/// trait Specialize { }
///
/// impl<T: ~const Bound> const Tr for T { }
/// impl<T: Bound + Specialize> Tr for T { }
fn trait_predicates_eq<'tcx>(
predicate1: ty::Predicate<'tcx>,
predicate2: ty::Predicate<'tcx>,
) -> bool {
let predicate_kind_without_constness = |kind: ty::PredicateKind<'tcx>| match kind {
ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity }) => {
ty::PredicateKind::Trait(ty::TraitPredicate {
trait_ref,
constness: ty::BoundConstness::NotConst,
polarity,
})
}
_ => kind,
};

let pred1_kind_not_const = predicate1.kind().map_bound(predicate_kind_without_constness);
let pred2_kind_not_const = predicate2.kind().map_bound(predicate_kind_without_constness);

pred1_kind_not_const == pred2_kind_not_const
}

#[instrument(level = "debug", skip(tcx))]
fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tcx>, span: Span) {
debug!("can_specialize_on(predicate = {:?})", predicate);
match predicate.kind().skip_binder() {
// Global predicates are either always true or always false, so we
// are fine to specialize on.
_ if predicate.is_global() => (),
// We allow specializing on explicitly marked traits with no associated
// items.
ty::PredicateKind::Trait(ty::TraitPredicate {
trait_ref,
constness: ty::BoundConstness::NotConst,
polarity: _,
}) => {
ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity: _ }) => {
if !matches!(
trait_predicate_kind(tcx, predicate),
Some(TraitSpecializationKind::Marker)
Expand Down Expand Up @@ -409,13 +434,10 @@ fn trait_predicate_kind<'tcx>(
predicate: ty::Predicate<'tcx>,
) -> Option<TraitSpecializationKind> {
match predicate.kind().skip_binder() {
ty::PredicateKind::Trait(ty::TraitPredicate {
trait_ref,
constness: ty::BoundConstness::NotConst,
polarity: _,
}) => Some(tcx.trait_def(trait_ref.def_id).specialization_kind),
ty::PredicateKind::Trait(_)
| ty::PredicateKind::RegionOutlives(_)
ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity: _ }) => {
Some(tcx.trait_def(trait_ref.def_id).specialization_kind)
}
ty::PredicateKind::RegionOutlives(_)
| ty::PredicateKind::TypeOutlives(_)
| ty::PredicateKind::Projection(_)
| ty::PredicateKind::WellFormed(_)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Tests that a const default trait impl can be specialized by another const
// trait impl and that the specializing impl will be used during const-eval.

// run-pass

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

trait Value {
fn value() -> u32;
}

const fn get_value<T: ~const Value>() -> u32 {
T::value()
}

impl<T> const Value for T {
default fn value() -> u32 {
0
}
}

struct FortyTwo;

impl const Value for FortyTwo {
fn value() -> u32 {
42
}
}

const ZERO: u32 = get_value::<()>();

const FORTY_TWO: u32 = get_value::<FortyTwo>();

fn main() {
assert_eq!(ZERO, 0);
assert_eq!(FORTY_TWO, 42);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Tests that a const default trait impl can be specialized by a non-const trait
// impl, but that the specializing impl cannot be used in a const context.

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

trait Value {
fn value() -> u32;
}

const fn get_value<T: ~const Value>() -> u32 {
// Ideally this error would show up at the call to `get_value`, not here.
T::value()
//~^ ERROR any use of this value will cause an error
//~| WARNING this was previously accepted
}

impl<T> const Value for T {
default fn value() -> u32 {
0
}
}

struct FortyTwo;

impl Value for FortyTwo {
fn value() -> u32 {
println!("You can't do that (constly)");
42
}
}

const ZERO: u32 = get_value::<FortyTwo>();

const FORTY_TWO: u32 = get_value::<FortyTwo>();

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
error: any use of this value will cause an error
--> $DIR/const-default-non-const-specialized.rs:13:5
|
LL | T::value()
| ^^^^^^^^^^
| |
| calling non-const function `<FortyTwo as Value>::value`
| inside `get_value::<FortyTwo>` at $DIR/const-default-non-const-specialized.rs:13:5
| inside `FORTY_TWO` at $DIR/const-default-non-const-specialized.rs:34:24
...
LL | const FORTY_TWO: u32 = get_value::<FortyTwo>();
| -----------------------------------------------
|
= note: `#[deny(const_err)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>

error: aborting due to previous error

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// check-pass

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

trait Foo {
fn foo();
}

impl const Foo for u32 {
default fn foo() {}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Tests that `~const` trait bounds can be used to specialize const trait impls.

// check-pass

#![feature(const_trait_impl)]
#![feature(rustc_attrs)]
#![feature(min_specialization)]

#[rustc_specialization_trait]
trait Specialize {}

trait Foo {}

impl<T> const Foo for T {}

impl<T> const Foo for T
where
T: ~const Specialize,
{}

trait Bar {}

impl<T> const Bar for T
where
T: ~const Foo,
{}

impl<T> const Bar for T
where
T: ~const Foo,
T: ~const Specialize,
{}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Tests that `T: ~const Foo` and `T: Foo` are treated as equivalent for the
// purposes of min_specialization.

// check-pass

#![feature(rustc_attrs)]
#![feature(min_specialization)]
#![feature(const_trait_impl)]

#[rustc_specialization_trait]
trait Specialize {}

trait Foo {}

trait Bar {}

impl<T> const Bar for T
where
T: ~const Foo,
{}

impl<T> Bar for T
where
T: Foo,
T: Specialize,
{}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Tests that a non-const default impl can be specialized by a const trait impl,
// but that the default impl cannot be used in a const context.

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

trait Value {
fn value() -> u32;
}

const fn get_value<T: ~const Value>() -> u32 {
T::value()
}

impl<T> Value for T {
default fn value() -> u32 {
println!("You can't do that (constly)");
0
}
}

struct FortyTwo;

impl const Value for FortyTwo {
fn value() -> u32 {
42
}
}

const ZERO: u32 = get_value::<()>(); //~ ERROR the trait bound `(): ~const Value` is not satisfied

const FORTY_TWO: u32 = get_value::<FortyTwo>();

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error[E0277]: the trait bound `(): ~const Value` is not satisfied
--> $DIR/non-const-default-const-specialized.rs:30:31
|
LL | const ZERO: u32 = get_value::<()>();
| ^^ the trait `~const Value` is not implemented for `()`
|
note: the trait `Value` is implemented for `()`, but that implementation is not `const`
--> $DIR/non-const-default-const-specialized.rs:30:31
|
LL | const ZERO: u32 = get_value::<()>();
| ^^
note: required by a bound in `get_value`
--> $DIR/non-const-default-const-specialized.rs:11:23
|
LL | const fn get_value<T: ~const Value>() -> u32 {
| ^^^^^^^^^^^^ required by this bound in `get_value`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

0 comments on commit 946bf54

Please sign in to comment.