rustc_builtin_macros/
derive.rs

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