Skip to content

Commit 32d85c0

Browse files
authored
Rollup merge of #92164 - WaffleLapkin:rustc_must_implement_one_of_attr, r=Aaron1011
Implement `#[rustc_must_implement_one_of]` attribute This PR adds a new attribute — `#[rustc_must_implement_one_of]` that allows changing the "minimal complete definition" of a trait. It's similar to GHC's minimal `{-# MINIMAL #-}` pragma, though `#[rustc_must_implement_one_of]` is weaker atm. Such attribute was long wanted. It can be, for example, used in `Read` trait to make transitions to recently added `read_buf` easier: ```rust #[rustc_must_implement_one_of(read, read_buf)] pub trait Read { fn read(&mut self, buf: &mut [u8]) -> Result<usize> { let mut buf = ReadBuf::new(buf); self.read_buf(&mut buf)?; Ok(buf.filled_len()) } fn read_buf(&mut self, buf: &mut ReadBuf<'_>) -> Result<()> { default_read_buf(|b| self.read(b), buf) } } impl Read for Ty0 {} //^ This will fail to compile even though all `Read` methods have default implementations // Both of these will compile just fine impl Read for Ty1 { fn read(&mut self, buf: &mut [u8]) -> Result<usize> { /* ... */ } } impl Read for Ty2 { fn read_buf(&mut self, buf: &mut ReadBuf<'_>) -> Result<()> { /* ... */ } } ``` For now, this is implemented as an internal attribute to start experimenting on the design of this feature. In the future we may want to extend it: - Allow arbitrary requirements like `a | (b & c)` - Allow multiple requirements like - ```rust #[rustc_must_implement_one_of(a, b)] #[rustc_must_implement_one_of(c, d)] ``` - Make it appear in rustdoc documentation - Change the syntax? - Etc Eventually, we should make an RFC and make this (or rather similar) attribute public. --- I'm fairly new to compiler development and not at all sure if the implementation makes sense, but at least it passes tests :)
2 parents 67bcbde + 28edd7a commit 32d85c0

17 files changed

+431
-4
lines changed

compiler/rustc_feature/src/builtin_attrs.rs

+6
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
682682
"the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \
683683
from method dispatch when the receiver is an array, for compatibility in editions < 2021."
684684
),
685+
rustc_attr!(
686+
rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."), ErrorFollowing,
687+
"the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \
688+
definition of a trait, it's currently in experimental form and should be changed before \
689+
being exposed outside of the std"
690+
),
685691

686692
// ==========================================================================
687693
// Internal attributes, Testing:

compiler/rustc_metadata/src/rmeta/decoder.rs

+2
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
820820
data.skip_array_during_method_dispatch,
821821
data.specialization_kind,
822822
self.def_path_hash(item_id),
823+
data.must_implement_one_of,
823824
)
824825
}
825826
EntryKind::TraitAlias => ty::TraitDef::new(
@@ -831,6 +832,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
831832
false,
832833
ty::trait_def::TraitSpecializationKind::None,
833834
self.def_path_hash(item_id),
835+
None,
834836
),
835837
_ => bug!("def-index does not refer to trait or trait alias"),
836838
}

compiler/rustc_metadata/src/rmeta/encoder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
15131513
is_marker: trait_def.is_marker,
15141514
skip_array_during_method_dispatch: trait_def.skip_array_during_method_dispatch,
15151515
specialization_kind: trait_def.specialization_kind,
1516+
must_implement_one_of: trait_def.must_implement_one_of.clone(),
15161517
};
15171518

15181519
EntryKind::Trait(self.lazy(data))

compiler/rustc_metadata/src/rmeta/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ struct TraitData {
378378
is_marker: bool,
379379
skip_array_during_method_dispatch: bool,
380380
specialization_kind: ty::trait_def::TraitSpecializationKind,
381+
must_implement_one_of: Option<Box<[Ident]>>,
381382
}
382383

383384
#[derive(TyEncodable, TyDecodable)]

