]>
Commit | Line | Data |
---|---|---|
6a06907d XL |
1 | use crate::cfg_eval::cfg_eval; |
2 | ||
c295e0f8 XL |
3 | use rustc_ast as ast; |
4 | use rustc_ast::{attr, token, GenericParamKind, ItemKind, MetaItemKind, NestedMetaItem, StmtKind}; | |
6a06907d XL |
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; | |
c295e0f8 | 10 | use rustc_span::symbol::{sym, Ident}; |
6a06907d XL |
11 | use rustc_span::Span; |
12 | ||
923072b8 | 13 | pub(crate) struct Expander; |
6a06907d XL |
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 | ||
c295e0f8 | 30 | let (sess, features) = (ecx.sess, ecx.ecfg.features); |
cdc7bbd5 XL |
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 | ); | |
6a06907d | 42 | |
c295e0f8 XL |
43 | let mut resolutions: Vec<_> = attr |
44 | .meta_item_list() | |
cdc7bbd5 XL |
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 | }) | |
c295e0f8 XL |
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 @ ..] => { | |
5e7ed085 FG |
67 | *first_item = cfg_eval( |
68 | sess, | |
69 | features, | |
70 | item.clone(), | |
71 | ecx.current_expansion.lint_node_id, | |
72 | ); | |
c295e0f8 XL |
73 | for (_, item, _) in others { |
74 | *item = first_item.clone(); | |
75 | } | |
76 | } | |
77 | } | |
78 | ||
79 | resolutions | |
cdc7bbd5 | 80 | }); |
6a06907d | 81 | |
cdc7bbd5 | 82 | match result { |
136023e0 | 83 | Ok(()) => ExpandResult::Ready(vec![item]), |
6a06907d XL |
84 | Err(Indeterminate) => ExpandResult::Retry(item), |
85 | } | |
86 | } | |
87 | } | |
88 | ||
c295e0f8 XL |
89 | // The cheapest `Annotatable` to construct. |
90 | fn dummy_annotatable() -> Annotatable { | |
91 | Annotatable::GenericParam(ast::GenericParam { | |
92 | id: ast::DUMMY_NODE_ID, | |
3c0e092e | 93 | ident: Ident::empty(), |
c295e0f8 XL |
94 | attrs: Default::default(), |
95 | bounds: Default::default(), | |
96 | is_placeholder: false, | |
97 | kind: GenericParamKind::Lifetime, | |
04454e1e | 98 | colon_span: None, |
c295e0f8 XL |
99 | }) |
100 | } | |
101 | ||
6a06907d XL |
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, | |
136023e0 | 119 | "`derive` may only be applied to `struct`s, `enum`s and `union`s", |
6a06907d | 120 | ) |
136023e0 XL |
121 | .span_label(span, "not applicable here") |
122 | .span_label(item.span(), "not a `struct`, `enum` or `union`") | |
6a06907d XL |
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.kind { | |
a2a8927a | 130 | token::Str if rustc_lexer::is_ident(lit.token.symbol.as_str()) => { |
6a06907d XL |
131 | format!("try using `#[derive({})]`", lit.token.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",) | |
136023e0 | 136 | .span_label(lit.span, "not a trait") |
6a06907d XL |
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) | |
923072b8 | 145 | .span_suggestion(span, action, "", Applicability::MachineApplicable) |
6a06907d XL |
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 | } |