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:
bors 2020-03-16 20:49:26 +00:00
commit e24252a12c
48 changed files with 1094 additions and 72 deletions

View File

@ -90,6 +90,7 @@ impl<T: ?Sized> !Send for *mut T {}
ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>"
)]
#[fundamental] // for Default, for example, which requires that `[T]: !Default` be evaluatable
#[cfg_attr(not(bootstrap), rustc_specialization_trait)]
pub trait Sized {
// Empty.
}

View File

@ -26,7 +26,8 @@
#![feature(in_band_lifetimes)]
#![feature(optin_builtin_traits)]
#![feature(rustc_attrs)]
#![feature(specialization)]
#![cfg_attr(bootstrap, feature(specialization))]
#![cfg_attr(not(bootstrap), feature(min_specialization))]
#![recursion_limit = "256"]
#[unstable(feature = "proc_macro_internals", issue = "27812")]

View File

@ -4,6 +4,7 @@ use crate::ty::{self, TyCtxt};
use rustc_ast::ast::Ident;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_errors::ErrorReported;
use rustc_hir::def_id::{DefId, DefIdMap};
/// A per-trait graph of impls in specialization order. At the moment, this
@ -23,17 +24,20 @@ use rustc_hir::def_id::{DefId, DefIdMap};
/// has at most one parent.
#[derive(RustcEncodable, RustcDecodable, HashStable)]
pub struct Graph {
// All impls have a parent; the "root" impls have as their parent the `def_id`
// of the trait.
/// All impls have a parent; the "root" impls have as their parent the `def_id`
/// of the trait.
pub parent: DefIdMap<DefId>,
// The "root" impls are found by looking up the trait's def_id.
/// The "root" impls are found by looking up the trait's def_id.
pub children: DefIdMap<Children>,
/// Whether an error was emitted while constructing the graph.
pub has_errored: bool,
}
impl Graph {
pub fn new() -> Graph {
Graph { parent: Default::default(), children: Default::default() }
Graph { parent: Default::default(), children: Default::default(), has_errored: false }
}
/// The parent of a given impl, which is the `DefId` of the trait when the
@ -179,17 +183,22 @@ impl<'tcx> Ancestors<'tcx> {
}
/// Walk up the specialization ancestors of a given impl, starting with that
/// impl itself.
/// impl itself. Returns `None` if an error was reported while building the
/// specialization graph.
pub fn ancestors(
tcx: TyCtxt<'tcx>,
trait_def_id: DefId,
start_from_impl: DefId,
) -> Ancestors<'tcx> {
) -> Result<Ancestors<'tcx>, ErrorReported> {
let specialization_graph = tcx.specialization_graph_of(trait_def_id);
Ancestors {
if specialization_graph.has_errored {
Err(ErrorReported)
} else {
Ok(Ancestors {
trait_def_id,
specialization_graph,
current_source: Some(Node::Impl(start_from_impl)),
})
}
}

View File

@ -10,6 +10,7 @@ use rustc_hir::HirId;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_errors::ErrorReported;
use rustc_macros::HashStable;
use std::collections::BTreeMap;
@ -35,11 +36,33 @@ pub struct TraitDef {
/// and thus `impl`s of it are allowed to overlap.
pub is_marker: bool,
/// Used to determine whether the standard library is allowed to specialize
/// on this trait.
pub specialization_kind: TraitSpecializationKind,
/// The ICH of this trait's DefPath, cached here so it doesn't have to be
/// recomputed all the time.
pub def_path_hash: DefPathHash,
}
/// Whether this trait is treated specially by the standard library
/// specialization lint.
#[derive(HashStable, PartialEq, Clone, Copy, RustcEncodable, RustcDecodable)]
pub enum TraitSpecializationKind {
/// The default. Specializing on this trait is not allowed.
None,
/// Specializing on this trait is allowed because it doesn't have any
/// methods. For example `Sized` or `FusedIterator`.
/// Applies to traits with the `rustc_unsafe_specialization_marker`
/// attribute.
Marker,
/// Specializing on this trait is allowed because all of the impls of this
/// trait are "always applicable". Always applicable means that if
/// `X<'x>: T<'y>` for any lifetimes, then `for<'a, 'b> X<'a>: T<'b>`.
/// Applies to traits with the `rustc_specialization_trait` attribute.
AlwaysApplicable,
}
#[derive(Default)]
pub struct TraitImpls {
blanket_impls: Vec<DefId>,
@ -54,16 +77,25 @@ impl<'tcx> TraitDef {
paren_sugar: bool,
has_auto_impl: bool,
is_marker: bool,
specialization_kind: TraitSpecializationKind,
def_path_hash: DefPathHash,
) -> TraitDef {
TraitDef { def_id, unsafety, paren_sugar, has_auto_impl, is_marker, def_path_hash }
TraitDef {
def_id,
unsafety,
paren_sugar,
has_auto_impl,
is_marker,
specialization_kind,
def_path_hash,
}
}
pub fn ancestors(
&self,
tcx: TyCtxt<'tcx>,
of_impl: DefId,
) -> specialization_graph::Ancestors<'tcx> {
) -> Result<specialization_graph::Ancestors<'tcx>, ErrorReported> {
specialization_graph::ancestors(tcx, self.def_id, of_impl)
}
}