compiler/rustc_middle/src/ty/trait_def.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::traits::specialization_graph;
22
use crate::ty::fast_reject::{self, SimplifiedType, SimplifyParams, StripReferences};
33
use crate::ty::fold::TypeFoldable;
4-
use crate::ty::{Ty, TyCtxt};
4+
use crate::ty::{Ident, Ty, TyCtxt};
55
use rustc_hir as hir;
66
use rustc_hir::def_id::DefId;
77
use rustc_hir::definitions::DefPathHash;
@@ -44,6 +44,10 @@ pub struct TraitDef {
4444
/// The ICH of this trait's DefPath, cached here so it doesn't have to be
4545
/// recomputed all the time.
4646
pub def_path_hash: DefPathHash,
47+
48+
/// List of functions from `#[rustc_must_implement_one_of]` attribute one of which
49+
/// must be implemented.
50+
pub must_implement_one_of: Option<Box<[Ident]>>,
4751
}
4852

4953
/// Whether this trait is treated specially by the standard library
@@ -87,6 +91,7 @@ impl<'tcx> TraitDef {
8791
skip_array_during_method_dispatch: bool,
8892
specialization_kind: TraitSpecializationKind,
8993
def_path_hash: DefPathHash,
94+
must_implement_one_of: Option<Box<[Ident]>>,
9095
) -> TraitDef {
9196
TraitDef {
9297
def_id,
@@ -97,6 +102,7 @@ impl<'tcx> TraitDef {
97102
skip_array_during_method_dispatch,
98103
specialization_kind,
99104
def_path_hash,
105+
must_implement_one_of,
100106
}
101107
}
102108

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,7 @@ symbols! {
11621162
rustc_macro_transparency,
11631163
rustc_main,
11641164
rustc_mir,
1165+
rustc_must_implement_one_of,
11651166
rustc_nonnull_optimization_guaranteed,
11661167
rustc_object_lifetime_default,
11671168
rustc_on_unimplemented,

compiler/rustc_typeck/src/check/check.rs

+29
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,10 @@ fn check_impl_items_against_trait<'tcx>(
978978
if let Ok(ancestors) = trait_def.ancestors(tcx, impl_id.to_def_id()) {
979979
// Check for missing items from trait
980980
let mut missing_items = Vec::new();
981+
982+
let mut must_implement_one_of: Option<&[Ident]> =
983+
trait_def.must_implement_one_of.as_deref();
984+
981985
for &trait_item_id in tcx.associated_item_def_ids(impl_trait_ref.def_id) {
982986
let is_implemented = ancestors
983987
.leaf_def(tcx, trait_item_id)
@@ -986,12 +990,37 @@ fn check_impl_items_against_trait<'tcx>(
986990
if !is_implemented && tcx.impl_defaultness(impl_id).is_final() {
987991
missing_items.push(tcx.associated_item(trait_item_id));
988992
}
993+
994+
if let Some(required_items) = &must_implement_one_of {
995+
// true if this item is specifically implemented in this impl
996+
let is_implemented_here = ancestors
997+
.leaf_def(tcx, trait_item_id)
998+
.map_or(false, |node_item| !node_item.defining_node.is_from_trait());
999+
1000+
if is_implemented_here {
1001+
let trait_item = tcx.associated_item(trait_item_id);
1002+
if required_items.contains(&trait_item.ident) {
1003+
must_implement_one_of = None;
1004+
}
1005+
}
1006+
}
9891007
}
9901008

9911009
if !missing_items.is_empty() {
9921010
let impl_span = tcx.sess.source_map().guess_head_span(full_impl_span);
9931011
missing_items_err(tcx, impl_span, &missing_items, full_impl_span);
9941012
}
1013+
1014+
if let Some(missing_items) = must_implement_one_of {
1015+
let impl_span = tcx.sess.source_map().guess_head_span(full_impl_span);
1016+
let attr_span = tcx
1017+
.get_attrs(impl_trait_ref.def_id)
1018+
.iter()
1019+
.find(|attr| attr.has_name(sym::rustc_must_implement_one_of))
1020+
.map(|attr| attr.span);
1021+
1022+
missing_items_must_implement_one_of_err(tcx, impl_span, missing_items, attr_span);
1023+
}
9951024
}
9961025
}
9971026

compiler/rustc_typeck/src/check/mod.rs

+25
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,31 @@ fn missing_items_err(
637637
err.emit();
638638
}
639639

