Skip to content

Commit 2871872

Browse files
committed
Auto merge of #53693 - scottmcm:marker-trait-attribute, r=nikomatsakis
Support an explicit annotation for marker traits From the tracking issue for rust-lang/rfcs#1268: > It seems obvious that we should make a `#[marker]` annotation. ~ #29864 (comment) This PR allows you to put `#[marker]` on a trait, at which point: - [x] The trait must not have any items ~~All of the trait's items must have defaults~~ - [x] Any impl of the trait must be empty (not override any items) - [x] But impls of the trait are allowed to overlap r? @nikomatsakis
2 parents 3a2190a + 3932249 commit 2871872

26 files changed

+540
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# `marker_trait_attr`
2+
3+
The tracking issue for this feature is: [#29864]
4+
5+
[#29864]: https://github.com/rust-lang/rust/issues/29864
6+
7+
------------------------
8+
9+
Normally, Rust keeps you from adding trait implementations that could
10+
overlap with each other, as it would be ambiguous which to use. This
11+
feature, however, carves out an exception to that rule: a trait can
12+
opt-in to having overlapping implementations, at the cost that those
13+
implementations are not allowed to override anything (and thus the
14+
trait itself cannot have any associated items, as they're pointless
15+
when they'd need to do the same thing for every type anyway).
16+
17+
```rust
18+
#![feature(marker_trait_attr)]
19+
20+
use std::fmt::{Debug, Display};
21+
22+
#[marker] trait MyMarker {}
23+
24+
impl<T: Debug> MyMarker for T {}
25+
impl<T: Display> MyMarker for T {}
26+
27+
fn foo<T: MyMarker>(t: T) -> T {
28+
t
29+
}
30+
```
31+
32+
This is expected to replace the unstable `overlapping_marker_traits`
33+
feature, which applied to all empty traits (without needing an opt-in).

src/librustc/hir/check_attr.rs

+24
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ enum Target {
3232
Statement,
3333
Closure,
3434
Static,
35+
Trait,
3536
Other,
3637
}
3738

@@ -45,6 +46,7 @@ impl Target {
4546
hir::ItemKind::Const(..) => Target::Const,
4647
hir::ItemKind::ForeignMod(..) => Target::ForeignMod,
4748
hir::ItemKind::Static(..) => Target::Static,
49+
hir::ItemKind::Trait(..) => Target::Trait,
4850
_ => Target::Other,
4951
}
5052
}
@@ -70,6 +72,8 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
7072
self.check_inline(attr, &item.span, target)
7173
} else if attr.check_name("non_exhaustive") {
7274
self.check_non_exhaustive(attr, item, target)
75+
} else if attr.check_name("marker") {
76+
self.check_marker(attr, item, target)
7377
}
7478
}
7579

@@ -114,6 +118,26 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
114118
}
115119
}
116120

121+
/// Check if the `#[marker]` attribute on an `item` is valid.
122+
fn check_marker(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
123+
match target {
124+
Target::Trait => { /* Valid */ },
125+
_ => {
126+
self.tcx.sess
127+
.struct_span_err(attr.span, "attribute can only be applied to a trait")
128+
.span_label(item.span, "not a trait")
129+
.emit();
130+
return;
131+
}
132+
}
133+
134+
if !attr.is_word() {
135+
self.tcx.sess
136+
.struct_span_err(attr.span, "attribute should be empty")
137+
.emit();
138+
}
139+
}
140+
117141
/// Check if the `#[repr]` attributes on `item` are valid.
118142
fn check_repr(&self, item: &hir::Item, target: Target) {
119143
// Extract the names of all repr hints, e.g., [foo, bar, align] for:

src/librustc/ich/impls_ty.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,7 @@ impl_stable_hash_for!(struct ty::TraitDef {
10331033
unsafety,
10341034
paren_sugar,
10351035
has_auto_impl,
1036+
is_marker,
10361037
def_path_hash,
10371038
});
10381039

src/librustc/ty/mod.rs

+24-15
Original file line numberDiff line numberDiff line change
@@ -2662,23 +2662,32 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
26622662
as Box<dyn Iterator<Item = AssociatedItem> + 'a>
26632663
}
26642664

