]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/derive.rs
Rollup merge of #100076 - tspiteri:const_slice_split_at, r=oli-obk
[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 = attr::mk_attr_outer(meta_item.clone());
36                 validate_attr::check_builtin_attribute(
37                     &sess.parse_sess,
38                     &attr,
39                     sym::derive,
40                     template,
41                 );
42
43                 let mut resolutions: Vec<_> = attr
44                     .meta_item_list()
45                     .unwrap_or_default()
46                     .into_iter()
47                     .filter_map(|nested_meta| match nested_meta {
48                         NestedMetaItem::MetaItem(meta) => Some(meta),
49                         NestedMetaItem::Literal(lit) => {
50                             // Reject `#[derive("Debug")]`.
51                             report_unexpected_literal(sess, &lit);
52                             None
53                         }
54                     })
55                     .map(|meta| {
56                         // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
57                         report_path_args(sess, &meta);
58                         meta.path
59                     })
60                     .map(|path| (path, dummy_annotatable(), None))
61                     .collect();
62
63                 // Do not configure or clone items unless necessary.
64                 match &mut resolutions[..] {
65                     [] => {}
66                     [(_, first_item, _), others @ ..] => {
67                         *first_item = cfg_eval(
68                             sess,
69                             features,
70                             item.clone(),
71                             ecx.current_expansion.lint_node_id,
72                         );
73                         for (_, item, _) in others {
74                             *item = first_item.clone();
75                         }
76                     }
77                 }
78
79                 resolutions
80             });
81
82         match result {
83             Ok(()) => ExpandResult::Ready(vec![item]),
84             Err(Indeterminate) => ExpandResult::Retry(item),
85         }
86     }
87 }
88
89 // The cheapest `Annotatable` to construct.
90 fn dummy_annotatable() -> Annotatable {
91     Annotatable::GenericParam(ast::GenericParam {
92         id: ast::DUMMY_NODE_ID,
93         ident: Ident::empty(),
94         attrs: Default::default(),
95         bounds: Default::default(),
96         is_placeholder: false,
97         kind: GenericParamKind::Lifetime,
98         colon_span: None,
99     })
100 }
101
102 fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool {
103     let item_kind = match item {
104         Annotatable::Item(item) => Some(&item.kind),
105         Annotatable::Stmt(stmt) => match &stmt.kind {
106             StmtKind::Item(item) => Some(&item.kind),
107             _ => None,
108         },
109         _ => None,
110     };
111
112     let bad_target =
113         !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)));
114     if bad_target {
115         struct_span_err!(
116             sess,
117             span,
118             E0774,
119             "`derive` may only be applied to `struct`s, `enum`s and `union`s",
120         )
121         .span_label(span, "not applicable here")
122         .span_label(item.span(), "not a `struct`, `enum` or `union`")
123         .emit();
124     }
125     bad_target
126 }
127
128 fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) {
129     let help_msg = match lit.token_lit.kind {
130         token::Str if rustc_lexer::is_ident(lit.token_lit.symbol.as_str()) => {
131             format!("try using `#[derive({})]`", lit.token_lit.symbol)
132         }
133         _ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(),
134     };
135     struct_span_err!(sess, lit.span, E0777, "expected path to a trait, found literal",)
136         .span_label(lit.span, "not a trait")
137         .help(&help_msg)
138         .emit();
139 }
140
141 fn report_path_args(sess: &Session, meta: &ast::MetaItem) {
142     let report_error = |title, action| {
143         let span = meta.span.with_lo(meta.path.span.hi());
144         sess.struct_span_err(span, title)
145             .span_suggestion(span, action, "", Applicability::MachineApplicable)
146             .emit();
147     };
148     match meta.kind {
149         MetaItemKind::Word => {}
150         MetaItemKind::List(..) => report_error(
151             "traits in `#[derive(...)]` don't accept arguments",
152             "remove the arguments",
153         ),
154         MetaItemKind::NameValue(..) => {
155             report_error("traits in `#[derive(...)]` don't accept values", "remove the value")
156         }
157     }
158 }