]>
Commit | Line | Data |
---|---|---|
6a06907d XL |
1 | use crate::cfg_eval::cfg_eval; |
2 | ||
3 | use rustc_ast::{self as ast, token, ItemKind, MetaItemKind, NestedMetaItem, StmtKind}; | |
4 | use rustc_errors::{struct_span_err, Applicability}; | |
5 | use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier}; | |
6 | use rustc_feature::AttributeTemplate; | |
7 | use rustc_parse::validate_attr; | |
8 | use rustc_session::Session; | |
9 | use rustc_span::symbol::sym; | |
10 | use rustc_span::Span; | |
11 | ||
12 | crate struct Expander; | |
13 | ||
14 | impl MultiItemModifier for Expander { | |
15 | fn expand( | |
16 | &self, | |
17 | ecx: &mut ExtCtxt<'_>, | |
18 | span: Span, | |
19 | meta_item: &ast::MetaItem, | |
20 | item: Annotatable, | |
21 | ) -> ExpandResult<Vec<Annotatable>, Annotatable> { | |
22 | let sess = ecx.sess; | |
23 | if report_bad_target(sess, &item, span) { | |
24 | // We don't want to pass inappropriate targets to derive macros to avoid | |
25 | // follow up errors, all other errors below are recoverable. | |
26 | return ExpandResult::Ready(vec![item]); | |
27 | } | |
28 | ||
29 | let template = | |
30 | AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() }; | |
31 | let attr = ecx.attribute(meta_item.clone()); | |
32 | validate_attr::check_builtin_attribute(&sess.parse_sess, &attr, sym::derive, template); | |
33 | ||
34 | let derives: Vec<_> = attr | |
35 | .meta_item_list() | |
36 | .unwrap_or_default() | |
37 | .into_iter() | |
38 | .filter_map(|nested_meta| match nested_meta { | |
39 | NestedMetaItem::MetaItem(meta) => Some(meta), | |
40 | NestedMetaItem::Literal(lit) => { | |
41 | // Reject `#[derive("Debug")]`. | |
42 | report_unexpected_literal(sess, &lit); | |
43 | None | |
44 | } | |
45 | }) | |
46 | .map(|meta| { | |
47 | // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths. | |
48 | report_path_args(sess, &meta); | |
49 | meta.path | |
50 | }) | |
51 | .collect(); | |
52 | ||
53 | // FIXME: Try to cache intermediate results to avoid collecting same paths multiple times. | |
54 | match ecx.resolver.resolve_derives(ecx.current_expansion.id, derives, ecx.force_mode) { | |
55 | Ok(()) => ExpandResult::Ready(cfg_eval(ecx, item)), | |
56 | Err(Indeterminate) => ExpandResult::Retry(item), | |
57 | } | |
58 | } | |
59 | } | |
60 | ||
61 | fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool { | |
62 | let item_kind = match item { | |
63 | Annotatable::Item(item) => Some(&item.kind), | |
64 | Annotatable::Stmt(stmt) => match &stmt.kind { | |
65 | StmtKind::Item(item) => Some(&item.kind), | |
66 | _ => None, | |
67 | }, | |
68 | _ => None, | |
69 | }; | |
70 | ||
71 | let bad_target = | |
72 | !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..))); | |
73 | if bad_target { | |
74 | struct_span_err!( | |
75 | sess, | |
76 | span, | |
77 | E0774, | |
78 | "`derive` may only be applied to structs, enums and unions", | |
79 | ) | |
80 | .emit(); | |
81 | } | |
82 | bad_target | |
83 | } | |
84 | ||
85 | fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) { | |
86 | let help_msg = match lit.token.kind { | |
87 | token::Str if rustc_lexer::is_ident(&lit.token.symbol.as_str()) => { | |
88 | format!("try using `#[derive({})]`", lit.token.symbol) | |
89 | } | |
90 | _ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(), | |
91 | }; | |
92 | struct_span_err!(sess, lit.span, E0777, "expected path to a trait, found literal",) | |
93 | .help(&help_msg) | |
94 | .emit(); | |
95 | } | |
96 | ||
97 | fn report_path_args(sess: &Session, meta: &ast::MetaItem) { | |
98 | let report_error = |title, action| { | |
99 | let span = meta.span.with_lo(meta.path.span.hi()); | |
100 | sess.struct_span_err(span, title) | |
101 | .span_suggestion(span, action, String::new(), Applicability::MachineApplicable) | |
102 | .emit(); | |
103 | }; | |
104 | match meta.kind { | |
105 | MetaItemKind::Word => {} | |
106 | MetaItemKind::List(..) => report_error( | |
107 | "traits in `#[derive(...)]` don't accept arguments", | |
108 | "remove the arguments", | |
109 | ), | |
110 | MetaItemKind::NameValue(..) => { | |
111 | report_error("traits in `#[derive(...)]` don't accept values", "remove the value") | |
112 | } | |
113 | } | |
114 | } |