]>
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 | ||
13 | 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 | ||
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 @ ..] => { | |
67 | *first_item = cfg_eval(sess, features, item.clone()); | |
68 | for (_, item, _) in others { | |
69 | *item = first_item.clone(); | |
70 | } | |
71 | } | |
72 | } | |
73 | ||
74 | resolutions | |
cdc7bbd5 | 75 | }); |
6a06907d | 76 | |
cdc7bbd5 | 77 | match result { |
136023e0 | 78 | Ok(()) => ExpandResult::Ready(vec![item]), |
6a06907d XL |
79 | Err(Indeterminate) => ExpandResult::Retry(item), |
80 | } | |
81 | } | |
82 | } | |
83 | ||
c295e0f8 XL |
84 | // The cheapest `Annotatable` to construct. |
85 | fn dummy_annotatable() -> Annotatable { | |
86 | Annotatable::GenericParam(ast::GenericParam { | |
87 | id: ast::DUMMY_NODE_ID, | |
3c0e092e | 88 | ident: Ident::empty(), |
c295e0f8 XL |
89 | attrs: Default::default(), |
90 | bounds: Default::default(), | |
91 | is_placeholder: false, | |
92 | kind: GenericParamKind::Lifetime, | |
93 | }) | |
94 | } | |
95 | ||
6a06907d XL |
96 | fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool { |
97 | let item_kind = match item { | |
98 | Annotatable::Item(item) => Some(&item.kind), | |
99 | Annotatable::Stmt(stmt) => match &stmt.kind { | |
100 | StmtKind::Item(item) => Some(&item.kind), | |
101 | _ => None, | |
102 | }, | |
103 | _ => None, | |
104 | }; | |
105 | ||
106 | let bad_target = | |
107 | !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..))); | |
108 | if bad_target { | |
109 | struct_span_err!( | |
110 | sess, | |
111 | span, | |
112 | E0774, | |
136023e0 | 113 | "`derive` may only be applied to `struct`s, `enum`s and `union`s", |
6a06907d | 114 | ) |
136023e0 XL |
115 | .span_label(span, "not applicable here") |
116 | .span_label(item.span(), "not a `struct`, `enum` or `union`") | |
6a06907d XL |
117 | .emit(); |
118 | } | |
119 | bad_target | |
120 | } | |
121 | ||
122 | fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) { | |
123 | let help_msg = match lit.token.kind { | |
124 | token::Str if rustc_lexer::is_ident(&lit.token.symbol.as_str()) => { | |
125 | format!("try using `#[derive({})]`", lit.token.symbol) | |
126 | } | |
127 | _ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(), | |
128 | }; | |
129 | struct_span_err!(sess, lit.span, E0777, "expected path to a trait, found literal",) | |
136023e0 | 130 | .span_label(lit.span, "not a trait") |
6a06907d XL |
131 | .help(&help_msg) |
132 | .emit(); | |
133 | } | |
134 | ||
135 | fn report_path_args(sess: &Session, meta: &ast::MetaItem) { | |
136 | let report_error = |title, action| { | |
137 | let span = meta.span.with_lo(meta.path.span.hi()); | |
138 | sess.struct_span_err(span, title) | |
139 | .span_suggestion(span, action, String::new(), Applicability::MachineApplicable) | |
140 | .emit(); | |
141 | }; | |
142 | match meta.kind { | |
143 | MetaItemKind::Word => {} | |
144 | MetaItemKind::List(..) => report_error( | |
145 | "traits in `#[derive(...)]` don't accept arguments", | |
146 | "remove the arguments", | |
147 | ), | |
148 | MetaItemKind::NameValue(..) => { | |
149 | report_error("traits in `#[derive(...)]` don't accept values", "remove the value") | |
150 | } | |
151 | } | |
152 | } |