Auto merge of #68970 - matthewjasper:min-spec, r=nikomatsakis
Implement a feature for a sound specialization subset
This implements a new feature (`min_specialization`) that restricts specialization to a subset that is reasonable for the standard library to use.
The plan is to then:
* Update `libcore` and `liballoc` to compile with `min_specialization`.
* Add a lint to forbid use of `feature(specialization)` (and other unsound, type system extending features) in the standard library.
* Fix the soundness issues around `specialization`.
* Remove `min_specialization`
The rest of this is an overview from a comment in this PR
## Basic approach
To enforce this requirement on specializations we take the following approach:
1. Match up the substs for `impl2` so that the implemented trait and self-type match those for `impl1`.
2. Check for any direct use of `'static` in the substs of `impl2`.
3. Check that all of the generic parameters of `impl1` occur at most once in the *unconstrained* substs for `impl2`. A parameter is constrained if its value is completely determined by an associated type projection predicate.
4. Check that all predicates on `impl1` also exist on `impl2` (after matching substs).
## Example
Suppose we have the following always applicable impl:
```rust
impl<T> SpecExtend<T> for std::vec::IntoIter<T> { /* specialized impl */ }
impl<T, I: Iterator<Item=T>> SpecExtend<T> for I { /* default impl */ }
```
We get that the subst for `impl2` are `[T, std::vec::IntoIter<T>]`. `T` is constrained to be `<I as Iterator>::Item`, so we check only `std::vec::IntoIter<T>` for repeated parameters, which it doesn't have. The predicates of `impl1` are only `T: Sized`, which is also a predicate of impl2`. So this specialization is sound.
## Extensions
Unfortunately not all specializations in the standard library are allowed by this. So there are two extensions to these rules that allow specializing on some traits.
### rustc_specialization_trait
If a trait is always applicable, then it's sound to specialize on it. We check trait is always applicable in the same way as impls, except that step 4 is now "all predicates on `impl1` are always applicable". We require that `specialization` or `min_specialization` is enabled to implement these traits.
### rustc_specialization_marker
There are also some specialization on traits with no methods, including the `FusedIterator` trait which is advertised as allowing optimizations. We allow marking marker traits with an unstable attribute that means we ignore them in point 3 of the checks above. This is unsound but we allow it in the short term because it can't cause use after frees with purely safe code in the same way as specializing on traits methods can.
r? @nikomatsakis
cc #31844 #67194
This commit is contained in:
commit
e24252a12c
48 changed files with 1094 additions and 72 deletions
|
|
@ -0,0 +1,6 @@
|
|||
#![feature(rustc_attrs)]
|
||||
|
||||
#[rustc_specialization_trait]
|
||||
pub trait SpecTrait {
|
||||
fn method(&self);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Test that associated types in trait objects are not considered to be
|
||||
// constrained.
|
||||
|
||||
#![feature(min_specialization)]
|
||||
|
||||
trait Specializable {
|
||||
fn f();
|
||||
}
|
||||
|
||||
trait B<T> {
|
||||
type Y;
|
||||
}
|
||||
|
||||
trait C {
|
||||
type Y;
|
||||
}
|
||||
|
||||
impl<A: ?Sized> Specializable for A {
|
||||
default fn f() {}
|
||||
}
|
||||
|
||||
impl<'a, T> Specializable for dyn B<T, Y = T> + 'a {
|
||||
//~^ ERROR specializing impl repeats parameter `T`
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
impl<'a, T> Specializable for dyn C<Y = (T, T)> + 'a {
|
||||
//~^ ERROR specializing impl repeats parameter `T`
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
error: specializing impl repeats parameter `T`
|
||||
--> $DIR/dyn-trait-assoc-types.rs:22:1
|
||||
|
|
||||
LL | / impl<'a, T> Specializable for dyn B<T, Y = T> + 'a {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: specializing impl repeats parameter `T`
|
||||
--> $DIR/dyn-trait-assoc-types.rs:27:1
|
||||
|
|
||||
LL | / impl<'a, T> Specializable for dyn C<Y = (T, T)> + 'a {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Check that specialization traits can't be implemented without a feature.
|
||||
|
||||
// gate-test-min_specialization
|
||||
|
||||
// aux-build:specialization-trait.rs
|
||||
|
||||
extern crate specialization_trait;
|
||||
|
||||
struct A {}
|
||||
|
||||
impl specialization_trait::SpecTrait for A {
|
||||
//~^ ERROR implementing `rustc_specialization_trait` traits is unstable
|
||||
fn method(&self) {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
error: implementing `rustc_specialization_trait` traits is unstable
|
||||
--> $DIR/impl_specialization_trait.rs:11:1
|
||||
|
|
||||
LL | impl specialization_trait::SpecTrait for A {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: add `#![feature(min_specialization)]` to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Test that specializing on the well-formed predicates of the trait and
|
||||
// self-type of an impl is allowed.
|
||||
|
||||
// check-pass
|
||||
|
||||
#![feature(min_specialization)]
|
||||
|
||||
struct OrdOnly<T: Ord>(T);
|
||||
|
||||
trait SpecTrait<U> {
|
||||
fn f();
|
||||
}
|
||||
|
||||
impl<T, U> SpecTrait<U> for T {
|
||||
default fn f() {}
|
||||
}
|
||||
|
||||
impl<T: Ord> SpecTrait<()> for OrdOnly<T> {
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
impl<T: Ord> SpecTrait<OrdOnly<T>> for () {
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
impl<T: Ord, U: Ord, V: Ord> SpecTrait<(OrdOnly<T>, OrdOnly<U>)> for &[OrdOnly<V>] {
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Test that projection bounds can't be specialized on.
|
||||
|
||||
#![feature(min_specialization)]
|
||||
|
||||
trait X {
|
||||
fn f();
|
||||
}
|
||||
trait Id {
|
||||
type This;
|
||||
}
|
||||
impl<T> Id for T {
|
||||
type This = T;
|
||||
}
|
||||
|
||||
impl<T: Id> X for T {
|
||||
default fn f() {}
|
||||
}
|
||||
|
||||
impl<I, V: Id<This = (I,)>> X for V {
|
||||
//~^ ERROR cannot specialize on
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
error: cannot specialize on `Binder(ProjectionPredicate(ProjectionTy { substs: [V], item_def_id: DefId(0:6 ~ repeated_projection_type[317d]::Id[0]::This[0]) }, (I,)))`
|
||||
--> $DIR/repeated_projection_type.rs:19:1
|
||||
|
|
||||
LL | / impl<I, V: Id<This = (I,)>> X for V {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Test that directly specializing on repeated lifetime parameters is not
|
||||
// allowed.
|
||||
|
||||
#![feature(min_specialization)]
|
||||
|
||||
trait X {
|
||||
fn f();
|
||||
}
|
||||
|
||||
impl<T> X for T {
|
||||
default fn f() {}
|
||||
}
|
||||
|
||||
impl<'a> X for (&'a u8, &'a u8) {
|
||||
//~^ ERROR specializing impl repeats parameter `'a`
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
error: specializing impl repeats parameter `'a`
|
||||
--> $DIR/repeating_lifetimes.rs:14:1
|
||||
|
|
||||
LL | / impl<'a> X for (&'a u8, &'a u8) {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Test that specializing on two type parameters being equal is not allowed.
|
||||
|
||||
#![feature(min_specialization)]
|
||||
|
||||
trait X {
|
||||
fn f();
|
||||
}
|
||||
|
||||
impl<T> X for T {
|
||||
default fn f() {}
|
||||
}
|
||||
impl<T> X for (T, T) {
|
||||
//~^ ERROR specializing impl repeats parameter `T`
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
error: specializing impl repeats parameter `T`
|
||||
--> $DIR/repeating_param.rs:12:1
|
||||
|
|
||||
LL | / impl<T> X for (T, T) {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
20
src/test/ui/specialization/min_specialization/spec-iter.rs
Normal file
20
src/test/ui/specialization/min_specialization/spec-iter.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Check that we can specialize on a concrete iterator type. This requires us
|
||||
// to consider which parameters in the parent impl are constrained.
|
||||
|
||||
// check-pass
|
||||
|
||||
#![feature(min_specialization)]
|
||||
|
||||
trait SpecFromIter<T> {
|
||||
fn f(&self);
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, I: Iterator<Item = &'a T>> SpecFromIter<T> for I {
|
||||
default fn f(&self) {}
|
||||
}
|
||||
|
||||
impl<'a, T> SpecFromIter<T> for std::slice::Iter<'a, T> {
|
||||
fn f(&self) {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Check that lifetime parameters are allowed in specializing impls.
|
||||
|
||||
// check-pass
|
||||
|
||||
#![feature(min_specialization)]
|
||||
|
||||
trait MySpecTrait {
|
||||
fn f();
|
||||
}
|
||||
|
||||
impl<T> MySpecTrait for T {
|
||||
default fn f() {}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> MySpecTrait for &'a T {
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Test that `rustc_unsafe_specialization_marker` is only allowed on marker traits.
|
||||
|
||||
#![feature(rustc_attrs)]
|
||||
|
||||
#[rustc_unsafe_specialization_marker]
|
||||
trait SpecMarker {
|
||||
fn f();
|
||||
//~^ ERROR marker traits
|
||||
}
|
||||
|
||||
#[rustc_unsafe_specialization_marker]
|
||||
trait SpecMarker2 {
|
||||
type X;
|
||||
//~^ ERROR marker traits
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
error[E0714]: marker traits cannot have associated items
|
||||
--> $DIR/specialization_marker.rs:7:5
|
||||
|
|
||||
LL | fn f();
|
||||
| ^^^^^^^
|
||||
|
||||
error[E0714]: marker traits cannot have associated items
|
||||
--> $DIR/specialization_marker.rs:13:5
|
||||
|
|
||||
LL | type X;
|
||||
| ^^^^^^^
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0714`.
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// Test that supertraits can't be assumed in impls of
|
||||
// `rustc_specialization_trait`, as such impls would
|
||||
// allow specializing on the supertrait.
|
||||
|
||||
#![feature(min_specialization)]
|
||||
#![feature(rustc_attrs)]
|
||||
|
||||
#[rustc_specialization_trait]
|
||||
trait SpecMarker: Default {
|
||||
fn f();
|
||||
}
|
||||
|
||||
impl<T: Default> SpecMarker for T {
|
||||
//~^ ERROR cannot specialize
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
error: cannot specialize on trait `std::default::Default`
|
||||
--> $DIR/specialization_super_trait.rs:13:1
|
||||
|
|
||||
LL | / impl<T: Default> SpecMarker for T {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Test that `rustc_specialization_trait` requires always applicable impls.
|
||||
|
||||
#![feature(min_specialization)]
|
||||
#![feature(rustc_attrs)]
|
||||
|
||||
#[rustc_specialization_trait]
|
||||
trait SpecMarker {
|
||||
fn f();
|
||||
}
|
||||
|
||||
impl SpecMarker for &'static u8 {
|
||||
//~^ ERROR cannot specialize
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
impl<T> SpecMarker for (T, T) {
|
||||
//~^ ERROR specializing impl
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
impl<T: Clone> SpecMarker for [T] {
|
||||
//~^ ERROR cannot specialize
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
error: cannot specialize on `'static` lifetime
|
||||
--> $DIR/specialization_trait.rs:11:1
|
||||
|
|
||||
LL | / impl SpecMarker for &'static u8 {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: specializing impl repeats parameter `T`
|
||||
--> $DIR/specialization_trait.rs:16:1
|
||||
|
|
||||
LL | / impl<T> SpecMarker for (T, T) {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: cannot specialize on trait `std::clone::Clone`
|
||||
--> $DIR/specialization_trait.rs:21:1
|
||||
|
|
||||
LL | / impl<T: Clone> SpecMarker for [T] {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Test that specializing on a `rustc_unsafe_specialization_marker` trait is
|
||||
// allowed.
|
||||
|
||||
// check-pass
|
||||
|
||||
#![feature(min_specialization)]
|
||||
#![feature(rustc_attrs)]
|
||||
|
||||
#[rustc_unsafe_specialization_marker]
|
||||
trait SpecMarker {}
|
||||
|
||||
trait X {
|
||||
fn f();
|
||||
}
|
||||
|
||||
impl<T> X for T {
|
||||
default fn f() {}
|
||||
}
|
||||
|
||||
impl<T: SpecMarker> X for T {
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Test that specializing on a `rustc_specialization_trait` trait is allowed.
|
||||
|
||||
// check-pass
|
||||
|
||||
#![feature(min_specialization)]
|
||||
#![feature(rustc_attrs)]
|
||||
|
||||
#[rustc_specialization_trait]
|
||||
trait SpecTrait {
|
||||
fn g(&self);
|
||||
}
|
||||
|
||||
trait X {
|
||||
fn f(&self);
|
||||
}
|
||||
|
||||
impl<T> X for T {
|
||||
default fn f(&self) {}
|
||||
}
|
||||
|
||||
impl<T: SpecTrait> X for T {
|
||||
fn f(&self) {
|
||||
self.g();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// Test that directly specializing on `'static` is not allowed.
|
||||
|
||||
#![feature(min_specialization)]
|
||||
|
||||
trait X {
|
||||
fn f();
|
||||
}
|
||||
|
||||
impl<T> X for &'_ T {
|
||||
default fn f() {}
|
||||
}
|
||||
|
||||
impl X for &'static u8 {
|
||||
//~^ ERROR cannot specialize on `'static` lifetime
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
error: cannot specialize on `'static` lifetime
|
||||
--> $DIR/specialize_on_static.rs:13:1
|
||||
|
|
||||
LL | / impl X for &'static u8 {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Test that specializing on a trait is not allowed in general.
|
||||
|
||||
#![feature(min_specialization)]
|
||||
|
||||
trait SpecMarker {}
|
||||
|
||||
trait X {
|
||||
fn f();
|
||||
}
|
||||
|
||||
impl<T> X for T {
|
||||
default fn f() {}
|
||||
}
|
||||
|
||||
impl<T: SpecMarker> X for T {
|
||||
//~^ ERROR cannot specialize on trait `SpecMarker`
|
||||
fn f() {}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
error: cannot specialize on trait `SpecMarker`
|
||||
--> $DIR/specialize_on_trait.rs:15:1
|
||||
|
|
||||
LL | / impl<T: SpecMarker> X for T {
|
||||
LL | |
|
||||
LL | | fn f() {}
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue