Skip to content

Commit

Permalink
Rollup merge of rust-lang#87027 - petrochenkov:builderhelp, r=oli-obk
Browse files Browse the repository at this point in the history
expand: Support helper attributes for built-in derive macros

This is needed for rust-lang#86735 (derive macro `Default` should have a helper attribute `default`).

With this PR we can specify helper attributes for built-in derives using syntax `#[rustc_builtin_macro(MacroName, attributes(attr1, attr2, ...))]` which mirrors equivalent syntax for proc macros `#[proc_macro_derive(MacroName, attributes(attr1, attr2, ...))]`.
Otherwise expansion infra was already ready for this.
The attribute parsing code is shared between proc macro derives and built-in macros (`fn parse_macro_name_and_helper_attrs`).
  • Loading branch information
GuillaumeGomez authored Jul 14, 2021
2 parents ee5ed4a + 6c9ea1e commit 4d141f5
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 81 deletions.
83 changes: 7 additions & 76 deletions compiler/rustc_builtin_macros/src/proc_macro_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rustc_ast::ptr::P;
use rustc_ast::visit::{self, Visitor};
use rustc_ast::{self as ast, NodeId};
use rustc_ast_pretty::pprust;
use rustc_expand::base::{ExtCtxt, ResolverExpand};
use rustc_expand::base::{parse_macro_name_and_helper_attrs, ExtCtxt, ResolverExpand};
use rustc_expand::expand::{AstFragment, ExpansionConfig};
use rustc_session::Session;
use rustc_span::hygiene::AstPass;
Expand Down Expand Up @@ -109,86 +109,17 @@ impl<'a> CollectProcMacros<'a> {
}

