Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a MustUse trait to complement #[must_use] #71816

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/libcore/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1446,4 +1446,13 @@ pub(crate) mod builtin {
pub macro RustcEncodable($item:item) {
/* compiler built-in */
}

/// Indicates that a value must be explicitly consumed by the user.
#[cfg(not(bootstrap))]
#[rustc_builtin_macro]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable(rustc_attrs, must_use_trait)]
pub macro must_use($item:item) {
/* compiler built-in */
}
}
10 changes: 10 additions & 0 deletions src/libcore/marker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,16 @@ impl<T: ?Sized> Unpin for *const T {}
#[stable(feature = "pin_raw", since = "1.38.0")]
impl<T: ?Sized> Unpin for *mut T {}

/// Indicates that a type must be consumed explicitly.
///
/// cf. `#[must_use]`
#[unstable(feature = "must_use_trait", issue = "none")]
#[cfg_attr(not(bootstrap), lang = "must_use")]
pub trait MustUse {
/// An explanation for why values of this type must be used.
const REASON: &'static str = "";
}

/// Implementations of `Copy` for primitive types.
///
/// Implementations that cannot be described in Rust
Expand Down
5 changes: 5 additions & 0 deletions src/libcore/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,8 @@ pub use crate::macros::builtin::{
)]
#[doc(no_inline)]
pub use crate::macros::builtin::cfg_accessible;

#[cfg(not(bootstrap))]
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
#[doc(no_inline)]
pub use crate::macros::builtin::must_use;
2 changes: 2 additions & 0 deletions src/librustc_builtin_macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mod global_allocator;
mod global_asm;
mod llvm_asm;
mod log_syntax;
mod must_use;
mod source_util;
mod test;
mod trace_macros;
Expand Down Expand Up @@ -92,6 +93,7 @@ pub fn register_builtin_macros(resolver: &mut dyn Resolver, edition: Edition) {
global_allocator: global_allocator::expand,
test: test::expand_test,
test_case: test::expand_test_case,
must_use: must_use::expand,
}

