about summary refs log tree commit diff
path: root/compiler/rustc_builtin_macros/src/derive.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_builtin_macros/src/derive.rs')
-rw-r--r--compiler/rustc_builtin_macros/src/derive.rs132
1 files changed, 132 insertions, 0 deletions
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")
+        }
+    }
+}