rustc_builtin_macros/
derive.rs

1use rustc_ast as ast;
2use rustc_ast::{GenericParamKind, ItemKind, MetaItemInner, MetaItemKind, StmtKind};
3use rustc_expand::base::{
4    Annotatable, DeriveResolution, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier,
5};
6use rustc_feature::AttributeTemplate;
7use rustc_parse::validate_attr;
8use rustc_session::Session;
9use rustc_span::{ErrorGuaranteed, Ident, Span, sym};
10
11use crate::cfg_eval::cfg_eval;
12use crate::errors;
13
14pub(crate) struct Expander {
15    pub is_const: bool,
16}
17
18impl MultiItemModifier for Expander {
19    fn expand(
20        &self,
21        ecx: &mut ExtCtxt<'_>,
22        span: Span,
23        meta_item: &ast::MetaItem,
24        item: Annotatable,
25        _: bool,
26    ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
27        let sess = ecx.sess;
28        if report_bad_target(sess, &item, span).is_err() {
29            // We don't want to pass inappropriate targets to derive macros to avoid
30            // follow up errors, all other errors below are recoverable.
31            return ExpandResult::Ready(vec![item]);
32        }
33
34        let (sess, features) = (ecx.sess, ecx.ecfg.features);
35        let result =
36            ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| {
37                let template =
38                    AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
39                validate_attr::check_builtin_meta_item(
40                    &sess.psess,
41                    meta_item,
42                    ast::AttrStyle::Outer,
43                    sym::derive,
44                    template,
45                    true,
46                );
47
48                let mut resolutions = match &meta_item.kind {
49                    MetaItemKind::List(list) => {
50                        list.iter()
51                            .filter_map(|meta_item_inner| match meta_item_inner {
52                                MetaItemInner::MetaItem(meta) => Some(meta),
53                                MetaItemInner::Lit(lit) => {
54                                    // Reject `#[derive("Debug")]`.
55                                    report_unexpected_meta_item_lit(sess, lit);
56                                    None
57                                }
58                            })
59                            .map(|meta| {
60                                // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the
61                                // paths.
62                                report_path_args(sess, meta);
63                                meta.path.clone()
64                            })
65                            .map(|path| DeriveResolution {
66                                path,
67                                item: dummy_annotatable(),
68                                exts: None,
69                                is_const: self.is_const,
70                            })
71                            .collect()
72                    }
73                    _ => vec![],
74                };
75
76                // Do not configure or clone items unless necessary.
77                match &mut resolutions[..] {
78                    [] => {}
79                    [first, others @ ..] => {
80                        first.item = cfg_eval(
81                            sess,
82                            features,
83                            item.clone(),
84                            ecx.current_expansion.lint_node_id,
85                        );
86                        for other in others {
87                            other.item = first.item.clone();
88                        }
89                    }
90                }
91
92                resolutions
93            });
94
95        match result {
96            Ok(()) => ExpandResult::Ready(vec![item]),
97            Err(Indeterminate) => ExpandResult::Retry(item),
98        }
99    }
100}
101
102// The cheapest `Annotatable` to construct.
103fn dummy_annotatable() -> Annotatable {
104    Annotatable::GenericParam(ast::GenericParam {
105        id: ast::DUMMY_NODE_ID,
106        ident: Ident::empty(),
107        attrs: Default::default(),
108        bounds: Default::default(),
109        is_placeholder: false,
110        kind: GenericParamKind::Lifetime,
111        colon_span: None,
112    })
113}
114
115fn report_bad_target(
116    sess: &Session,
117    item: &Annotatable,
118    span: Span,
119) -> Result<(), ErrorGuaranteed> {
120    let item_kind = match item {
121        Annotatable::Item(item) => Some(&item.kind),
122        Annotatable::Stmt(stmt) => match &stmt.kind {
123            StmtKind::Item(item) => Some(&item.kind),
124            _ => None,
125        },
126        _ => None,
127    };
128
129    let bad_target =
130        !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)));
131    if bad_target {
132        return Err(sess.dcx().emit_err(errors::BadDeriveTarget { span, item: item.span() }));
133    }
134    Ok(())
135}
136
137fn report_unexpected_meta_item_lit(sess: &Session, lit: &ast::MetaItemLit) {
138    let help = match lit.kind {
139        ast::LitKind::Str(_, ast::StrStyle::Cooked)
140            if rustc_lexer::is_ident(lit.symbol.as_str()) =>
141        {
142            errors::BadDeriveLitHelp::StrLit { sym: lit.symbol }
143        }
144        _ => errors::BadDeriveLitHelp::Other,
145    };
146    sess.dcx().emit_err(errors::BadDeriveLit { span: lit.span, help });
147}
148
149fn report_path_args(sess: &Session, meta: &ast::MetaItem) {
150    let span = meta.span.with_lo(meta.path.span.hi());
151
152    match meta.kind {
153        MetaItemKind::Word => {}
154        MetaItemKind::List(..) => {
155            sess.dcx().emit_err(errors::DerivePathArgsList { span });
156        }
157        MetaItemKind::NameValue(..) => {
158            sess.dcx().emit_err(errors::DerivePathArgsValue { span });
159        }
160    }
161}