register_derive! {
Expand Down
186 changes: 186 additions & 0 deletions src/librustc_builtin_macros/must_use.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
use ast::{AttrStyle, Ident, MacArgs};
use rustc_ast::{ast, ptr::P, tokenstream::TokenStream};
use rustc_attr::{mk_attr, HasAttrs};
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_feature::BUILTIN_ATTRIBUTE_MAP;
use rustc_parse::validate_attr;
use rustc_span::{sym, symbol::kw, Span};
use std::iter;

pub fn expand(
ecx: &mut ExtCtxt<'_>,
span: Span,
meta: &ast::MetaItem,
mut item: Annotatable,
) -> Vec<Annotatable> {
// Validate input against the `#[rustc_must_use]` template.
let (_, _, template, _) = &BUILTIN_ATTRIBUTE_MAP[&sym::rustc_must_use];
let attr = ecx.attribute(meta.clone());
validate_attr::check_builtin_attribute(ecx.parse_sess, &attr, sym::must_use, template.clone());

let reason = meta.name_value_literal();
let mac_args = match reason {
None => MacArgs::Empty,
Some(lit) => MacArgs::Eq(span, TokenStream::new(vec![lit.token_tree().into()])),
};

// def-site context makes rustc accept the unstable `rustc_must_use` and `MustUse` trait.
let def_span = ecx.with_def_site_ctxt(item.span());

// Put a `#[rustc_must_use]` on the item in any case. This allows rustdoc to pick it up and
// render it.
item.visit_attrs(|attrs| {
attrs.push(mk_attr(
AttrStyle::Outer,
ast::Path::from_ident(Ident::with_dummy_span(sym::rustc_must_use)),
mac_args,
def_span,
));
});

// This macro desugars `#[must_use]` on types to `impl`s of the `MustUse` trait.
// Any other uses are forwarded to the `#[rustc_must_use]` macro.
if let Annotatable::Item(ast_item) = &item {
match &ast_item.kind {
ast::ItemKind::Enum(_, generics)
| ast::ItemKind::Struct(_, generics)
| ast::ItemKind::Union(_, generics) => {
// Generate a derive-style impl for a concrete type.
let impl_items = if let Some(reason) = reason {
let item = ast::AssocItem {
attrs: vec![],
id: ast::DUMMY_NODE_ID,
span: def_span,
vis: ast::Visibility {
node: ast::VisibilityKind::Inherited,
span: def_span,
},
ident: Ident::new(sym::REASON, def_span),
kind: ast::AssocItemKind::Const(
ast::Defaultness::Final,
ecx.ty(
def_span,
ast::TyKind::Rptr(
Some(ecx.lifetime(
def_span,
Ident::new(kw::StaticLifetime, def_span),
)),
ecx.ty_mt(
ecx.ty_ident(def_span, Ident::new(sym::str, def_span)),
ast::Mutability::Not,
),
),
),
Some(ecx.expr_lit(def_span, reason.kind.clone())),
),
tokens: None,
};

vec![P(item)]
} else {
vec![]
};

let mut impl_generics = generics.clone();
for param in impl_generics.params.iter_mut() {
match &mut param.kind {
ast::GenericParamKind::Type { default } => {
// Delete defaults as they're not usable in impls.
*default = None;
}

ast::GenericParamKind::Lifetime
| ast::GenericParamKind::Const { ty: _ } => {}
}
}
let new_impl = ast::ItemKind::Impl {
unsafety: ast::Unsafe::No,
polarity: ast::ImplPolarity::Positive,
defaultness: ast::Defaultness::Final,
constness: ast::Const::No,
generics: impl_generics,
of_trait: Some(ecx.trait_ref(ecx.path(
def_span,
vec![
Ident::new(kw::DollarCrate, def_span),
Ident::new(sym::marker, def_span),
Ident::new(sym::MustUse, def_span),
],
))),
self_ty: ecx.ty_path(
ecx.path_all(
def_span,
false,
vec![ast_item.ident],
generics
.params
.iter()
.map(|param| match &param.kind {
ast::GenericParamKind::Lifetime => ast::GenericArg::Lifetime(
ecx.lifetime(def_span, param.ident),
),
ast::GenericParamKind::Type { default: _ } => {
ast::GenericArg::Type(ecx.ty_ident(def_span, param.ident))
}
ast::GenericParamKind::Const { ty: _ } => {
ast::GenericArg::Const(
ecx.const_ident(def_span, param.ident),
)
}
})
.collect(),
),
),
items: impl_items,
};

// Copy some important attributes from the original item, just like the built-in
// derives.
let attrs = ast_item
.attrs
.iter()
.filter(|a| {
[sym::allow, sym::warn, sym::deny, sym::forbid, sym::stable, sym::unstable]
.contains(&a.name_or_empty())
})
.cloned()
.chain(iter::once(
ecx.attribute(ecx.meta_word(def_span, sym::automatically_derived)),
))
.collect();

let new_impl =
Annotatable::Item(ecx.item(def_span, Ident::invalid(), attrs, new_impl));

return vec![item, new_impl];
}

ast::ItemKind::Trait(_, _, _, _, _) => {
// Generate a blanket `impl<T: Trait> MustUse for T` impl.
// FIXME(jschievink): This currently doesn't work due to overlapping impls. Even
// with specialization, you couldn't implement 2 `#[must_use]` traits for the same
// type.
// Technically, we could permit overlapping impls here, since it cannot lead to
// unsoundness. At worst, the lint will only print one of the applicable messages
// (though it could attempt to collect messages from all impls that are known to
// apply).
}

ast::ItemKind::TraitAlias(_, _)
| ast::ItemKind::Impl { .. }
| ast::ItemKind::MacCall(..)
| ast::ItemKind::MacroDef(..)
| ast::ItemKind::ExternCrate(..)
| ast::ItemKind::Use(..)
| ast::ItemKind::Static(..)
| ast::ItemKind::Const(..)
| ast::ItemKind::Fn(..)
| ast::ItemKind::Mod(..)
| ast::ItemKind::ForeignMod(..)
| ast::ItemKind::GlobalAsm(..)
| ast::ItemKind::TyAlias(..) => {}
}
}

vec![item]
}
1 change: 1 addition & 0 deletions src/librustc_expand/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
if !self.cx.ecfg.custom_inner_attributes()
&& attr.style == ast::AttrStyle::Inner
&& !attr.has_name(sym::test)
&& !attr.has_name(sym::must_use)
{
feature_err(
&self.cx.parse_sess,
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_feature/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
ungated!(allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
ungated!(forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
ungated!(deny, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
ungated!(must_use, Whitelisted, template!(Word, NameValueStr: "reason")),
// FIXME(#14407)
ungated!(
deprecated, Normal,
Expand Down Expand Up @@ -440,6 +439,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
// Internal attributes, Diagnostics related:
// ==========================================================================

rustc_attr!(rustc_must_use, Whitelisted, template!(Word, NameValueStr: "reason"), IMPL_DETAIL),
rustc_attr!(
rustc_on_unimplemented, Whitelisted,
template!(
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_hir/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,6 @@ language_item_table! {
AlignOffsetLangItem, "align_offset", align_offset_fn, Target::Fn;

TerminationTraitLangItem, "termination", termination, Target::Trait;

MustUseTraitLangItem, "must_use", must_use_trait, Target::Trait;
}
Loading