Skip to content

Commit

Permalink
Rollup merge of rust-lang#92164 - WaffleLapkin:rustc_must_implement_o…
Browse files Browse the repository at this point in the history
…ne_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 :)
  • Loading branch information
matthiaskrgr authored Jan 12, 2022
2 parents 219c5d3 + 4ccfa97 commit faf8d66
Show file tree
Hide file tree
Showing 15 changed files with 357 additions and 4 deletions.
6 changes: 6 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: "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"
),

// ==========================================================================
// Internal attributes, Testing:
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_metadata/src/rmeta/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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"),
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,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))
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_metadata/src/rmeta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Box<[Ident]>>,
}

#[derive(TyEncodable, TyDecodable)]
Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_middle/src/ty/trait_def.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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 functions from `#[rustc_must_implement_one_of]` attribute one of which
/// must be implemented.
pub must_implement_one_of: Option<Box<[Ident]>>,
}

/// Whether this trait is treated specially by the standard library
Expand Down Expand Up @@ -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<Box<[Ident]>>,
) -> TraitDef {
TraitDef {
def_id,
Expand All @@ -97,6 +102,7 @@ impl<'tcx> TraitDef {
skip_array_during_method_dispatch,
specialization_kind,
def_path_hash,
must_implement_one_of,
}
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
29 changes: 29 additions & 0 deletions compiler/rustc_typeck/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,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<FxHashSet<Ident>> =
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)
Expand All @@ -991,12 +995,37 @@ 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 {
// 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;
}
}
}
}

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);
}
}
}

Expand Down
25 changes: 25 additions & 0 deletions compiler/rustc_typeck/src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Ident>,
annotation_span: Option<Span>,
) {
let missing_items_msg =
missing_items.iter().map(Ident::to_string).collect::<Vec<_>>().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>,
Expand Down
85 changes: 82 additions & 3 deletions compiler/rustc_typeck/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1197,9 +1197,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"),
};

Expand All @@ -1226,6 +1228,82 @@ 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::<Result<Box<[_]>, _>>()
.map_err(|span| {
tcx.sess
.struct_span_err(span, "must be a name of an associated function")
.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
// 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);

match item {
Some(item) if matches!(item.kind, hir::AssocItemKind::Fn { .. }) => {
if !item.defaultness.has_value() {
tcx.sess
.struct_span_err(
item.span,
"This function 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 function")
.span_note(attr_span, "required by this annotation")
.note(
"All `#[rustc_must_implement_one_of]` arguments \
must be associated function names",
)
.emit(),
None => tcx
.sess
.struct_span_err(ident.span, "Function not found in this trait")
.emit(),
}

Some(())
});

(errors.count() == 0).then_some(list)
});

ty::TraitDef::new(
def_id,
unsafety,
Expand All @@ -1235,6 +1313,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,
)
}

Expand Down
44 changes: 44 additions & 0 deletions src/test/ui/traits/default-method/rustc_must_implement_one_of.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.
Loading

0 comments on commit faf8d66

Please sign in to comment.