View File

@ -542,15 +542,12 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
}
fn visit_assoc_item(&mut self, i: &'a ast::AssocItem, ctxt: AssocCtxt) {
if let ast::Defaultness::Default(_) = i.kind.defaultness() {
gate_feature_post!(&self, specialization, i.span, "specialization is unstable");
}
match i.kind {
let is_fn = match i.kind {
ast::AssocItemKind::Fn(_, ref sig, _, _) => {
if let (ast::Const::Yes(_), AssocCtxt::Trait) = (sig.header.constness, ctxt) {
gate_feature_post!(&self, const_fn, i.span, "const fn is unstable");
}
true
}
ast::AssocItemKind::TyAlias(_, ref generics, _, ref ty) => {
if let (Some(_), AssocCtxt::Trait) = (ty, ctxt) {
@ -565,8 +562,19 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
self.check_impl_trait(ty);
}
self.check_gat(generics, i.span);
false
}
_ => {}
_ => false,
};
if let ast::Defaultness::Default(_) = i.kind.defaultness() {
// Limit `min_specialization` to only specializing functions.
gate_feature_fn!(
&self,
|x: &Features| x.specialization || (is_fn && x.min_specialization),
i.span,
sym::specialization,
"specialization is unstable"
);
}
visit::walk_assoc_item(self, i, ctxt)
}

View File

@ -301,6 +301,11 @@ declare_features! (
/// Allows specialization of implementations (RFC 1210).
(active, specialization, "1.7.0", Some(31844), None),
/// A minimal, sound subset of specialization intended to be used by the
/// standard library until the soundness issues with specialization
/// are fixed.
(active, min_specialization, "1.7.0", Some(31844), None),
/// Allows using `#[naked]` on functions.
(active, naked_functions, "1.9.0", Some(32408), None),

View File

@ -530,6 +530,14 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
rustc_test_marker, Normal, template!(Word),
"the `#[rustc_test_marker]` attribute is used internally to track tests",
),
rustc_attr!(
rustc_unsafe_specialization_marker, Normal, template!(Word),
"the `#[rustc_unsafe_specialization_marker]` attribute is used to check specializations"
),
rustc_attr!(
rustc_specialization_trait, Normal, template!(Word),
"the `#[rustc_specialization_trait]` attribute is used to check specializations"
),
// ==========================================================================
// Internal attributes, Testing:

View File

@ -651,6 +651,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
data.paren_sugar,
data.has_auto_impl,
data.is_marker,
data.specialization_kind,
self.def_path_table.def_path_hash(item_id),
)
}
@ -660,6 +661,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
false,
false,
false,
ty::trait_def::TraitSpecializationKind::None,
self.def_path_table.def_path_hash(item_id),
),
_ => bug!("def-index does not refer to trait or trait alias"),

View File

@ -1077,12 +1077,13 @@ impl EncodeContext<'tcx> {
let polarity = self.tcx.impl_polarity(def_id);
let parent = if let Some(trait_ref) = trait_ref {
let trait_def = self.tcx.trait_def(trait_ref.def_id);
trait_def.ancestors(self.tcx, def_id).nth(1).and_then(|node| {
trait_def.ancestors(self.tcx, def_id).ok()
.and_then(|mut an| an.nth(1).and_then(|node| {
match node {
specialization_graph::Node::Impl(parent) => Some(parent),
_ => None,
}
})
}))
} else {
None
};
@ -1114,6 +1115,7 @@ impl EncodeContext<'tcx> {
paren_sugar: trait_def.paren_sugar,
has_auto_impl: self.tcx.trait_is_auto(def_id),
is_marker: trait_def.is_marker,
specialization_kind: trait_def.specialization_kind,
};
EntryKind::Trait(self.lazy(data))

