From 5ab40c8f99607a28441c921690b4498f0186c701 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 21 Dec 2021 18:40:50 +0300 Subject: [PATCH 1/9] Implement `#[rustc_must_implement_one_of]` attribute --- compiler/rustc_feature/src/builtin_attrs.rs | 6 ++ compiler/rustc_metadata/src/rmeta/decoder.rs | 2 + compiler/rustc_metadata/src/rmeta/encoder.rs | 1 + compiler/rustc_metadata/src/rmeta/mod.rs | 1 + compiler/rustc_middle/src/ty/trait_def.rs | 8 +- compiler/rustc_span/src/symbol.rs | 1 + compiler/rustc_typeck/src/check/check.rs | 23 +++++ compiler/rustc_typeck/src/check/mod.rs | 25 ++++++ compiler/rustc_typeck/src/collect.rs | 83 ++++++++++++++++++- src/test/ui/rustc_must_implement_one_of.rs | 44 ++++++++++ .../ui/rustc_must_implement_one_of.stderr | 15 ++++ 11 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 src/test/ui/rustc_must_implement_one_of.rs create mode 100644 src/test/ui/rustc_must_implement_one_of.stderr diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index f25b2d8f566c0..6f119d5ab8892 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -677,6 +677,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ "the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \ from method dispatch when the receiver is an array, for compatibility in editions < 2021." ), + rustc_attr!( + rustc_must_implement_one_of, Normal, template!(List: "method1, method2, ..."), ErrorFollowing, + "the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \ + definition of a trait, it's currently in experimental form and should be changed before \ + being exposed outside of the std" + ), // ========================================================================== // Internal attributes, Testing: diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 603b21d400578..2cdbdfdd99b89 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -820,6 +820,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { data.skip_array_during_method_dispatch, data.specialization_kind, self.def_path_hash(item_id), + data.must_implement_one_of, ) } EntryKind::TraitAlias => ty::TraitDef::new( @@ -831,6 +832,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { false, ty::trait_def::TraitSpecializationKind::None, self.def_path_hash(item_id), + None, ), _ => bug!("def-index does not refer to trait or trait alias"), } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 8a74aef60ddaf..c2c231d5cf90e 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1514,6 +1514,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { is_marker: trait_def.is_marker, skip_array_during_method_dispatch: trait_def.skip_array_during_method_dispatch, specialization_kind: trait_def.specialization_kind, + must_implement_one_of: trait_def.must_implement_one_of.clone(), }; EntryKind::Trait(self.lazy(data)) diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 432ca547de6cd..c7e5a7c5d2c7c 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -378,6 +378,7 @@ struct TraitData { is_marker: bool, skip_array_during_method_dispatch: bool, specialization_kind: ty::trait_def::TraitSpecializationKind, + must_implement_one_of: Option>, } #[derive(TyEncodable, TyDecodable)] diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index 34d059f4ec849..c4c75434980d2 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -1,7 +1,7 @@ use crate::traits::specialization_graph; use crate::ty::fast_reject::{self, SimplifiedType, SimplifyParams, StripReferences}; use crate::ty::fold::TypeFoldable; -use crate::ty::{Ty, TyCtxt}; +use crate::ty::{Ident, Ty, TyCtxt}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir::definitions::DefPathHash; @@ -44,6 +44,10 @@ pub struct TraitDef { /// 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, + + /// List of methods from `#[rustc_must_implement_one_of]` attribute one of which + /// must be implemented. + pub must_implement_one_of: Option>, } /// Whether this trait is treated specially by the standard library @@ -87,6 +91,7 @@ impl<'tcx> TraitDef { skip_array_during_method_dispatch: bool, specialization_kind: TraitSpecializationKind, def_path_hash: DefPathHash, + must_implement_one_of: Option>, ) -> TraitDef { TraitDef { def_id, @@ -97,6 +102,7 @@ impl<'tcx> TraitDef { skip_array_during_method_dispatch, specialization_kind, def_path_hash, + must_implement_one_of, } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 84cf8878af809..483171917ddf4 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1136,6 +1136,7 @@ symbols! { rustc_macro_transparency, rustc_main, rustc_mir, + rustc_must_implement_one_of, rustc_nonnull_optimization_guaranteed, rustc_object_lifetime_default, rustc_on_unimplemented, diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs index dcf42e1aefebc..ff1031595662d 100644 --- a/compiler/rustc_typeck/src/check/check.rs +++ b/compiler/rustc_typeck/src/check/check.rs @@ -979,6 +979,10 @@ fn check_impl_items_against_trait<'tcx>( if let Ok(ancestors) = trait_def.ancestors(tcx, impl_id.to_def_id()) { // Check for missing items from trait let mut missing_items = Vec::new(); + + let mut must_implement_one_of: Option> = + trait_def.must_implement_one_of.as_deref().map(|slice| slice.iter().copied().collect()); + for &trait_item_id in tcx.associated_item_def_ids(impl_trait_ref.def_id) { let is_implemented = ancestors .leaf_def(tcx, trait_item_id) @@ -987,12 +991,31 @@ fn check_impl_items_against_trait<'tcx>( if !is_implemented && tcx.impl_defaultness(impl_id).is_final() { missing_items.push(tcx.associated_item(trait_item_id)); } + + if let Some(required_items) = &must_implement_one_of { + let trait_item = tcx.associated_item(trait_item_id); + + if is_implemented && required_items.contains(&trait_item.ident) { + must_implement_one_of = None; + } + } } if !missing_items.is_empty() { let impl_span = tcx.sess.source_map().guess_head_span(full_impl_span); missing_items_err(tcx, impl_span, &missing_items, full_impl_span); } + + if let Some(missing_items) = must_implement_one_of { + let impl_span = tcx.sess.source_map().guess_head_span(full_impl_span); + let attr_span = tcx + .get_attrs(impl_trait_ref.def_id) + .iter() + .find(|attr| attr.has_name(sym::rustc_must_implement_one_of)) + .map(|attr| attr.span); + + missing_items_must_implement_one_of_err(tcx, impl_span, &missing_items, attr_span); + } } } diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs index d576154ff9073..17c4bc1b3014f 100644 --- a/compiler/rustc_typeck/src/check/mod.rs +++ b/compiler/rustc_typeck/src/check/mod.rs @@ -641,6 +641,31 @@ fn missing_items_err( err.emit(); } +fn missing_items_must_implement_one_of_err( + tcx: TyCtxt<'_>, + impl_span: Span, + missing_items: &FxHashSet, + annotation_span: Option, +) { + let missing_items_msg = + missing_items.iter().map(Ident::to_string).collect::>().join("`, `"); + + let mut err = struct_span_err!( + tcx.sess, + impl_span, + E0046, + "not all trait items implemented, missing one of: `{}`", + missing_items_msg + ); + err.span_label(impl_span, format!("missing one of `{}` in implementation", missing_items_msg)); + + if let Some(annotation_span) = annotation_span { + err.span_note(annotation_span, "required because of this annotation"); + } + + err.emit(); +} + /// Resugar `ty::GenericPredicates` in a way suitable to be used in structured suggestions. fn bounds_from_generic_predicates<'tcx>( tcx: TyCtxt<'tcx>, diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs index d4d4baa3f71da..50613ef7104da 100644 --- a/compiler/rustc_typeck/src/collect.rs +++ b/compiler/rustc_typeck/src/collect.rs @@ -1198,9 +1198,11 @@ fn super_predicates_that_define_assoc_type( fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { let item = tcx.hir().expect_item(def_id.expect_local()); - let (is_auto, unsafety) = match item.kind { - hir::ItemKind::Trait(is_auto, unsafety, ..) => (is_auto == hir::IsAuto::Yes, unsafety), - hir::ItemKind::TraitAlias(..) => (false, hir::Unsafety::Normal), + let (is_auto, unsafety, items) = match item.kind { + hir::ItemKind::Trait(is_auto, unsafety, .., items) => { + (is_auto == hir::IsAuto::Yes, unsafety, items) + } + hir::ItemKind::TraitAlias(..) => (false, hir::Unsafety::Normal, &[][..]), _ => span_bug!(item.span, "trait_def_of_item invoked on non-trait"), }; @@ -1227,6 +1229,80 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { ty::trait_def::TraitSpecializationKind::None }; let def_path_hash = tcx.def_path_hash(def_id); + + let must_implement_one_of = tcx + .get_attrs(def_id) + .iter() + .find(|attr| attr.has_name(sym::rustc_must_implement_one_of)) + // Check that there are at least 2 arguments of `#[rustc_must_implement_one_of]` + // and that they are all identifiers + .and_then(|attr| match attr.meta_item_list() { + Some(items) if items.len() < 2 => { + tcx.sess + .struct_span_err( + attr.span, + "the `#[rustc_must_implement_one_of]` attribute must be \ + used with at least 2 args", + ) + .emit(); + + None + } + Some(items) => items + .into_iter() + .map(|item| item.ident().ok_or(item.span())) + .collect::, _>>() + .map_err(|span| { + tcx.sess.struct_span_err(span, "must be an identifier of a method").emit(); + }) + .ok() + .zip(Some(attr.span)), + // Error is reported by `rustc_attr!` + None => None, + }) + // Check that all arguments of `#[rustc_must_implement_one_of]` reference + // methods in the trait with default implementations + .and_then(|(list, attr_span)| { + let errors = list.iter().filter_map(|ident| { + let item = items.iter().find(|item| item.ident == *ident); + + match item { + Some(item) if matches!(item.kind, hir::AssocItemKind::Fn { .. }) => { + if !item.defaultness.has_value() { + tcx.sess + .struct_span_err( + item.span, + "This method doesn't have a default implementation", + ) + .span_note(attr_span, "required by this annotation") + .emit(); + + return Some(()); + } + + return None; + } + Some(item) => tcx + .sess + .struct_span_err(item.span, "Not a method") + .span_note(attr_span, "required by this annotation") + .note( + "All `#[rustc_must_implement_one_of]` arguments \ + must be method identifiers", + ) + .emit(), + None => tcx + .sess + .struct_span_err(ident.span, "Method not found in this trait") + .emit(), + } + + Some(()) + }); + + (errors.count() == 0).then_some(list) + }); + ty::TraitDef::new( def_id, unsafety, @@ -1236,6 +1312,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { skip_array_during_method_dispatch, spec_kind, def_path_hash, + must_implement_one_of, ) } diff --git a/src/test/ui/rustc_must_implement_one_of.rs b/src/test/ui/rustc_must_implement_one_of.rs new file mode 100644 index 0000000000000..f98d3bdc3fa5e --- /dev/null +++ b/src/test/ui/rustc_must_implement_one_of.rs @@ -0,0 +1,44 @@ +#![feature(rustc_attrs)] + +#[rustc_must_implement_one_of(eq, neq)] +trait Equal { + fn eq(&self, other: &Self) -> bool { + !self.neq(other) + } + + fn neq(&self, other: &Self) -> bool { + !self.eq(other) + } +} + +struct T0; +struct T1; +struct T2; +struct T3; + +impl Equal for T0 { + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl Equal for T1 { + fn neq(&self, _other: &Self) -> bool { + false + } +} + +impl Equal for T2 { + fn eq(&self, _other: &Self) -> bool { + true + } + + fn neq(&self, _other: &Self) -> bool { + false + } +} + +impl Equal for T3 {} +//~^ not all trait items implemented, missing one of: `neq`, `eq` + +fn main() {} diff --git a/src/test/ui/rustc_must_implement_one_of.stderr b/src/test/ui/rustc_must_implement_one_of.stderr new file mode 100644 index 0000000000000..53c4e71cf8b43 --- /dev/null +++ b/src/test/ui/rustc_must_implement_one_of.stderr @@ -0,0 +1,15 @@ +error[E0046]: not all trait items implemented, missing one of: `neq`, `eq` + --> $DIR/rustc_must_implement_one_of.rs:41:1 + | +LL | impl Equal for T3 {} + | ^^^^^^^^^^^^^^^^^ missing one of `neq`, `eq` in implementation + | +note: required because of this annotation + --> $DIR/rustc_must_implement_one_of.rs:3:1 + | +LL | #[rustc_must_implement_one_of(eq, neq)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0046`. From e6bc0ac6f365cca5aaec5cf7fe8d6adb15e973a7 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 3 Jan 2022 16:42:13 +0300 Subject: [PATCH 2/9] Add test for misused `#[rustc_must_implement_one_of]` --- .../ui/rustc_must_implement_one_of_misuse.rs | 38 +++++++++ .../rustc_must_implement_one_of_misuse.stderr | 82 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/test/ui/rustc_must_implement_one_of_misuse.rs create mode 100644 src/test/ui/rustc_must_implement_one_of_misuse.stderr diff --git a/src/test/ui/rustc_must_implement_one_of_misuse.rs b/src/test/ui/rustc_must_implement_one_of_misuse.rs new file mode 100644 index 0000000000000..161e94273f8cb --- /dev/null +++ b/src/test/ui/rustc_must_implement_one_of_misuse.rs @@ -0,0 +1,38 @@ +#![feature(rustc_attrs)] + +#[rustc_must_implement_one_of(a, b)] +//~^ Method not found in this trait +//~| Method not found in this trait +trait Tr0 {} + +#[rustc_must_implement_one_of(a, b)] +//~^ Method not found in this trait +trait Tr1 { + fn a() {} +} + +#[rustc_must_implement_one_of(a)] +//~^ the `#[rustc_must_implement_one_of]` attribute must be used with at least 2 args +trait Tr2 { + fn a() {} +} + +#[rustc_must_implement_one_of] +//~^ malformed `rustc_must_implement_one_of` attribute input +trait Tr3 {} + +#[rustc_must_implement_one_of(A, B)] +trait Tr4 { + const A: u8 = 1; //~ Not a method + + type B; //~ Not a method +} + +#[rustc_must_implement_one_of(a, b)] +trait Tr5 { + fn a(); //~ This method doesn't have a default implementation + + fn b(); //~ This method doesn't have a default implementation +} + +fn main() {} diff --git a/src/test/ui/rustc_must_implement_one_of_misuse.stderr b/src/test/ui/rustc_must_implement_one_of_misuse.stderr new file mode 100644 index 0000000000000..b5428cf331028 --- /dev/null +++ b/src/test/ui/rustc_must_implement_one_of_misuse.stderr @@ -0,0 +1,82 @@ +error: malformed `rustc_must_implement_one_of` attribute input + --> $DIR/rustc_must_implement_one_of_misuse.rs:20:1 + | +LL | #[rustc_must_implement_one_of] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_must_implement_one_of(method1, method2, ...)]` + +error: Method not found in this trait + --> $DIR/rustc_must_implement_one_of_misuse.rs:3:31 + | +LL | #[rustc_must_implement_one_of(a, b)] + | ^ + +error: Method not found in this trait + --> $DIR/rustc_must_implement_one_of_misuse.rs:3:34 + | +LL | #[rustc_must_implement_one_of(a, b)] + | ^ + +error: Method not found in this trait + --> $DIR/rustc_must_implement_one_of_misuse.rs:8:34 + | +LL | #[rustc_must_implement_one_of(a, b)] + | ^ + +error: the `#[rustc_must_implement_one_of]` attribute must be used with at least 2 args + --> $DIR/rustc_must_implement_one_of_misuse.rs:14:1 + | +LL | #[rustc_must_implement_one_of(a)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Not a method + --> $DIR/rustc_must_implement_one_of_misuse.rs:26:5 + | +LL | const A: u8 = 1; + | ^^^^^^^^^^^^^^^^ + | +note: required by this annotation + --> $DIR/rustc_must_implement_one_of_misuse.rs:24:1 + | +LL | #[rustc_must_implement_one_of(A, B)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: All `#[rustc_must_implement_one_of]` arguments must be method identifiers + +error: Not a method + --> $DIR/rustc_must_implement_one_of_misuse.rs:28:5 + | +LL | type B; + | ^^^^^^^ + | +note: required by this annotation + --> $DIR/rustc_must_implement_one_of_misuse.rs:24:1 + | +LL | #[rustc_must_implement_one_of(A, B)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: All `#[rustc_must_implement_one_of]` arguments must be method identifiers + +error: This method doesn't have a default implementation + --> $DIR/rustc_must_implement_one_of_misuse.rs:33:5 + | +LL | fn a(); + | ^^^^^^^ + | +note: required by this annotation + --> $DIR/rustc_must_implement_one_of_misuse.rs:31:1 + | +LL | #[rustc_must_implement_one_of(a, b)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: This method doesn't have a default implementation + --> $DIR/rustc_must_implement_one_of_misuse.rs:35:5 + | +LL | fn b(); + | ^^^^^^^ + | +note: required by this annotation + --> $DIR/rustc_must_implement_one_of_misuse.rs:31:1 + | +LL | #[rustc_must_implement_one_of(a, b)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 9 previous errors + From 268ae9a232372dcdff59830310d353d8539acb35 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 3 Jan 2022 16:48:18 +0300 Subject: [PATCH 3/9] Move `#[rustc_must_implement_one_of]` tests to fix tidy check --- .../ui/{ => traits/default-method}/rustc_must_implement_one_of.rs | 0 .../default-method}/rustc_must_implement_one_of.stderr | 0 .../default-method}/rustc_must_implement_one_of_misuse.rs | 0 .../default-method}/rustc_must_implement_one_of_misuse.stderr | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/test/ui/{ => traits/default-method}/rustc_must_implement_one_of.rs (100%) rename src/test/ui/{ => traits/default-method}/rustc_must_implement_one_of.stderr (100%) rename src/test/ui/{ => traits/default-method}/rustc_must_implement_one_of_misuse.rs (100%) rename src/test/ui/{ => traits/default-method}/rustc_must_implement_one_of_misuse.stderr (100%) diff --git a/src/test/ui/rustc_must_implement_one_of.rs b/src/test/ui/traits/default-method/rustc_must_implement_one_of.rs similarity index 100% rename from src/test/ui/rustc_must_implement_one_of.rs rename to src/test/ui/traits/default-method/rustc_must_implement_one_of.rs diff --git a/src/test/ui/rustc_must_implement_one_of.stderr b/src/test/ui/traits/default-method/rustc_must_implement_one_of.stderr similarity index 100% rename from src/test/ui/rustc_must_implement_one_of.stderr rename to src/test/ui/traits/default-method/rustc_must_implement_one_of.stderr diff --git a/src/test/ui/rustc_must_implement_one_of_misuse.rs b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs similarity index 100% rename from src/test/ui/rustc_must_implement_one_of_misuse.rs rename to src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs diff --git a/src/test/ui/rustc_must_implement_one_of_misuse.stderr b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr similarity index 100% rename from src/test/ui/rustc_must_implement_one_of_misuse.stderr rename to src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr From 96b2f8ac3268f0aee4d10868f9e31a5f7b66641b Mon Sep 17 00:00:00 2001 From: Waffle Maybe Date: Tue, 4 Jan 2022 13:21:56 +0300 Subject: [PATCH 4/9] Apply suggestions from code review Use "(associated) function" terminology instead of "method". Co-authored-by: Daniel Henry-Mantilla --- compiler/rustc_feature/src/builtin_attrs.rs | 2 +- compiler/rustc_middle/src/ty/trait_def.rs | 2 +- compiler/rustc_typeck/src/collect.rs | 14 +++++++------ .../rustc_must_implement_one_of_misuse.rs | 14 ++++++------- .../rustc_must_implement_one_of_misuse.stderr | 20 +++++++++---------- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 6f119d5ab8892..3e1605aabfdc2 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -678,7 +678,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ from method dispatch when the receiver is an array, for compatibility in editions < 2021." ), rustc_attr!( - rustc_must_implement_one_of, Normal, template!(List: "method1, method2, ..."), ErrorFollowing, + rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."), ErrorFollowing, "the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \ definition of a trait, it's currently in experimental form and should be changed before \ being exposed outside of the std" diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index c4c75434980d2..9f8053d4a4eac 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -45,7 +45,7 @@ pub struct TraitDef { /// recomputed all the time. pub def_path_hash: DefPathHash, - /// List of methods from `#[rustc_must_implement_one_of]` attribute one of which + /// List of functions from `#[rustc_must_implement_one_of]` attribute one of which /// must be implemented. pub must_implement_one_of: Option>, } diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs index 50613ef7104da..b95a2b6fa498c 100644 --- a/compiler/rustc_typeck/src/collect.rs +++ b/compiler/rustc_typeck/src/collect.rs @@ -1253,7 +1253,9 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { .map(|item| item.ident().ok_or(item.span())) .collect::, _>>() .map_err(|span| { - tcx.sess.struct_span_err(span, "must be an identifier of a method").emit(); + tcx.sess + .struct_span_err(span, "must be a name of an associated function") + .emit(); }) .ok() .zip(Some(attr.span)), @@ -1261,7 +1263,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { None => None, }) // Check that all arguments of `#[rustc_must_implement_one_of]` reference - // methods in the trait with default implementations + // functions in the trait with default implementations .and_then(|(list, attr_span)| { let errors = list.iter().filter_map(|ident| { let item = items.iter().find(|item| item.ident == *ident); @@ -1272,7 +1274,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { tcx.sess .struct_span_err( item.span, - "This method doesn't have a default implementation", + "This function doesn't have a default implementation", ) .span_note(attr_span, "required by this annotation") .emit(); @@ -1284,16 +1286,16 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { } Some(item) => tcx .sess - .struct_span_err(item.span, "Not a method") + .struct_span_err(item.span, "Not a function") .span_note(attr_span, "required by this annotation") .note( "All `#[rustc_must_implement_one_of]` arguments \ - must be method identifiers", + must be associated function names", ) .emit(), None => tcx .sess - .struct_span_err(ident.span, "Method not found in this trait") + .struct_span_err(ident.span, "Function not found in this trait") .emit(), } diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs index 161e94273f8cb..1089e5f9c4ab9 100644 --- a/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs +++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs @@ -1,12 +1,12 @@ #![feature(rustc_attrs)] #[rustc_must_implement_one_of(a, b)] -//~^ Method not found in this trait -//~| Method not found in this trait +//~^ Function not found in this trait +//~| Function not found in this trait trait Tr0 {} #[rustc_must_implement_one_of(a, b)] -//~^ Method not found in this trait +//~^ Function not found in this trait trait Tr1 { fn a() {} } @@ -23,16 +23,16 @@ trait Tr3 {} #[rustc_must_implement_one_of(A, B)] trait Tr4 { - const A: u8 = 1; //~ Not a method + const A: u8 = 1; //~ Not a function - type B; //~ Not a method + type B; //~ Not a function } #[rustc_must_implement_one_of(a, b)] trait Tr5 { - fn a(); //~ This method doesn't have a default implementation + fn a(); //~ This function doesn't have a default implementation - fn b(); //~ This method doesn't have a default implementation + fn b(); //~ This function doesn't have a default implementation } fn main() {} diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr index b5428cf331028..74a6dc8fec97c 100644 --- a/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr +++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr @@ -2,21 +2,21 @@ error: malformed `rustc_must_implement_one_of` attribute input --> $DIR/rustc_must_implement_one_of_misuse.rs:20:1 | LL | #[rustc_must_implement_one_of] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_must_implement_one_of(method1, method2, ...)]` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_must_implement_one_of(function1, function2, ...)]` -error: Method not found in this trait +error: Function not found in this trait --> $DIR/rustc_must_implement_one_of_misuse.rs:3:31 | LL | #[rustc_must_implement_one_of(a, b)] | ^ -error: Method not found in this trait +error: Function not found in this trait --> $DIR/rustc_must_implement_one_of_misuse.rs:3:34 | LL | #[rustc_must_implement_one_of(a, b)] | ^ -error: Method not found in this trait +error: Function not found in this trait --> $DIR/rustc_must_implement_one_of_misuse.rs:8:34 | LL | #[rustc_must_implement_one_of(a, b)] @@ -28,7 +28,7 @@ error: the `#[rustc_must_implement_one_of]` attribute must be used with at least LL | #[rustc_must_implement_one_of(a)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: Not a method +error: Not a function --> $DIR/rustc_must_implement_one_of_misuse.rs:26:5 | LL | const A: u8 = 1; @@ -39,9 +39,9 @@ note: required by this annotation | LL | #[rustc_must_implement_one_of(A, B)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: All `#[rustc_must_implement_one_of]` arguments must be method identifiers + = note: All `#[rustc_must_implement_one_of]` arguments must be associated function names -error: Not a method +error: Not a function --> $DIR/rustc_must_implement_one_of_misuse.rs:28:5 | LL | type B; @@ -52,9 +52,9 @@ note: required by this annotation | LL | #[rustc_must_implement_one_of(A, B)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: All `#[rustc_must_implement_one_of]` arguments must be method identifiers + = note: All `#[rustc_must_implement_one_of]` arguments must be associated function names -error: This method doesn't have a default implementation +error: This function doesn't have a default implementation --> $DIR/rustc_must_implement_one_of_misuse.rs:33:5 | LL | fn a(); @@ -66,7 +66,7 @@ note: required by this annotation LL | #[rustc_must_implement_one_of(a, b)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: This method doesn't have a default implementation +error: This function doesn't have a default implementation --> $DIR/rustc_must_implement_one_of_misuse.rs:35:5 | LL | fn b(); From f64daff0c6e400819c997c888ac2aca32168dd53 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 10 Jan 2022 12:54:04 +0300 Subject: [PATCH 5/9] Fix `#[rustc_must_implement_one_of]` This adds the old, pre 90639 `is_implemented` that previously only was true if the implementation of the item was from the given impl block and not from the trait default. --- compiler/rustc_typeck/src/check/check.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs index ff1031595662d..a49c6bd2e8038 100644 --- a/compiler/rustc_typeck/src/check/check.rs +++ b/compiler/rustc_typeck/src/check/check.rs @@ -993,10 +993,16 @@ fn check_impl_items_against_trait<'tcx>( } if let Some(required_items) = &must_implement_one_of { - let trait_item = tcx.associated_item(trait_item_id); - - if is_implemented && required_items.contains(&trait_item.ident) { - must_implement_one_of = None; + // true if this item is specifically implemented in this impl + let is_implemented_here = ancestors + .leaf_def(tcx, trait_item_id) + .map_or(false, |node_item| !node_item.defining_node.is_from_trait()); + + if is_implemented_here { + let trait_item = tcx.associated_item(trait_item_id); + if required_items.contains(&trait_item.ident) { + must_implement_one_of = None; + } } } } From 4ccfa97021abbe228dbf9dcc36c76ce7489b08b3 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 11 Jan 2022 03:39:43 +0300 Subject: [PATCH 6/9] Add a test for ungated `#[rustc_must_implement_one_of]` This test checks that `#[rustc_must_implement_one_of]` is gated behind `#![feature(rustc_attrs)]`. --- .../rustc_must_implement_one_of_gated.rs | 13 +++++++++++++ .../rustc_must_implement_one_of_gated.stderr | 11 +++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/test/ui/traits/default-method/rustc_must_implement_one_of_gated.rs create mode 100644 src/test/ui/traits/default-method/rustc_must_implement_one_of_gated.stderr diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of_gated.rs b/src/test/ui/traits/default-method/rustc_must_implement_one_of_gated.rs new file mode 100644 index 0000000000000..ec2995872de02 --- /dev/null +++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of_gated.rs @@ -0,0 +1,13 @@ +#[rustc_must_implement_one_of(eq, neq)] +//~^ the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete definition of a trait, it's currently in experimental form and should be changed before being exposed outside of the std +trait Equal { + fn eq(&self, other: &Self) -> bool { + !self.neq(other) + } + + fn neq(&self, other: &Self) -> bool { + !self.eq(other) + } +} + +fn main() {} diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of_gated.stderr b/src/test/ui/traits/default-method/rustc_must_implement_one_of_gated.stderr new file mode 100644 index 0000000000000..228bc3e35c21d --- /dev/null +++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of_gated.stderr @@ -0,0 +1,11 @@ +error[E0658]: the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete definition of a trait, it's currently in experimental form and should be changed before being exposed outside of the std + --> $DIR/rustc_must_implement_one_of_gated.rs:1:1 + | +LL | #[rustc_must_implement_one_of(eq, neq)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. From c30ec5a6fd3ba5e742d14d0a2089b04e43ee4e32 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 14 Jan 2022 16:38:47 +0300 Subject: [PATCH 7/9] Check for duplicate arguments in `#[rustc_must_implement_one_of]` --- compiler/rustc_typeck/src/collect.rs | 26 ++++++++++++++ .../rustc_must_implement_one_of_duplicates.rs | 19 +++++++++++ ...tc_must_implement_one_of_duplicates.stderr | 34 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 src/test/ui/traits/default-method/rustc_must_implement_one_of_duplicates.rs create mode 100644 src/test/ui/traits/default-method/rustc_must_implement_one_of_duplicates.stderr diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs index b95a2b6fa498c..23b328b918622 100644 --- a/compiler/rustc_typeck/src/collect.rs +++ b/compiler/rustc_typeck/src/collect.rs @@ -1303,6 +1303,32 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { }); (errors.count() == 0).then_some(list) + }) + // Check for duplicates + .and_then(|list| { + let mut set: FxHashSet<&Ident> = FxHashSet::default(); + let mut no_dups = true; + + for ident in &*list { + if let Some(dup) = set.replace(ident) { + let dup2 = set.get(&dup).copied().unwrap(); // We've just inserted it + + tcx.sess + .struct_span_err( + vec![dup.span, dup2.span], + "Functions names are duplicated", + ) + .note( + "All `#[rustc_must_implement_one_of]` arguments \ + must be unique", + ) + .emit(); + + no_dups = false; + } + } + + no_dups.then_some(list) }); ty::TraitDef::new( diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of_duplicates.rs b/src/test/ui/traits/default-method/rustc_must_implement_one_of_duplicates.rs new file mode 100644 index 0000000000000..56e8fcff0fce5 --- /dev/null +++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of_duplicates.rs @@ -0,0 +1,19 @@ +#![feature(rustc_attrs)] + +#[rustc_must_implement_one_of(a, a)] +//~^ Functions names are duplicated +trait Trait { + fn a() {} +} + +#[rustc_must_implement_one_of(b, a, a, c, b, c)] +//~^ Functions names are duplicated +//~| Functions names are duplicated +//~| Functions names are duplicated +trait Trait1 { + fn a() {} + fn b() {} + fn c() {} +} + +fn main() {} diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of_duplicates.stderr b/src/test/ui/traits/default-method/rustc_must_implement_one_of_duplicates.stderr new file mode 100644 index 0000000000000..777beba618277 --- /dev/null +++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of_duplicates.stderr @@ -0,0 +1,34 @@ +error: Functions names are duplicated + --> $DIR/rustc_must_implement_one_of_duplicates.rs:3:31 + | +LL | #[rustc_must_implement_one_of(a, a)] + | ^ ^ + | + = note: All `#[rustc_must_implement_one_of]` arguments must be unique + +error: Functions names are duplicated + --> $DIR/rustc_must_implement_one_of_duplicates.rs:9:34 + | +LL | #[rustc_must_implement_one_of(b, a, a, c, b, c)] + | ^ ^ + | + = note: All `#[rustc_must_implement_one_of]` arguments must be unique + +error: Functions names are duplicated + --> $DIR/rustc_must_implement_one_of_duplicates.rs:9:31 + | +LL | #[rustc_must_implement_one_of(b, a, a, c, b, c)] + | ^ ^ + | + = note: All `#[rustc_must_implement_one_of]` arguments must be unique + +error: Functions names are duplicated + --> $DIR/rustc_must_implement_one_of_duplicates.rs:9:40 + | +LL | #[rustc_must_implement_one_of(b, a, a, c, b, c)] + | ^ ^ + | + = note: All `#[rustc_must_implement_one_of]` arguments must be unique + +error: aborting due to 4 previous errors + From f9174e1d80c4672c288f59aa490a28d3463fd131 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 14 Jan 2022 16:52:30 +0300 Subject: [PATCH 8/9] Do not use `HashSet` for `#[rustc_must_implement_one_of]` --- compiler/rustc_typeck/src/check/check.rs | 6 +++--- compiler/rustc_typeck/src/check/mod.rs | 2 +- .../ui/traits/default-method/rustc_must_implement_one_of.rs | 2 +- .../default-method/rustc_must_implement_one_of.stderr | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs index a49c6bd2e8038..88862ea83fded 100644 --- a/compiler/rustc_typeck/src/check/check.rs +++ b/compiler/rustc_typeck/src/check/check.rs @@ -980,8 +980,8 @@ fn check_impl_items_against_trait<'tcx>( // Check for missing items from trait let mut missing_items = Vec::new(); - let mut must_implement_one_of: Option> = - trait_def.must_implement_one_of.as_deref().map(|slice| slice.iter().copied().collect()); + let mut must_implement_one_of: Option<&[Ident]> = + trait_def.must_implement_one_of.as_deref(); for &trait_item_id in tcx.associated_item_def_ids(impl_trait_ref.def_id) { let is_implemented = ancestors @@ -1020,7 +1020,7 @@ fn check_impl_items_against_trait<'tcx>( .find(|attr| attr.has_name(sym::rustc_must_implement_one_of)) .map(|attr| attr.span); - missing_items_must_implement_one_of_err(tcx, impl_span, &missing_items, attr_span); + missing_items_must_implement_one_of_err(tcx, impl_span, missing_items, attr_span); } } } diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs index 17c4bc1b3014f..b26c4c89d6f2f 100644 --- a/compiler/rustc_typeck/src/check/mod.rs +++ b/compiler/rustc_typeck/src/check/mod.rs @@ -644,7 +644,7 @@ fn missing_items_err( fn missing_items_must_implement_one_of_err( tcx: TyCtxt<'_>, impl_span: Span, - missing_items: &FxHashSet, + missing_items: &[Ident], annotation_span: Option, ) { let missing_items_msg = diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of.rs b/src/test/ui/traits/default-method/rustc_must_implement_one_of.rs index f98d3bdc3fa5e..5ba2f5ce33426 100644 --- a/src/test/ui/traits/default-method/rustc_must_implement_one_of.rs +++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of.rs @@ -39,6 +39,6 @@ impl Equal for T2 { } impl Equal for T3 {} -//~^ not all trait items implemented, missing one of: `neq`, `eq` +//~^ not all trait items implemented, missing one of: `eq`, `neq` fn main() {} diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of.stderr b/src/test/ui/traits/default-method/rustc_must_implement_one_of.stderr index 53c4e71cf8b43..5a4dd1388b2e0 100644 --- a/src/test/ui/traits/default-method/rustc_must_implement_one_of.stderr +++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of.stderr @@ -1,8 +1,8 @@ -error[E0046]: not all trait items implemented, missing one of: `neq`, `eq` +error[E0046]: not all trait items implemented, missing one of: `eq`, `neq` --> $DIR/rustc_must_implement_one_of.rs:41:1 | LL | impl Equal for T3 {} - | ^^^^^^^^^^^^^^^^^ missing one of `neq`, `eq` in implementation + | ^^^^^^^^^^^^^^^^^ missing one of `eq`, `neq` in implementation | note: required because of this annotation --> $DIR/rustc_must_implement_one_of.rs:3:1 From 28edd7ac090d621ad8fd0ebe220d897d9df386a9 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Sat, 15 Jan 2022 02:04:58 +0300 Subject: [PATCH 9/9] Use `Symbol` for dup check in `#[rustc_must_implement_one_of]` --- compiler/rustc_typeck/src/collect.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs index 23b328b918622..84315cb682718 100644 --- a/compiler/rustc_typeck/src/collect.rs +++ b/compiler/rustc_typeck/src/collect.rs @@ -1306,18 +1306,13 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef { }) // Check for duplicates .and_then(|list| { - let mut set: FxHashSet<&Ident> = FxHashSet::default(); + let mut set: FxHashMap = FxHashMap::default(); let mut no_dups = true; for ident in &*list { - if let Some(dup) = set.replace(ident) { - let dup2 = set.get(&dup).copied().unwrap(); // We've just inserted it - + if let Some(dup) = set.insert(ident.name, ident.span) { tcx.sess - .struct_span_err( - vec![dup.span, dup2.span], - "Functions names are duplicated", - ) + .struct_span_err(vec![dup, ident.span], "Functions names are duplicated") .note( "All `#[rustc_must_implement_one_of]` arguments \ must be unique",