2665-
/// Returns true if the impls are the same polarity and are implementing
2666-
/// a trait which contains no items
2665+
/// Returns true if the impls are the same polarity and the trait either
2666+
/// has no items or is annotated #[marker] and prevents item overrides.
26672667
pub fn impls_are_allowed_to_overlap(self, def_id1: DefId, def_id2: DefId) -> bool {
2668-
if !self.features().overlapping_marker_traits {
2669-
return false;
2668+
if self.features().overlapping_marker_traits {
2669+
let trait1_is_empty = self.impl_trait_ref(def_id1)
2670+
.map_or(false, |trait_ref| {
2671+
self.associated_item_def_ids(trait_ref.def_id).is_empty()
2672+
});
2673+
let trait2_is_empty = self.impl_trait_ref(def_id2)
2674+
.map_or(false, |trait_ref| {
2675+
self.associated_item_def_ids(trait_ref.def_id).is_empty()
2676+
});
2677+
self.impl_polarity(def_id1) == self.impl_polarity(def_id2)
2678+
&& trait1_is_empty
2679+
&& trait2_is_empty
2680+
} else if self.features().marker_trait_attr {
2681+
let is_marker_impl = |def_id: DefId| -> bool {
2682+
let trait_ref = self.impl_trait_ref(def_id);
2683+
trait_ref.map_or(false, |tr| self.trait_def(tr.def_id).is_marker)
2684+
};
2685+
self.impl_polarity(def_id1) == self.impl_polarity(def_id2)
2686+
&& is_marker_impl(def_id1)
2687+
&& is_marker_impl(def_id2)
2688+
} else {
2689+
false
26702690
}
2671-
let trait1_is_empty = self.impl_trait_ref(def_id1)
2672-
.map_or(false, |trait_ref| {
2673-
self.associated_item_def_ids(trait_ref.def_id).is_empty()
2674-
});
2675-
let trait2_is_empty = self.impl_trait_ref(def_id2)
2676-
.map_or(false, |trait_ref| {
2677-
self.associated_item_def_ids(trait_ref.def_id).is_empty()
2678-
});
2679-
self.impl_polarity(def_id1) == self.impl_polarity(def_id2)
2680-
&& trait1_is_empty
2681-
&& trait2_is_empty
26822691
}
26832692

26842693
// Returns `ty::VariantDef` if `def` refers to a struct,

src/librustc/ty/trait_def.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ pub struct TraitDef {
3636

3737
pub has_auto_impl: bool,
3838

39+
/// If `true`, then this trait has the `#[marker]` attribute, indicating
40+
/// that all its associated items have defaults that cannot be overridden,
41+
/// and thus `impl`s of it are allowed to overlap.
42+
pub is_marker: bool,
43+
3944
/// The ICH of this trait's DefPath, cached here so it doesn't have to be
4045
/// recomputed all the time.
4146
pub def_path_hash: DefPathHash,
@@ -53,13 +58,15 @@ impl<'a, 'gcx, 'tcx> TraitDef {
5358
unsafety: hir::Unsafety,
5459
paren_sugar: bool,
5560
has_auto_impl: bool,
61+
is_marker: bool,
5662
def_path_hash: DefPathHash)
5763
-> TraitDef {
5864
TraitDef {
5965
def_id,
60-
paren_sugar,
6166
unsafety,
67+
paren_sugar,
6268
has_auto_impl,
69+
is_marker,
6370
def_path_hash,
6471
}
6572
}

src/librustc_metadata/decoder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ impl<'a, 'tcx> CrateMetadata {
539539
data.unsafety,
540540
data.paren_sugar,
541541
data.has_auto_impl,
542+
data.is_marker,
542543
self.def_path_table.def_path_hash(item_id))
543544
}
544545