View File

@ -343,6 +343,7 @@ struct TraitData {
paren_sugar: bool,
has_auto_impl: bool,
is_marker: bool,
specialization_kind: ty::trait_def::TraitSpecializationKind,
}
#[derive(RustcEncodable, RustcDecodable)]

View File

@ -453,6 +453,7 @@ symbols! {
min_align_of,
min_const_fn,
min_const_unsafe_fn,
min_specialization,
mips_target_feature,
mmx_target_feature,
module,
@ -654,6 +655,8 @@ symbols! {
rustc_proc_macro_decls,
rustc_promotable,
rustc_regions,
rustc_unsafe_specialization_marker,
rustc_specialization_trait,
rustc_stable,
rustc_std_internal_symbol,
rustc_symbol_name,

View File

@ -21,6 +21,7 @@ use rustc::ty::fold::{TypeFoldable, TypeFolder};
use rustc::ty::subst::{InternalSubsts, Subst};
use rustc::ty::{self, ToPolyTraitRef, ToPredicate, Ty, TyCtxt, WithConstness};
use rustc_ast::ast::Ident;
use rustc_errors::ErrorReported;
use rustc_hir::def_id::DefId;
use rustc_span::symbol::sym;
use rustc_span::DUMMY_SP;
@ -1010,7 +1011,8 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
// NOTE: This should be kept in sync with the similar code in
// `rustc::ty::instance::resolve_associated_item()`.
let node_item =
assoc_ty_def(selcx, impl_data.impl_def_id, obligation.predicate.item_def_id);
assoc_ty_def(selcx, impl_data.impl_def_id, obligation.predicate.item_def_id)
.map_err(|ErrorReported| ())?;
let is_default = if node_item.node.is_from_trait() {
// If true, the impl inherited a `type Foo = Bar`
@ -1405,7 +1407,10 @@ fn confirm_impl_candidate<'cx, 'tcx>(
let trait_def_id = tcx.trait_id_of_impl(impl_def_id).unwrap();
let param_env = obligation.param_env;
let assoc_ty = assoc_ty_def(selcx, impl_def_id, assoc_item_id);
let assoc_ty = match assoc_ty_def(selcx, impl_def_id, assoc_item_id) {
Ok(assoc_ty) => assoc_ty,
Err(ErrorReported) => return Progress { ty: tcx.types.err, obligations: nested },
};
if !assoc_ty.item.defaultness.has_value() {
// This means that the impl is missing a definition for the
@ -1444,14 +1449,14 @@ fn assoc_ty_def(
selcx: &SelectionContext<'_, '_>,
impl_def_id: DefId,
assoc_ty_def_id: DefId,
) -> specialization_graph::NodeItem<ty::AssocItem> {
) -> Result<specialization_graph::NodeItem<ty::AssocItem>, ErrorReported> {
let tcx = selcx.tcx();
let assoc_ty_name = tcx.associated_item(assoc_ty_def_id).ident;
let trait_def_id = tcx.impl_trait_ref(impl_def_id).unwrap().def_id;
let trait_def = tcx.trait_def(trait_def_id);
// This function may be called while we are still building the
// specialization graph that is queried below (via TraidDef::ancestors()),
// specialization graph that is queried below (via TraitDef::ancestors()),
// so, in order to avoid unnecessary infinite recursion, we manually look
// for the associated item at the given impl.
// If there is no such item in that impl, this function will fail with a
@ -1461,17 +1466,16 @@ fn assoc_ty_def(
if matches!(item.kind, ty::AssocKind::Type | ty::AssocKind::OpaqueTy)
&& tcx.hygienic_eq(item.ident, assoc_ty_name, trait_def_id)
{
return specialization_graph::NodeItem {
return Ok(specialization_graph::NodeItem {
node: specialization_graph::Node::Impl(impl_def_id),
item: *item,
};
});
}
}
if let Some(assoc_item) =
trait_def.ancestors(tcx, impl_def_id).leaf_def(tcx, assoc_ty_name, ty::AssocKind::Type)
{
assoc_item
let ancestors = trait_def.ancestors(tcx, impl_def_id)?;
if let Some(assoc_item) = ancestors.leaf_def(tcx, assoc_ty_name, ty::AssocKind::Type) {
Ok(assoc_item)
} else {
// This is saying that neither the trait nor
// the impl contain a definition for this

View File

@ -130,7 +130,7 @@ pub fn find_associated_item<'tcx>(
let trait_def_id = tcx.trait_id_of_impl(impl_data.impl_def_id).unwrap();
let trait_def = tcx.trait_def(trait_def_id);
let ancestors = trait_def.ancestors(tcx, impl_data.impl_def_id);
if let Ok(ancestors) = trait_def.ancestors(tcx, impl_data.impl_def_id) {
match ancestors.leaf_def(tcx, item.ident, item.kind) {
Some(node_item) => {
let substs = tcx.infer_ctxt().enter(|infcx| {
@ -149,6 +149,9 @@ pub fn find_associated_item<'tcx>(
}
None => bug!("{:?} not found in {:?}", item, impl_data.impl_def_id),
}
} else {
(item.def_id, substs)
}
}
/// Is `impl1` a specialization of `impl2`?
@ -161,7 +164,9 @@ pub(super) fn specializes(tcx: TyCtxt<'_>, (impl1_def_id, impl2_def_id): (DefId,
// The feature gate should prevent introducing new specializations, but not
// taking advantage of upstream ones.
if !tcx.features().specialization && (impl1_def_id.is_local() || impl2_def_id.is_local()) {
let features = tcx.features();
let specialization_enabled = features.specialization || features.min_specialization;
if !specialization_enabled && (impl1_def_id.is_local() || impl2_def_id.is_local()) {
return false;
}
@ -380,6 +385,7 @@ pub(super) fn specialization_graph_provider(
match used_to_be_allowed {
None => {
sg.has_errored = true;
let err = struct_span_err!(tcx.sess, impl_span, E0119, "");
decorate(LintDiagnosticBuilder::new(err));
}

View File

@ -1901,8 +1901,11 @@ fn check_specialization_validity<'tcx>(
hir::ImplItemKind::TyAlias(_) => ty::AssocKind::Type,
};
let mut ancestor_impls = trait_def
.ancestors(tcx, impl_id)
let ancestors = match trait_def.ancestors(tcx, impl_id) {
Ok(ancestors) => ancestors,
Err(_) => return,
};
let mut ancestor_impls = ancestors
.skip(1)
.filter_map(|parent| {
if parent.is_from_trait() {
@ -2083,9 +2086,9 @@ fn check_impl_items_against_trait<'tcx>(
// Check for missing items from trait
let mut missing_items = Vec::new();
if let Ok(ancestors) = trait_def.ancestors(tcx, impl_id) {
for trait_item in tcx.associated_items(impl_trait_ref.def_id).in_definition_order() {
let is_implemented = trait_def
.ancestors(tcx, impl_id)
let is_implemented = ancestors
.leaf_def(tcx, trait_item.ident, trait_item.kind)
.map(|node_item| !node_item.node.is_from_trait())
.unwrap_or(false);
@ -2096,6 +2099,7 @@ fn check_impl_items_against_trait<'tcx>(
}
}
}
}
if !missing_items.is_empty() {
missing_items_err(tcx, impl_span, &missing_items, full_impl_span);

View File

@ -4,6 +4,7 @@ use crate::constrained_generic_params::{identify_constrained_generic_params, Par
use rustc::middle::lang_items;
use rustc::session::parse::feature_err;
use rustc::ty::subst::{InternalSubsts, Subst};
use rustc::ty::trait_def::TraitSpecializationKind;
use rustc::ty::{
self, AdtKind, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeFoldable, WithConstness,
};
@ -412,7 +413,9 @@ fn check_trait(tcx: TyCtxt<'_>, item: &hir::Item<'_>) {
let trait_def_id = tcx.hir().local_def_id(item.hir_id);
let trait_def = tcx.trait_def(trait_def_id);
if trait_def.is_marker {
if trait_def.is_marker
|| matches!(trait_def.specialization_kind, TraitSpecializationKind::Marker)
{
for associated_def_id in &*tcx.associated_item_def_ids(trait_def_id) {
struct_span_err!(
tcx.sess,

View File

@ -76,6 +76,22 @@ fn enforce_trait_manually_implementable(tcx: TyCtxt<'_>, impl_def_id: DefId, tra
return;
}
if let ty::trait_def::TraitSpecializationKind::AlwaysApplicable =
tcx.trait_def(trait_def_id).specialization_kind
{
if !tcx.features().specialization && !tcx.features().min_specialization {
let span = impl_header_span(tcx, impl_def_id);
tcx.sess
.struct_span_err(
span,
"implementing `rustc_specialization_trait` traits is unstable",
)
.help("add `#![feature(min_specialization)]` to the crate attributes to enable")
.emit();
return;
}
}
let trait_name = if did == li.fn_trait() {
"Fn"
} else if did == li.fn_mut_trait() {

View File

@ -1032,8 +1032,23 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::TraitDef {
}
let is_marker = tcx.has_attr(def_id, sym::marker);
let spec_kind = if tcx.has_attr(def_id, sym::rustc_unsafe_specialization_marker) {
ty::trait_def::TraitSpecializationKind::Marker
} else if tcx.has_attr(def_id, sym::rustc_specialization_trait) {
ty::trait_def::TraitSpecializationKind::AlwaysApplicable
} else {
ty::trait_def::TraitSpecializationKind::None
};
let def_path_hash = tcx.def_path_hash(def_id);
let def = ty::TraitDef::new(def_id, unsafety, paren_sugar, is_auto, is_marker, def_path_hash);
let def = ty::TraitDef::new(
def_id,
unsafety,
paren_sugar,
is_auto,
is_marker,
spec_kind,
def_path_hash,
);
tcx.arena.alloc(def)
}

View File

@ -79,10 +79,18 @@ impl<'tcx> TypeVisitor<'tcx> for ParameterCollector {
}
fn visit_const(&mut self, c: &'tcx ty::Const<'tcx>) -> bool {
if let ty::ConstKind::Param(data) = c.val {
match c.val {
ty::ConstKind::Unevaluated(..) if !self.include_nonconstraining => {
// Constant expressions are not injective
return c.ty.visit_with(self);
}
ty::ConstKind::Param(data) => {
self.parameters.push(Parameter::from(data));
}
false
_ => {}
}
c.super_visit_with(self)
}
}

View File

@ -9,6 +9,8 @@
//! fixed, but for the moment it's easier to do these checks early.
use crate::constrained_generic_params as cgp;
use min_specialization::check_min_specialization;
use rustc::ty::query::Providers;
use rustc::ty::{self, TyCtxt, TypeFoldable};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@ -16,9 +18,11 @@ use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_span::Span;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use rustc_span::Span;
mod min_specialization;
/// Checks that all the type/lifetime parameters on an impl also
/// appear in the trait ref or self type (or are constrained by a
@ -60,7 +64,9 @@ pub fn impl_wf_check(tcx: TyCtxt<'_>) {
}
fn check_mod_impl_wf(tcx: TyCtxt<'_>, module_def_id: DefId) {
tcx.hir().visit_item_likes_in_module(module_def_id, &mut ImplWfCheck { tcx });
let min_specialization = tcx.features().min_specialization;
tcx.hir()
.visit_item_likes_in_module(module_def_id, &mut ImplWfCheck { tcx, min_specialization });
}
pub fn provide(providers: &mut Providers<'_>) {
@ -69,6 +75,7 @@ pub fn provide(providers: &mut Providers<'_>) {
struct ImplWfCheck<'tcx> {
tcx: TyCtxt<'tcx>,
min_specialization: bool,
}
impl ItemLikeVisitor<'tcx> for ImplWfCheck<'tcx> {
@ -77,6 +84,9 @@ impl ItemLikeVisitor<'tcx> for ImplWfCheck<'tcx> {
let impl_def_id = self.tcx.hir().local_def_id(item.hir_id);
enforce_impl_params_are_constrained(self.tcx, impl_def_id, items);
enforce_impl_items_are_distinct(self.tcx, items);
if self.min_specialization {
check_min_specialization(self.tcx, impl_def_id, item.span);
}
}
}

View File

@ -0,0 +1,409 @@
//! # Minimal Specialization
//!
//! This module contains the checks for sound specialization used when the
//! `min_specialization` feature is enabled. This requires that the impl is
//! *always applicable*.
//!
//! If `impl1` specializes `impl2` then `impl1` is always applicable if we know
//! that all the bounds of `impl2` are satisfied, and all of the bounds of
//! `impl1` are satisfied for some choice of lifetimes then we know that
//! `impl1` applies for any choice of lifetimes.
//!
//! ## 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` either exist on `impl2` (after
//! matching substs), or are well-formed predicates for the trait's type
//! arguments.
//!
//! ## 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: that is, using them as bounds on the specializing impl,
//! even when they don't occur in the base impl.
//!
//! ### 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_unsafe_specialization_marker
//!
//! There are also some specialization on traits with no methods, including the
//! stable `FusedIterator` trait. We allow marking marker traits with an
//! unstable attribute that means we ignore them in point 3 of the checks
//! above. This is unsound, in the sense that the specialized impl may be used
//! when it doesn't apply, but we allow it in the short term since it can't
//! cause use after frees with purely safe code in the same way as specializing
//! on traits with methods can.
use crate::constrained_generic_params as cgp;
use rustc::middle::region::ScopeTree;
use rustc::ty::subst::{GenericArg, InternalSubsts, SubstsRef};
use rustc::ty::trait_def::TraitSpecializationKind;
use rustc::ty::{self, InstantiatedPredicates, TyCtxt, TypeFoldable};
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::{InferCtxt, SuppressRegionErrors, TyCtxtInferExt};
use rustc_infer::traits::specialization_graph::Node;
use rustc_span::Span;
use rustc_trait_selection::traits::{self, translate_substs, wf};
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) {
tcx.infer_ctxt().enter(|infcx| {
check_always_applicable(&infcx, impl_def_id, node, span);
});
}
}
fn parent_specialization_node(tcx: TyCtxt<'_>, impl1_def_id: DefId) -> Option<Node> {
let trait_ref = tcx.impl_trait_ref(impl1_def_id)?;
let trait_def = tcx.trait_def(trait_ref.def_id);
let impl2_node = trait_def.ancestors(tcx, impl1_def_id).ok()?.nth(1)?;
let always_applicable_trait =
matches!(trait_def.specialization_kind, TraitSpecializationKind::AlwaysApplicable);
if impl2_node.is_from_trait() && !always_applicable_trait {
// Implementing a normal trait isn't a specialization.
return None;
}
Some(impl2_node)
}
/// Check that `impl1` is a sound specialization
fn check_always_applicable(
infcx: &InferCtxt<'_, '_>,
impl1_def_id: DefId,
impl2_node: Node,
span: Span,
) {
if let Some((impl1_substs, impl2_substs)) =
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
);
let tcx = infcx.tcx;
let parent_substs = if impl2_node.is_from_trait() {
impl2_substs.to_vec()
} else {
unconstrained_parent_impl_substs(tcx, impl2_def_id, impl2_substs)
};
check_static_lifetimes(tcx, &parent_substs, span);
check_duplicate_params(tcx, impl1_substs, &parent_substs, span);
check_predicates(infcx, impl1_def_id, impl1_substs, impl2_node, impl2_substs, span);
}
}
/// Given a specializing impl `impl1`, and the base impl `impl2`, returns two
/// substitutions `(S1, S2)` that equate their trait references. The returned
/// types are expressed in terms of the generics of `impl1`.
///
/// Example
///
/// impl<A, B> Foo<A> for B { /* impl2 */ }
/// impl<C> Foo<Vec<C>> for C { /* impl1 */ }
///
/// Would return `S1 = [C]` and `S2 = [Vec<C>, C]`.
fn get_impl_substs<'tcx>(
infcx: &InferCtxt<'_, 'tcx>,
impl1_def_id: DefId,
impl2_node: Node,
span: Span,
) -> Option<(SubstsRef<'tcx>, SubstsRef<'tcx>)> {
let tcx = infcx.tcx;
let param_env = tcx.param_env(impl1_def_id);
let impl1_substs = InternalSubsts::identity_for_item(tcx, impl1_def_id);
let impl2_substs = translate_substs(infcx, param_env, impl1_def_id, impl1_substs, impl2_node);
// Conservatively use an empty `ParamEnv`.
let outlives_env = OutlivesEnvironment::new(ty::ParamEnv::empty());
infcx.resolve_regions_and_report_errors(
impl1_def_id,
&ScopeTree::default(),
&outlives_env,
SuppressRegionErrors::default(),
);
let impl2_substs = match infcx.fully_resolve(&impl2_substs) {
Ok(s) => s,
Err(_) => {
tcx.sess.struct_span_err(span, "could not resolve substs on overridden impl").emit();
return None;
}
};
Some((impl1_substs, impl2_substs))
}
/// Returns a list of all of the unconstrained subst of the given impl.
///
/// For example given the impl:
///
/// impl<'a, T, I> ... where &'a I: IntoIterator<Item=&'a T>
///
/// This would return the substs corresponding to `['a, I]`, because knowing
/// `'a` and `I` determines the value of `T`.
fn unconstrained_parent_impl_substs<'tcx>(
tcx: TyCtxt<'tcx>,
impl_def_id: DefId,
impl_substs: SubstsRef<'tcx>,
) -> Vec<GenericArg<'tcx>> {
let impl_generic_predicates = tcx.predicates_of(impl_def_id);
let mut unconstrained_parameters = FxHashSet::default();
let mut constrained_params = FxHashSet::default();
let impl_trait_ref = tcx.impl_trait_ref(impl_def_id);
// Unfortunately the functions in `constrained_generic_parameters` don't do
// what we want here. We want only a list of constrained parameters while
// the functions in `cgp` add the constrained parameters to a list of
// unconstrained parameters.
for (predicate, _) in impl_generic_predicates.predicates.iter() {
if let ty::Predicate::Projection(proj) = predicate {
let projection_ty = proj.skip_binder().projection_ty;
let projected_ty = proj.skip_binder().ty;
let unbound_trait_ref = projection_ty.trait_ref(tcx);
if Some(unbound_trait_ref) == impl_trait_ref {
continue;
}
unconstrained_parameters.extend(cgp::parameters_for(&projection_ty, true));
for param in cgp::parameters_for(&projected_ty, false) {
if !unconstrained_parameters.contains(&param) {
constrained_params.insert(param.0);
}
}
unconstrained_parameters.extend(cgp::parameters_for(&projected_ty, true));
}
}
impl_substs
.iter()
.enumerate()
.filter(|&(idx, _)| !constrained_params.contains(&(idx as u32)))
.map(|(_, arg)| *arg)
.collect()
}
/// Check that parameters of the derived impl don't occur more than once in the
/// equated substs of the base impl.
///
/// For example forbid the following:
///
/// impl<A> Tr for A { }
/// impl<B> Tr for (B, B) { }
///
/// Note that only consider the unconstrained parameters of the base impl:
///
/// impl<S, I: IntoIterator<Item = S>> Tr<S> for I { }
/// impl<T> Tr<T> for Vec<T> { }
///
/// The substs for the parent impl here are `[T, Vec<T>]`, which repeats `T`,
/// but `S` is constrained in the parent impl, so `parent_substs` is only
/// `[Vec<T>]`. This means we allow this impl.
fn check_duplicate_params<'tcx>(
tcx: TyCtxt<'tcx>,
impl1_substs: SubstsRef<'tcx>,
parent_substs: &Vec<GenericArg<'tcx>>,
span: Span,
) {
let mut base_params = cgp::parameters_for(parent_substs, true);
base_params.sort_by_key(|param| param.0);
if let (_, [duplicate, ..]) = base_params.partition_dedup() {
let param = impl1_substs[duplicate.0 as usize];
tcx.sess
.struct_span_err(span, &format!("specializing impl repeats parameter `{}`", param))
.emit();
}
}
/// Check that `'static` lifetimes are not introduced by the specializing impl.
///
/// For example forbid the following:
///
/// impl<A> Tr for A { }
/// impl Tr for &'static i32 { }
fn check_static_lifetimes<'tcx>(
tcx: TyCtxt<'tcx>,
parent_substs: &Vec<GenericArg<'tcx>>,
span: Span,
) {
if tcx.any_free_region_meets(parent_substs, |r| *r == ty::ReStatic) {
tcx.sess.struct_span_err(span, &format!("cannot specialize on `'static` lifetime")).emit();
}
}
/// Check whether predicates on the specializing impl (`impl1`) are allowed.
///
/// Each predicate `P` must be:
///
/// * 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.
/// * a well-formed predicate of a type argument of the trait being implemented,
/// including the `Self`-type.
fn check_predicates<'tcx>(
infcx: &InferCtxt<'_, 'tcx>,
impl1_def_id: DefId,
impl1_substs: SubstsRef<'tcx>,
impl2_node: Node,
impl2_substs: SubstsRef<'tcx>,
span: Span,
) {
let tcx = infcx.tcx;
let impl1_predicates = tcx.predicates_of(impl1_def_id).instantiate(tcx, impl1_substs);
let mut impl2_predicates = if impl2_node.is_from_trait() {
// Always applicable traits have to be always applicable without any
// assumptions.
InstantiatedPredicates::empty()
} else {
tcx.predicates_of(impl2_node.def_id()).instantiate(tcx, impl2_substs)
};
debug!(
"check_always_applicable(\nimpl1_predicates={:?},\nimpl2_predicates={:?}\n)",
impl1_predicates, impl2_predicates,
);
// Since impls of always applicable traits don't get to assume anything, we
// can also assume their supertraits apply.
//
// For example, we allow:
//
// #[rustc_specialization_trait]
// trait AlwaysApplicable: Debug { }
//
// impl<T> Tr for T { }
// impl<T: AlwaysApplicable> Tr for T { }
//
// Specializing on `AlwaysApplicable` allows also specializing on `Debug`
// which is sound because we forbid impls like the following
//
// impl<D: Debug> AlwaysApplicable for D { }
let always_applicable_traits: Vec<_> = impl1_predicates
.predicates
.iter()
.filter(|predicate| {
matches!(
trait_predicate_kind(tcx, predicate),
Some(TraitSpecializationKind::AlwaysApplicable)
)
})
.copied()
.collect();
// Include the well-formed predicates of the type parameters of the impl.
for ty in tcx.impl_trait_ref(impl1_def_id).unwrap().substs.types() {
if let Some(obligations) = wf::obligations(
infcx,
tcx.param_env(impl1_def_id),
tcx.hir().as_local_hir_id(impl1_def_id).unwrap(),
ty,
span,
) {
impl2_predicates
.predicates
.extend(obligations.into_iter().map(|obligation| obligation.predicate))
}
}
impl2_predicates.predicates.extend(traits::elaborate_predicates(tcx, always_applicable_traits));
for predicate in impl1_predicates.predicates {
if !impl2_predicates.predicates.contains(&predicate) {
check_specialization_on(tcx, &predicate, span)
}
}
}
fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: &ty::Predicate<'tcx>, span: Span) {
debug!("can_specialize_on(predicate = {:?})", predicate);
match predicate {
// 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::Predicate::Trait(pred, hir::Constness::NotConst) => {
if !matches!(
trait_predicate_kind(tcx, predicate),
Some(TraitSpecializationKind::Marker)
) {
tcx.sess
.struct_span_err(
span,
&format!(
"cannot specialize on trait `{}`",
tcx.def_path_str(pred.def_id()),
),
)
.emit()
}
}
_ => tcx
.sess
.struct_span_err(span, &format!("cannot specialize on `{:?}`", predicate))
.emit(),
}
}
fn trait_predicate_kind<'tcx>(
tcx: TyCtxt<'tcx>,
predicate: &ty::Predicate<'tcx>,
) -> Option<TraitSpecializationKind> {
match predicate {
ty::Predicate::Trait(pred, hir::Constness::NotConst) => {
Some(tcx.trait_def(pred.def_id()).specialization_kind)
}
ty::Predicate::Trait(_, hir::Constness::Const)
| ty::Predicate::RegionOutlives(_)
| ty::Predicate::TypeOutlives(_)
| ty::Predicate::Projection(_)
| ty::Predicate::WellFormed(_)
| ty::Predicate::Subtype(_)
| ty::Predicate::ObjectSafe(_)
| ty::Predicate::ClosureKind(..)
| ty::Predicate::ConstEvaluatable(..) => None,
}
}

View File

@ -64,6 +64,7 @@ This API is completely unstable and subject to change.
#![feature(nll)]
#![feature(try_blocks)]
#![feature(never_type)]
#![feature(slice_partition_dedup)]
#![recursion_limit = "256"]
#[macro_use]

View File

@ -292,7 +292,8 @@
#![feature(shrink_to)]
#![feature(slice_concat_ext)]
#![feature(slice_internals)]
#![feature(specialization)]
#![cfg_attr(bootstrap, feature(specialization))]
#![cfg_attr(not(bootstrap), feature(min_specialization))]
#![feature(staged_api)]
#![feature(std_internals)]
#![feature(stdsimd)]

View File

@ -0,0 +1,6 @@
#![feature(rustc_attrs)]
#[rustc_specialization_trait]
pub trait SpecTrait {
fn method(&self);
}

View File

@ -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() {}

View File

@ -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

View File

@ -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() {}

View File

@ -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

View File

@ -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() {}

View File

@ -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() {}

View File

@ -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

View File

@ -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() {}

View File

@ -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

View File

@ -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() {}

View File

@ -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

View 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() {}

View File

@ -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() {}

View File

@ -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() {}

View File

@ -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`.

View File

@ -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() {}

View File

@ -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

View File

@ -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() {}

View File

@ -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

View File

@ -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() {}

View File

@ -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() {}

View File

@ -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() {}

View File

@ -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

View File

@ -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() {}

View File

@ -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