]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/derive.rs
Rollup merge of #101828 - aDotInTheVoid:test-101743, r=jsha
[rust.git] / compiler / rustc_builtin_macros / src / derive.rs
1 use crate::cfg_eval::cfg_eval;
2
3 use rustc_ast as ast;
4 use rustc_ast::{attr, token, GenericParamKind, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
5 use rustc_errors::{struct_span_err, Applicability};
6 use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
7 use rustc_feature::AttributeTemplate;
8 use rustc_parse::validate_attr;
9 use rustc_session::Session;
10 use rustc_span::symbol::{sym, Ident};
11 use rustc_span::Span;
12
13 pub(crate) struct Expander;
14
15 impl MultiItemModifier for Expander {
16     fn expand(
17         &self,
18         ecx: &mut ExtCtxt<'_>,
19         span: Span,
20         meta_item: &ast::MetaItem,
21         item: Annotatable,
22     ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
23         let sess = ecx.sess;
24         if report_bad_target(sess, &item, span) {
25             // We don't want to pass inappropriate targets to derive macros to avoid
26             // follow up errors, all other errors below are recoverable.
27             return ExpandResult::Ready(vec![item]);
28         }
29
30         let (sess, features) = (ecx.sess, ecx.ecfg.features);
31         let result =
32             ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| {
33                 let template =
34                     AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
35                 let attr =
36                     attr::mk_attr_outer(&sess.parse_sess.attr_id_generator, meta_item.clone());
37                 validate_attr::check_builtin_attribute(
38                     &sess.parse_sess,
39                     &attr,
40                     sym::derive,
41                     template,
42                 );
43
44                 let mut resolutions: Vec<_> = attr
45                     .meta_item_list()
46                     .unwrap_or_default()
47                     .into_iter()
48                     .filter_map(|nested_meta| match nested_meta {
49                         NestedMetaItem::MetaItem(meta) => Some(meta),
50                         NestedMetaItem::Literal(lit) => {
51                             // Reject `#[derive("Debug")]`.
52                             report_unexpected_literal(sess, &lit);
53                             None
54                         }
55                     })
56                     .map(|meta| {
57                         // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
58                         report_path_args(sess, &meta);
59                         meta.path
60                     })
61                     .map(|path| (path, dummy_annotatable(), None))
62                     .collect();
63
64                 // Do not configure or clone items unless necessary.
65                 match &mut resolutions[..] {
66                     [] => {}
67                     [(_, first_item, _), others @ ..] => {
68                         *first_item = cfg_eval(
69                             sess,
70                             features,
71                             item.clone(),
72                             ecx.current_expansion.lint_node_id,
73                         );
74                         for (_, item, _) in others {
75                             *item = first_item.clone();
76                         }
77                     }
78                 }
79
80                 resolutions
81             });
82
83         match result {
84             Ok(()) => ExpandResult::Ready(vec![item]),
85             Err(Indeterminate) => ExpandResult::Retry(item),
86         }
87     }
88 }
89
90 // The cheapest `Annotatable` to construct.
91 fn dummy_annotatable() -> Annotatable {
92     Annotatable::GenericParam(ast::GenericParam {
93         id: ast::DUMMY_NODE_ID,
94         ident: Ident::empty(),
95         attrs: Default::default(),
96         bounds: Default::default(),
97         is_placeholder: false,
98         kind: GenericParamKind::Lifetime,
99         colon_span: None,
100     })
101 }
102
103 fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool {
104     let item_kind = match item {
105         Annotatable::Item(item) => Some(&item.kind),
106         Annotatable::Stmt(stmt) => match &stmt.kind {
107             StmtKind::Item(item) => Some(&item.kind),
108             _ => None,
109         },
110         _ => None,
111     };
112
113     let bad_target =
114         !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)));
115     if bad_target {
116         struct_span_err!(
117             sess,
118             span,
119             E0774,
120             "`derive` may only be applied to `struct`s, `enum`s and `union`s",
121         )
122         .span_label(span, "not applicable here")
123         .span_label(item.span(), "not a `struct`, `enum` or `union`")
124         .emit();
125     }
126     bad_target
127 }
128
129 fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) {
130     let help_msg = match lit.token_lit.kind {
131         token::Str if rustc_lexer::is_ident(lit.token_lit.symbol.as_str()) => {
132             format!("try using `#[derive({})]`", lit.token_lit.symbol)
133         }
134         _ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(),
135     };
136     struct_span_err!(sess, lit.span, E0777, "expected path to a trait, found literal",)
137         .span_label(lit.span, "not a trait")
138         .help(&help_msg)
139         .emit();
140 }
141
142 fn report_path_args(sess: &Session, meta: &ast::MetaItem) {
143     let report_error = |title, action| {
144         let span = meta.span.with_lo(meta.path.span.hi());
145         sess.struct_span_err(span, title)
146             .span_suggestion(span, action, "", Applicability::MachineApplicable)
147             .emit();
148     };
149     match meta.kind {
150         MetaItemKind::Word => {}
151         MetaItemKind::List(..) => report_error(
152             "traits in `#[derive(...)]` don't accept arguments",
153             "remove the arguments",
154         ),
155         MetaItemKind::NameValue(..) => {
156             report_error("traits in `#[derive(...)]` don't accept values", "remove the value")
157         }
158     }
159 }