diff options
Diffstat (limited to 'compiler/rustc_builtin_macros')
| -rw-r--r-- | compiler/rustc_builtin_macros/Cargo.toml | 1 | ||||
| -rw-r--r-- | compiler/rustc_builtin_macros/src/derive.rs | 132 | ||||
| -rw-r--r-- | compiler/rustc_builtin_macros/src/lib.rs | 2 |
3 files changed, 135 insertions, 0 deletions
diff --git a/compiler/rustc_builtin_macros/Cargo.toml b/compiler/rustc_builtin_macros/Cargo.toml index c397a854126..eb022b5b2b1 100644 --- a/compiler/rustc_builtin_macros/Cargo.toml +++ b/compiler/rustc_builtin_macros/Cargo.toml @@ -15,6 +15,7 @@ rustc_attr = { path = "../rustc_attr" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_feature = { path = "../rustc_feature" } +rustc_lexer = { path = "../rustc_lexer" } rustc_parse = { path = "../rustc_parse" } rustc_target = { path = "../rustc_target" } rustc_session = { path = "../rustc_session" } diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs new file mode 100644 index 00000000000..fad64858ce3 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -0,0 +1,132 @@ +use rustc_ast::{self as ast, token, ItemKind, MetaItemKind, NestedMetaItem, StmtKind}; +use rustc_errors::{struct_span_err, Applicability}; +use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier}; +use rustc_expand::config::StripUnconfigured; +use rustc_feature::AttributeTemplate; +use rustc_parse::validate_attr; +use rustc_session::Session; +use rustc_span::symbol::sym; +use rustc_span::Span; + +crate struct Expander; + +impl MultiItemModifier for Expander { + fn expand( + &self, + ecx: &mut ExtCtxt<'_>, + span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, + ) -> ExpandResult<Vec<Annotatable>, Annotatable> { + let sess = ecx.sess; + if report_bad_target(sess, &item, span) { + // We don't want to pass inappropriate targets to derive macros to avoid + // follow up errors, all other errors below are recoverable. + return ExpandResult::Ready(vec![item]); + } + + let template = + AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() }; + let attr = ecx.attribute(meta_item.clone()); + validate_attr::check_builtin_attribute(&sess.parse_sess, &attr, sym::derive, template); + + let derives: Vec<_> = attr + .meta_item_list() + .unwrap_or_default() + .into_iter() + .filter_map(|nested_meta| match nested_meta { + NestedMetaItem::MetaItem(meta) => Some(meta), + NestedMetaItem::Literal(lit) => { + // Reject `#[derive("Debug")]`. + report_unexpected_literal(sess, &lit); + None + } + }) + .map(|meta| { + // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths. + report_path_args(sess, &meta); + meta.path + }) + .collect(); + + // FIXME: Try to cache intermediate results to avoid collecting same paths multiple times. + match ecx.resolver.resolve_derives(ecx.current_expansion.id, derives, ecx.force_mode) { + Ok(()) => { + let mut visitor = + StripUnconfigured { sess, features: ecx.ecfg.features, modified: false }; + let mut item = visitor.fully_configure(item); + if visitor.modified { + // Erase the tokens if cfg-stripping modified the item + // This will cause us to synthesize fake tokens + // when `nt_to_tokenstream` is called on this item. + match &mut item { + Annotatable::Item(item) => item, + Annotatable::Stmt(stmt) => match &mut stmt.kind { + StmtKind::Item(item) => item, + _ => unreachable!(), + }, + _ => unreachable!(), + } + .tokens = None; + } + ExpandResult::Ready(vec![item]) + } + Err(Indeterminate) => ExpandResult::Retry(item), + } + } +} + +fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool { + let item_kind = match item { + Annotatable::Item(item) => Some(&item.kind), + Annotatable::Stmt(stmt) => match &stmt.kind { + StmtKind::Item(item) => Some(&item.kind), + _ => None, + }, + _ => None, + }; + + let bad_target = + !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..))); + if bad_target { + struct_span_err!( + sess, + span, + E0774, + "`derive` may only be applied to structs, enums and unions", + ) + .emit(); + } + bad_target +} + +fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) { + let help_msg = match lit.token.kind { + token::Str if rustc_lexer::is_ident(&lit.token.symbol.as_str()) => { + format!("try using `#[derive({})]`", lit.token.symbol) + } + _ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(), + }; + struct_span_err!(sess, lit.span, E0777, "expected path to a trait, found literal",) + .help(&help_msg) + .emit(); +} + +fn report_path_args(sess: &Session, meta: &ast::MetaItem) { + let report_error = |title, action| { + let span = meta.span.with_lo(meta.path.span.hi()); + sess.struct_span_err(span, title) + .span_suggestion(span, action, String::new(), Applicability::MachineApplicable) + .emit(); + }; + match meta.kind { + MetaItemKind::Word => {} + MetaItemKind::List(..) => report_error( + "traits in `#[derive(...)]` don't accept arguments", + "remove the arguments", + ), + MetaItemKind::NameValue(..) => { + report_error("traits in `#[derive(...)]` don't accept values", "remove the value") + } + } +} diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 59844b6c692..9a3c914337c 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -27,6 +27,7 @@ mod cfg_accessible; mod compile_error; mod concat; mod concat_idents; +mod derive; mod deriving; mod env; mod format; @@ -88,6 +89,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { register_attr! { bench: test::expand_bench, cfg_accessible: cfg_accessible::Expander, + derive: derive::Expander, global_allocator: global_allocator::expand, test: test::expand_test, test_case: test::expand_test_case, |
