]> git.lizzy.rs Git - rust.git/blob - crates/hir-expand/src/builtin_attr_macro.rs
Auto merge of #12652 - lnicola:openvsx, r=lnicola
[rust.git] / crates / hir-expand / src / builtin_attr_macro.rs
1 //! Builtin attributes.
2
3 use crate::{db::AstDatabase, name, ExpandResult, MacroCallId, MacroCallKind};
4
5 macro_rules! register_builtin {
6     ( $(($name:ident, $variant:ident) => $expand:ident),* ) => {
7         #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8         pub enum BuiltinAttrExpander {
9             $($variant),*
10         }
11
12         impl BuiltinAttrExpander {
13             pub fn expand(
14                 &self,
15                 db: &dyn AstDatabase,
16                 id: MacroCallId,
17                 tt: &tt::Subtree,
18             ) -> ExpandResult<tt::Subtree> {
19                 let expander = match *self {
20                     $( BuiltinAttrExpander::$variant => $expand, )*
21                 };
22                 expander(db, id, tt)
23             }
24
25             fn find_by_name(name: &name::Name) -> Option<Self> {
26                 match name {
27                     $( id if id == &name::name![$name] => Some(BuiltinAttrExpander::$variant), )*
28                      _ => None,
29                 }
30             }
31         }
32
33     };
34 }
35
36 impl BuiltinAttrExpander {
37     pub fn is_derive(self) -> bool {
38         matches!(self, BuiltinAttrExpander::Derive)
39     }
40     pub fn is_test(self) -> bool {
41         matches!(self, BuiltinAttrExpander::Test)
42     }
43     pub fn is_bench(self) -> bool {
44         matches!(self, BuiltinAttrExpander::Bench)
45     }
46 }
47
48 register_builtin! {
49     (bench, Bench) => dummy_attr_expand,
50     (cfg_accessible, CfgAccessible) => dummy_attr_expand,
51     (cfg_eval, CfgEval) => dummy_attr_expand,
52     (derive, Derive) => derive_attr_expand,
53     (global_allocator, GlobalAllocator) => dummy_attr_expand,
54     (test, Test) => dummy_attr_expand,
55     (test_case, TestCase) => dummy_attr_expand
56 }
57
58 pub fn find_builtin_attr(ident: &name::Name) -> Option<BuiltinAttrExpander> {
59     BuiltinAttrExpander::find_by_name(ident)
60 }
61
62 fn dummy_attr_expand(
63     _db: &dyn AstDatabase,
64     _id: MacroCallId,
65     tt: &tt::Subtree,
66 ) -> ExpandResult<tt::Subtree> {
67     ExpandResult::ok(tt.clone())
68 }
69
70 /// We generate a very specific expansion here, as we do not actually expand the `#[derive]` attribute
71 /// itself in name res, but we do want to expand it to something for the IDE layer, so that the input
72 /// derive attributes can be downmapped, and resolved as proper paths.
73 /// This is basically a hack, that simplifies the hacks we need in a lot of ide layer places to
74 /// somewhat inconsistently resolve derive attributes.
75 ///
76 /// As such, we expand `#[derive(Foo, bar::Bar)]` into
77 /// ```
78 ///  #[Foo]
79 ///  #[bar::Bar]
80 ///  ();
81 /// ```
82 /// which allows fallback path resolution in hir::Semantics to properly identify our derives.
83 /// Since we do not expand the attribute in nameres though, we keep the original item.
84 ///
85 /// The ideal expansion here would be for the `#[derive]` to re-emit the annotated item and somehow
86 /// use the input paths in its output as well.
87 /// But that would bring two problems with it, for one every derive would duplicate the item token tree
88 /// wasting a lot of memory, and it would also require some way to use a path in a way that makes it
89 /// always resolve as a derive without nameres recollecting them.
90 /// So this hacky approach is a lot more friendly for us, though it does require a bit of support in
91 /// [`hir::Semantics`] to make this work.
92 fn derive_attr_expand(
93     db: &dyn AstDatabase,
94     id: MacroCallId,
95     tt: &tt::Subtree,
96 ) -> ExpandResult<tt::Subtree> {
97     let loc = db.lookup_intern_macro_call(id);
98     let derives = match &loc.kind {
99         MacroCallKind::Attr { attr_args, is_derive: true, .. } => &attr_args.0,
100         _ => return ExpandResult::ok(Default::default()),
101     };
102     pseudo_derive_attr_expansion(tt, derives)
103 }
104
105 pub fn pseudo_derive_attr_expansion(
106     tt: &tt::Subtree,
107     args: &tt::Subtree,
108 ) -> ExpandResult<tt::Subtree> {
109     let mk_leaf = |char| {
110         tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct {
111             char,
112             spacing: tt::Spacing::Alone,
113             id: tt::TokenId::unspecified(),
114         }))
115     };
116
117     let mut token_trees = Vec::new();
118     for tt in (&args.token_trees)
119         .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', .. }))))
120     {
121         token_trees.push(mk_leaf('#'));
122         token_trees.push(mk_leaf('['));
123         token_trees.extend(tt.iter().cloned());
124         token_trees.push(mk_leaf(']'));
125     }
126     token_trees.push(mk_leaf('('));
127     token_trees.push(mk_leaf(')'));
128     token_trees.push(mk_leaf(';'));
129     ExpandResult::ok(tt::Subtree { delimiter: tt.delimiter, token_trees })
130 }