summaryrefslogtreecommitdiffstats
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.rs158
1 files changed, 158 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 000000000..d3de10ca4
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/derive.rs
@@ -0,0 +1,158 @@
+use crate::cfg_eval::cfg_eval;
+
+use rustc_ast as ast;
+use rustc_ast::{attr, token, GenericParamKind, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
+use rustc_errors::{struct_span_err, Applicability};
+use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
+use rustc_feature::AttributeTemplate;
+use rustc_parse::validate_attr;
+use rustc_session::Session;
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::Span;
+
+pub(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 (sess, features) = (ecx.sess, ecx.ecfg.features);
+ let result =
+ ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| {
+ let template =
+ AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
+ let attr = attr::mk_attr_outer(meta_item.clone());
+ validate_attr::check_builtin_attribute(
+ &sess.parse_sess,
+ &attr,
+ sym::derive,
+ template,
+ );
+
+ let mut resolutions: 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
+ })
+ .map(|path| (path, dummy_annotatable(), None))
+ .collect();
+
+ // Do not configure or clone items unless necessary.
+ match &mut resolutions[..] {
+ [] => {}
+ [(_, first_item, _), others @ ..] => {
+ *first_item = cfg_eval(
+ sess,
+ features,
+ item.clone(),
+ ecx.current_expansion.lint_node_id,
+ );
+ for (_, item, _) in others {
+ *item = first_item.clone();
+ }
+ }
+ }
+
+ resolutions
+ });
+
+ match result {
+ Ok(()) => ExpandResult::Ready(vec![item]),
+ Err(Indeterminate) => ExpandResult::Retry(item),
+ }
+ }
+}
+
+// The cheapest `Annotatable` to construct.
+fn dummy_annotatable() -> Annotatable {
+ Annotatable::GenericParam(ast::GenericParam {
+ id: ast::DUMMY_NODE_ID,
+ ident: Ident::empty(),
+ attrs: Default::default(),
+ bounds: Default::default(),
+ is_placeholder: false,
+ kind: GenericParamKind::Lifetime,
+ colon_span: None,
+ })
+}
+
+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 `struct`s, `enum`s and `union`s",
+ )
+ .span_label(span, "not applicable here")
+ .span_label(item.span(), "not a `struct`, `enum` or `union`")
+ .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",)
+ .span_label(lit.span, "not a trait")
+ .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, "", 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")
+ }
+ }
+}