src/librustc_metadata/encoder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,7 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
11521152
unsafety: trait_def.unsafety,
11531153
paren_sugar: trait_def.paren_sugar,
11541154
has_auto_impl: tcx.trait_is_auto(def_id),
1155+
is_marker: trait_def.is_marker,
11551156
super_predicates: self.lazy(&tcx.super_predicates_of(def_id)),
11561157
};
11571158

src/librustc_metadata/schema.rs

+2
Original file line numberDiff line numberDiff line change
@@ -472,13 +472,15 @@ pub struct TraitData<'tcx> {
472472
pub unsafety: hir::Unsafety,
473473
pub paren_sugar: bool,
474474
pub has_auto_impl: bool,
475+
pub is_marker: bool,
475476
pub super_predicates: Lazy<ty::GenericPredicates<'tcx>>,
476477
}
477478

478479
impl_stable_hash_for!(struct TraitData<'tcx> {
479480
unsafety,
480481
paren_sugar,
481482
has_auto_impl,
483+
is_marker,
482484
super_predicates
483485
});
484486

src/librustc_typeck/check/wfcheck.rs

+13
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,19 @@ fn check_type_defn<'a, 'tcx, F>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
304304

305305
fn check_trait<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, item: &hir::Item) {
306306
let trait_def_id = tcx.hir.local_def_id(item.id);
307+
308+
let trait_def = tcx.trait_def(trait_def_id);
309+
if trait_def.is_marker {
310+
for associated_def_id in &*tcx.associated_item_def_ids(trait_def_id) {
311+
struct_span_err!(
312+
tcx.sess,
313+
tcx.def_span(*associated_def_id),
314+
E0714,
315+
"marker traits cannot have associated items",
316+
).emit();
317+
}
318+
}
319+
307320
for_item(tcx, item).with_fcx(|fcx, _| {
308321
check_where_clauses(tcx, fcx, item.span, trait_def_id, None);
309322
vec![]

src/librustc_typeck/coherence/mod.rs

+20
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ fn check_impl<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, node_id: ast::NodeId) {
4646
}
4747

4848
enforce_trait_manually_implementable(tcx, impl_def_id, trait_ref.def_id);
49+
enforce_empty_impls_for_marker_traits(tcx, impl_def_id, trait_ref.def_id);
4950
}
5051
}
5152

@@ -99,6 +100,25 @@ fn enforce_trait_manually_implementable(tcx: TyCtxt, impl_def_id: DefId, trait_d
99100
.emit();
100101
}
101102

103+
/// We allow impls of marker traits to overlap, so they can't override impls
104+
/// as that could make it ambiguous which associated item to use.
105+
fn enforce_empty_impls_for_marker_traits(tcx: TyCtxt, impl_def_id: DefId, trait_def_id: DefId) {
106+
if !tcx.trait_def(trait_def_id).is_marker {
107+
return;
108+
}
109+
110+
if tcx.associated_item_def_ids(trait_def_id).is_empty() {
111+
return;
112+
}
113+
114+
let span = tcx.sess.source_map().def_span(tcx.span_of_impl(impl_def_id).unwrap());
115+
struct_span_err!(tcx.sess,
116+
span,
117+
E0715,
118+
"impls for marker traits cannot contain items")
119+
.emit();
120+
}
121+
102122
pub fn provide(providers: &mut Providers) {
103123
use self::builtin::coerce_unsized_info;
104124
use self::inherent_impls::{crate_inherent_impls, inherent_impls};

src/librustc_typeck/collect.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -740,8 +740,9 @@ fn trait_def<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> &'tcx ty::
740740
err.emit();
741741
}
742742