640+
fn missing_items_must_implement_one_of_err(
641+
tcx: TyCtxt<'_>,
642+
impl_span: Span,
643+
missing_items: &[Ident],
644+
annotation_span: Option<Span>,
645+
) {
646+
let missing_items_msg =
647+
missing_items.iter().map(Ident::to_string).collect::<Vec<_>>().join("`, `");
648+
649+
let mut err = struct_span_err!(
650+
tcx.sess,
651+
impl_span,
652+
E0046,
653+
"not all trait items implemented, missing one of: `{}`",
654+
missing_items_msg
655+
);
656+
err.span_label(impl_span, format!("missing one of `{}` in implementation", missing_items_msg));
657+
658+
if let Some(annotation_span) = annotation_span {
659+
err.span_note(annotation_span, "required because of this annotation");
660+
}
661+
662+
err.emit();
663+
}
664+
640665
/// Resugar `ty::GenericPredicates` in a way suitable to be used in structured suggestions.
641666
fn bounds_from_generic_predicates<'tcx>(
642667
tcx: TyCtxt<'tcx>,

compiler/rustc_typeck/src/collect.rs

+103-3
Original file line numberDiff line numberDiff line change
@@ -1190,9 +1190,11 @@ fn super_predicates_that_define_assoc_type(
11901190
fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef {
11911191
let item = tcx.hir().expect_item(def_id.expect_local());
11921192

1193-
let (is_auto, unsafety) = match item.kind {
1194-
hir::ItemKind::Trait(is_auto, unsafety, ..) => (is_auto == hir::IsAuto::Yes, unsafety),
1195-
hir::ItemKind::TraitAlias(..) => (false, hir::Unsafety::Normal),
1193+
let (is_auto, unsafety, items) = match item.kind {
1194+
hir::ItemKind::Trait(is_auto, unsafety, .., items) => {
1195+
(is_auto == hir::IsAuto::Yes, unsafety, items)
1196+
}
1197+
hir::ItemKind::TraitAlias(..) => (false, hir::Unsafety::Normal, &[][..]),
11961198
_ => span_bug!(item.span, "trait_def_of_item invoked on non-trait"),
11971199
};
11981200

@@ -1219,6 +1221,103 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef {
12191221
ty::trait_def::TraitSpecializationKind::None
12201222
};
12211223
let def_path_hash = tcx.def_path_hash(def_id);
1224+
1225+
let must_implement_one_of = tcx
1226+
.get_attrs(def_id)
1227+
.iter()
1228+
.find(|attr| attr.has_name(sym::rustc_must_implement_one_of))
1229+
// Check that there are at least 2 arguments of `#[rustc_must_implement_one_of]`
1230+
// and that they are all identifiers
1231+
.and_then(|attr| match attr.meta_item_list() {
1232+
Some(items) if items.len() < 2 => {
1233+
tcx.sess
1234+
.struct_span_err(
1235+
attr.span,
1236+
"the `#[rustc_must_implement_one_of]` attribute must be \
1237+
used with at least 2 args",
1238+
)
1239+
.emit();
1240+
1241+
None
1242+
}
1243+
Some(items) => items
1244+
.into_iter()
1245+
.map(|item| item.ident().ok_or(item.span()))
1246+
.collect::<Result<Box<[_]>, _>>()
1247+
.map_err(|span| {
1248+
tcx.sess
1249+
.struct_span_err(span, "must be a name of an associated function")
1250+
.emit();
1251+
})
1252+
.ok()
1253+
.zip(Some(attr.span)),
1254+
// Error is reported by `rustc_attr!`
1255+
None => None,
1256+
})
1257+
// Check that all arguments of `#[rustc_must_implement_one_of]` reference
1258+
// functions in the trait with default implementations
1259+
.and_then(|(list, attr_span)| {
1260+
let errors = list.iter().filter_map(|ident| {
1261+
let item = items.iter().find(|item| item.ident == *ident);
1262+
1263+
match item {
1264+
Some(item) if matches!(item.kind, hir::AssocItemKind::Fn { .. }) => {
1265+
if !item.defaultness.has_value() {
1266+
tcx.sess
1267+
.struct_span_err(
1268+
item.span,
1269+
"This function doesn't have a default implementation",
1270+
)
1271+
.span_note(attr_span, "required by this annotation")
1272+
.emit();
1273+
1274+
return Some(());
1275+
}
1276+
1277+
return None;
1278+
}
1279+
Some(item) => tcx
1280+
.sess
1281+
.struct_span_err(item.span, "Not a function")
1282+
.span_note(attr_span, "required by this annotation")
1283+
.note(
1284+
"All `#[rustc_must_implement_one_of]` arguments \
1285+
must be associated function names",
1286+
)
1287+
.emit(),
1288+
None => tcx
1289+
.sess
1290+
.struct_span_err(ident.span, "Function not found in this trait")
1291+
.emit(),
1292+
}
1293+
1294+
Some(())
1295+
});
1296+
1297+
(errors.count() == 0).then_some(list)
1298+
})
1299+
// Check for duplicates
1300+
.and_then(|list| {
1301+
let mut set: FxHashMap<Symbol, Span> = FxHashMap::default();
1302+
let mut no_dups = true;
1303+
1304+
for ident in &*list {
1305+
if let Some(dup) = set.insert(ident.name, ident.span) {
1306+
tcx.sess
1307+
.struct_span_err(vec![dup, ident.span], "Functions names are duplicated")
1308+
.note(
1309+
"All `#[rustc_must_implement_one_of]` arguments \
1310+
must be unique",
1311+
)
1312+
.emit();
1313+
1314+
no_dups = false;
1315+
}
1316+
}
1317+
1318+
no_dups.then_some(list)
1319+
});
1320+
12221321
ty::TraitDef::new(
12231322
def_id,
12241323
unsafety,
@@ -1228,6 +1327,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef {
12281327
skip_array_during_method_dispatch,
12291328
spec_kind,
12301329
def_path_hash,
1330+
must_implement_one_of,
12311331
)
12321332
}
12331333

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#![feature(rustc_attrs)]
2+
3+
#[rustc_must_implement_one_of(eq, neq)]
4+
trait Equal {
5+
fn eq(&self, other: &Self) -> bool {
6+
!self.neq(other)
7+
}
8+
9+
fn neq(&self, other: &Self) -> bool {
10+
!self.eq(other)
11+
}
12+
}
13+
14+
struct T0;
15+
struct T1;
16+
struct T2;
17+
struct T3;
18+
19+
impl Equal for T0 {
20+
fn eq(&self, _other: &Self) -> bool {
21+
true
22+
}
23+
}
24+
25+
impl Equal for T1 {
26+
fn neq(&self, _other: &Self) -> bool {
27+
false
28+
}
29+
}
30+
31+
impl Equal for T2 {
32+
fn eq(&self, _other: &Self) -> bool {
33+
true
34+
}
35+
36+
fn neq(&self, _other: &Self) -> bool {
37+
false
38+
}
39+
}
40+
41+
impl Equal for T3 {}
42+
//~^ not all trait items implemented, missing one of: `eq`, `neq`
43+
44+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error[E0046]: not all trait items implemented, missing one of: `eq`, `neq`
2+
--> $DIR/rustc_must_implement_one_of.rs:41:1
3+
|
4+
LL | impl Equal for T3 {}
5+
| ^^^^^^^^^^^^^^^^^ missing one of `eq`, `neq` in implementation
6+
|
7+
note: required because of this annotation
8+
--> $DIR/rustc_must_implement_one_of.rs:3:1
9+
|
10+
LL | #[rustc_must_implement_one_of(eq, neq)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to previous error
14+
15+
For more information about this error, try `rustc --explain E0046`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#![feature(rustc_attrs)]
2+
3+
#[rustc_must_implement_one_of(a, a)]
4+
//~^ Functions names are duplicated
5+
trait Trait {
6+
fn a() {}
7+
}
8+
9+
#[rustc_must_implement_one_of(b, a, a, c, b, c)]
10+
//~^ Functions names are duplicated
11+
//~| Functions names are duplicated
12+
//~| Functions names are duplicated
13+
trait Trait1 {
14+
fn a() {}
15+
fn b() {}
16+
fn c() {}
17+
}
18+
19+
fn main() {}

0 commit comments

Comments
 (0)