fn collect_custom_derive(&mut self, item: &'a ast::Item, attr: &'a ast::Attribute) {
// Once we've located the `#[proc_macro_derive]` attribute, verify
// that it's of the form `#[proc_macro_derive(Foo)]` or
// `#[proc_macro_derive(Foo, attributes(A, ..))]`
let list = match attr.meta_item_list() {
Some(list) => list,
None => return,
};
if list.len() != 1 && list.len() != 2 {
self.handler.span_err(attr.span, "attribute must have either one or two arguments");
return;
}
let trait_attr = match list[0].meta_item() {
Some(meta_item) => meta_item,
_ => {
self.handler.span_err(list[0].span(), "not a meta item");
return;
}
};
let trait_ident = match trait_attr.ident() {
Some(trait_ident) if trait_attr.is_word() => trait_ident,
_ => {
self.handler.span_err(trait_attr.span, "must only be one word");
return;
}
};

if !trait_ident.name.can_be_raw() {
self.handler.span_err(
trait_attr.span,
&format!("`{}` cannot be a name of derive macro", trait_ident),
);
}

let attributes_attr = list.get(1);
let proc_attrs: Vec<_> = if let Some(attr) = attributes_attr {
if !attr.has_name(sym::attributes) {
self.handler.span_err(attr.span(), "second argument must be `attributes`")
}
attr.meta_item_list()
.unwrap_or_else(|| {
self.handler
.span_err(attr.span(), "attribute must be of form: `attributes(foo, bar)`");
&[]
})
.iter()
.filter_map(|attr| {
let attr = match attr.meta_item() {
Some(meta_item) => meta_item,
_ => {
self.handler.span_err(attr.span(), "not a meta item");
return None;
}
};

let ident = match attr.ident() {
Some(ident) if attr.is_word() => ident,
_ => {
self.handler.span_err(attr.span, "must only be one word");
return None;
}
};
if !ident.name.can_be_raw() {
self.handler.span_err(
attr.span,
&format!("`{}` cannot be a name of derive helper attribute", ident),
);
}

Some(ident.name)
})
.collect()
} else {
Vec::new()
};
let (trait_name, proc_attrs) =
match parse_macro_name_and_helper_attrs(self.handler, attr, "derive") {
Some(name_and_attrs) => name_and_attrs,
None => return,
};

if self.in_root && item.vis.kind.is_pub() {
self.macros.push(ProcMacro::Derive(ProcMacroDerive {
id: item.id,
span: item.span,
trait_name: trait_ident.name,
trait_name,
function_name: item.ident,
attrs: proc_attrs,
}));
Expand Down
94 changes: 92 additions & 2 deletions compiler/rustc_expand/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,9 +745,17 @@ impl SyntaxExtension {
}
}

let builtin_name = sess
let (builtin_name, helper_attrs) = sess
.find_by_name(attrs, sym::rustc_builtin_macro)
.map(|a| a.value_str().unwrap_or(name));
.map(|attr| {
// Override `helper_attrs` passed above if it's a built-in macro,
// marking `proc_macro_derive` macros as built-in is not a realistic use case.
parse_macro_name_and_helper_attrs(sess.diagnostic(), attr, "built-in").map_or_else(
|| (Some(name), Vec::new()),
|(name, helper_attrs)| (Some(name), helper_attrs),
)
})
.unwrap_or_else(|| (None, helper_attrs));
let (stability, const_stability) = attr::find_stability(&sess, attrs, span);
if let Some((_, sp)) = const_stability {
sess.parse_sess
Expand Down Expand Up @@ -1213,6 +1221,88 @@ pub fn get_exprs_from_tts(
Some(es)
}

pub fn parse_macro_name_and_helper_attrs(
diag: &rustc_errors::Handler,
attr: &Attribute,
descr: &str,
) -> Option<(Symbol, Vec<Symbol>)> {
// Once we've located the `#[proc_macro_derive]` attribute, verify
// that it's of the form `#[proc_macro_derive(Foo)]` or
// `#[proc_macro_derive(Foo, attributes(A, ..))]`
let list = match attr.meta_item_list() {
Some(list) => list,
None => return None,
};
if list.len() != 1 && list.len() != 2 {
diag.span_err(attr.span, "attribute must have either one or two arguments");
return None;
}
let trait_attr = match list[0].meta_item() {
Some(meta_item) => meta_item,
_ => {
diag.span_err(list[0].span(), "not a meta item");
return None;
}
};
let trait_ident = match trait_attr.ident() {
Some(trait_ident) if trait_attr.is_word() => trait_ident,
_ => {
diag.span_err(trait_attr.span, "must only be one word");
return None;
}
};

if !trait_ident.name.can_be_raw() {
diag.span_err(
trait_attr.span,
&format!("`{}` cannot be a name of {} macro", trait_ident, descr),
);
}

let attributes_attr = list.get(1);
let proc_attrs: Vec<_> = if let Some(attr) = attributes_attr {
if !attr.has_name(sym::attributes) {
diag.span_err(attr.span(), "second argument must be `attributes`")
}
attr.meta_item_list()
.unwrap_or_else(|| {
diag.span_err(attr.span(), "attribute must be of form: `attributes(foo, bar)`");
&[]
})
.iter()
.filter_map(|attr| {
let attr = match attr.meta_item() {
Some(meta_item) => meta_item,
_ => {
diag.span_err(attr.span(), "not a meta item");
return None;
}
};

let ident = match attr.ident() {
Some(ident) if attr.is_word() => ident,
_ => {
diag.span_err(attr.span, "must only be one word");
return None;
}
};
if !ident.name.can_be_raw() {
diag.span_err(
attr.span,
&format!("`{}` cannot be a name of derive helper attribute", ident),
);
}

Some(ident.name)
})
.collect()
} else {
Vec::new()
};

Some((trait_ident.name, proc_attrs))
}

/// This nonterminal looks like some specific enums from
/// `proc-macro-hack` and `procedural-masquerade` crates.
/// We need to maintain some special pretty-printing behavior for them due to incorrect
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,11 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
// Internal attributes, Macro related:
// ==========================================================================

rustc_attr!(rustc_builtin_macro, AssumedUsed, template!(Word, NameValueStr: "name"), IMPL_DETAIL),
rustc_attr!(
rustc_builtin_macro, AssumedUsed,
template!(Word, List: "name, /*opt*/ attributes(name1, name2, ...)"),
IMPL_DETAIL,
),
rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), INTERNAL_UNSTABLE),
rustc_attr!(
rustc_macro_transparency, AssumedUsed,
Expand Down
3 changes: 2 additions & 1 deletion library/core/src/macros/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[doc = include_str!("panic.md")]
#[macro_export]
#[rustc_builtin_macro = "core_panic"]
#[cfg_attr(bootstrap, rustc_builtin_macro = "core_panic")]
#[cfg_attr(not(bootstrap), rustc_builtin_macro(core_panic))]
#[allow_internal_unstable(edition_panic)]
#[stable(feature = "core", since = "1.6.0")]
#[rustc_diagnostic_item = "core_panic_macro"]
Expand Down
3 changes: 2 additions & 1 deletion library/std/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
#[doc = include_str!("../../core/src/macros/panic.md")]
#[macro_export]
#[rustc_builtin_macro = "std_panic"]
#[cfg_attr(bootstrap, rustc_builtin_macro = "std_panic")]
#[cfg_attr(not(bootstrap), rustc_builtin_macro(std_panic))]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable(edition_panic)]
#[cfg_attr(not(test), rustc_diagnostic_item = "std_panic_macro")]
Expand Down
36 changes: 36 additions & 0 deletions src/test/ui/deriving/deriving-with-helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// check-pass
// compile-flags: --crate-type=lib

#![feature(decl_macro)]
#![feature(lang_items)]
#![feature(no_core)]
#![feature(rustc_attrs)]

#![no_core]

#[rustc_builtin_macro]
macro derive() {}

#[rustc_builtin_macro(Default, attributes(default))]
macro Default() {}

mod default {
pub trait Default {
fn default() -> Self;
}

impl Default for u8 {
fn default() -> u8 {
0
}
}
}

#[lang = "sized"]
trait Sized {}

#[derive(Default)]
struct S {
#[default] // OK
field: u8,
}

0 comments on commit 4d141f5

Please sign in to comment.