743+
let is_marker = tcx.has_attr(def_id, "marker");
743744
let def_path_hash = tcx.def_path_hash(def_id);
744-
let def = ty::TraitDef::new(def_id, unsafety, paren_sugar, is_auto, def_path_hash);
745+
let def = ty::TraitDef::new(def_id, unsafety, paren_sugar, is_auto, is_marker, def_path_hash);
745746
tcx.alloc_trait_def(def)
746747
}
747748

src/librustc_typeck/diagnostics.rs

+16
Original file line numberDiff line numberDiff line change
@@ -4750,6 +4750,22 @@ ambiguity for some types, we disallow calling methods on raw pointers when
47504750
the type is unknown.
47514751
"##,
47524752

4753+
E0714: r##"
4754+
A `#[marker]` trait contained an associated item.
4755+
4756+
The items of marker traits cannot be overridden, so there's no need to have them
4757+
when they cannot be changed per-type anyway. If you wanted them for ergonomic
4758+
reasons, consider making an extension trait instead.
4759+
"##,
4760+
4761+
E0715: r##"
4762+
An `impl` for a `#[marker]` trait tried to override an associated item.
4763+
4764+
Because marker traits are allowed to have multiple implementations for the same
4765+
type, it's not allowed to override anything in those implementations, as it
4766+
would be ambiguous which override should actually be used.
4767+
"##,
4768+
47534769
}
47544770

47554771
register_diagnostics! {

src/libsyntax/feature_gate.rs

+9
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,9 @@ declare_features! (
355355
// Allows overlapping impls of marker traits
356356
(active, overlapping_marker_traits, "1.18.0", Some(29864), None),
357357

358+
// Trait attribute to allow overlapping impls
359+
(active, marker_trait_attr, "1.30.0", Some(29864), None),
360+
358361
// rustc internal
359362
(active, abi_thiscall, "1.19.0", None, None),
360363

@@ -808,6 +811,12 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
808811
"non exhaustive is an experimental feature",
809812
cfg_fn!(non_exhaustive))),
810813

814+
// RFC #1268
815+
("marker", Normal, Gated(Stability::Unstable,
816+
"marker_trait_attr",
817+
"marker traits is an experimental feature",
818+
cfg_fn!(marker_trait_attr))),
819+
811820
("plugin", CrateLevel, Gated(Stability::Unstable,
812821
"plugin",
813822
"compiler plugins are experimental \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Tests for RFC 1268: we allow overlapping impls of marker traits,
12+
// that is, traits with #[marker]. In this case, a type `T` is
13+
// `MyMarker` if it is either `Debug` or `Display`.
14+
15+
#![feature(marker_trait_attr)]
16+
17+
use std::fmt::{Debug, Display};
18+
19+
#[marker] trait MyMarker {}
20+
21+
impl<T: Debug> MyMarker for T {}
22+
impl<T: Display> MyMarker for T {}
23+
24+
fn foo<T: MyMarker>(t: T) -> T {
25+
t
26+
}
27+
28+
fn main() {
29+
// Debug && Display:
30+
assert_eq!(1, foo(1));
31+
assert_eq!(2.0, foo(2.0));
32+
33+
// Debug && !Display:
34+
assert_eq!(vec![1], foo(vec![1]));
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use std::fmt::{Debug, Display};
12+
13+
#[marker] trait ExplicitMarker {}
14+
//~^ ERROR marker traits is an experimental feature (see issue #29864)
15+
16+
impl<T: Display> ExplicitMarker for T {}
17+
impl<T: Debug> ExplicitMarker for T {}
18+
19+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0658]: marker traits is an experimental feature (see issue #29864)
2+
--> $DIR/feature-gate-marker_trait_attr.rs:13:1
3+
|
4+
LL | #[marker] trait ExplicitMarker {}
5+
| ^^^^^^^^^
6+
|
7+
= help: add #![feature(marker_trait_attr)] to the crate attributes to enable
8+
9+
error: aborting due to previous error
10+
11+
For more information about this error, try `rustc --explain E0658`.

0 commit comments

Comments
 (0)