]> git.lizzy.rs Git - rust.git/blob - crates/hir_expand/src/builtin_derive_macro.rs
internal: consistent module naming
[rust.git] / crates / hir_expand / src / builtin_derive_macro.rs
1 //! Builtin derives.
2
3 use tracing::debug;
4
5 use mbe::ExpandResult;
6 use syntax::{
7     ast::{self, AstNode, HasGenericParams, HasModuleItem, HasName},
8     match_ast,
9 };
10
11 use crate::{db::AstDatabase, name, quote, AstId, CrateId, MacroCallId, MacroDefId, MacroDefKind};
12
13 macro_rules! register_builtin {
14     ( $($trait:ident => $expand:ident),* ) => {
15         #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16         pub enum BuiltinDeriveExpander {
17             $($trait),*
18         }
19
20         impl BuiltinDeriveExpander {
21             pub fn expand(
22                 &self,
23                 db: &dyn AstDatabase,
24                 id: MacroCallId,
25                 tt: &tt::Subtree,
26             ) -> ExpandResult<tt::Subtree> {
27                 let expander = match *self {
28                     $( BuiltinDeriveExpander::$trait => $expand, )*
29                 };
30                 expander(db, id, tt)
31             }
32
33             fn find_by_name(name: &name::Name) -> Option<Self> {
34                 match name {
35                     $( id if id == &name::name![$trait] => Some(BuiltinDeriveExpander::$trait), )*
36                      _ => None,
37                 }
38             }
39         }
40
41     };
42 }
43
44 register_builtin! {
45     Copy => copy_expand,
46     Clone => clone_expand,
47     Default => default_expand,
48     Debug => debug_expand,
49     Hash => hash_expand,
50     Ord => ord_expand,
51     PartialOrd => partial_ord_expand,
52     Eq => eq_expand,
53     PartialEq => partial_eq_expand
54 }
55
56 pub fn find_builtin_derive(
57     ident: &name::Name,
58     krate: CrateId,
59     ast_id: AstId<ast::Macro>,
60 ) -> Option<MacroDefId> {
61     let expander = BuiltinDeriveExpander::find_by_name(ident)?;
62     Some(MacroDefId {
63         krate,
64         kind: MacroDefKind::BuiltInDerive(expander, ast_id),
65         local_inner: false,
66     })
67 }
68
69 struct BasicAdtInfo {
70     name: tt::Ident,
71     type_params: usize,
72 }
73
74 fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, mbe::ExpandError> {
75     let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, mbe::ParserEntryPoint::Items)?; // FragmentKind::Items doesn't parse attrs?
76     let macro_items = ast::MacroItems::cast(parsed.syntax_node()).ok_or_else(|| {
77         debug!("derive node didn't parse");
78         mbe::ExpandError::UnexpectedToken
79     })?;
80     let item = macro_items.items().next().ok_or_else(|| {
81         debug!("no module item parsed");
82         mbe::ExpandError::NoMatchingRule
83     })?;
84     let node = item.syntax();
85     let (name, params) = match_ast! {
86         match node {
87             ast::Struct(it) => (it.name(), it.generic_param_list()),
88             ast::Enum(it) => (it.name(), it.generic_param_list()),
89             ast::Union(it) => (it.name(), it.generic_param_list()),
90             _ => {
91                 debug!("unexpected node is {:?}", node);
92                 return Err(mbe::ExpandError::ConversionError)
93             },
94         }
95     };
96     let name = name.ok_or_else(|| {
97         debug!("parsed item has no name");
98         mbe::ExpandError::NoMatchingRule
99     })?;
100     let name_token_id = token_map.token_by_range(name.syntax().text_range()).ok_or_else(|| {
101         debug!("name token not found");
102         mbe::ExpandError::ConversionError
103     })?;
104     let name_token = tt::Ident { id: name_token_id, text: name.text().into() };
105     let type_params = params.map_or(0, |type_param_list| type_param_list.type_params().count());
106     Ok(BasicAdtInfo { name: name_token, type_params })
107 }
108
109 fn make_type_args(n: usize, bound: Vec<tt::TokenTree>) -> Vec<tt::TokenTree> {
110     let mut result = Vec::<tt::TokenTree>::with_capacity(n * 2);
111     result.push(
112         tt::Leaf::Punct(tt::Punct {
113             char: '<',
114             spacing: tt::Spacing::Alone,
115             id: tt::TokenId::unspecified(),
116         })
117         .into(),
118     );
119     for i in 0..n {
120         if i > 0 {
121             result.push(
122                 tt::Leaf::Punct(tt::Punct {
123                     char: ',',
124                     spacing: tt::Spacing::Alone,
125                     id: tt::TokenId::unspecified(),
126                 })
127                 .into(),
128             );
129         }
130         result.push(
131             tt::Leaf::Ident(tt::Ident {
132                 id: tt::TokenId::unspecified(),
133                 text: format!("T{}", i).into(),
134             })
135             .into(),
136         );
137         result.extend(bound.iter().cloned());
138     }
139     result.push(
140         tt::Leaf::Punct(tt::Punct {
141             char: '>',
142             spacing: tt::Spacing::Alone,
143             id: tt::TokenId::unspecified(),
144         })
145         .into(),
146     );
147     result
148 }
149
150 fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResult<tt::Subtree> {
151     let info = match parse_adt(tt) {
152         Ok(info) => info,
153         Err(e) => return ExpandResult::only_err(e),
154     };
155     let name = info.name;
156     let trait_path_clone = trait_path.token_trees.clone();
157     let bound = (quote! { : ##trait_path_clone }).token_trees;
158     let type_params = make_type_args(info.type_params, bound);
159     let type_args = make_type_args(info.type_params, Vec::new());
160     let trait_path = trait_path.token_trees;
161     let expanded = quote! {
162         impl ##type_params ##trait_path for #name ##type_args {}
163     };
164     ExpandResult::ok(expanded)
165 }
166
167 fn find_builtin_crate(db: &dyn AstDatabase, id: MacroCallId) -> tt::TokenTree {
168     // FIXME: make hygiene works for builtin derive macro
169     // such that $crate can be used here.
170     let cg = db.crate_graph();
171     let krate = db.lookup_intern_macro(id).krate;
172
173     // XXX
174     //  All crates except core itself should have a dependency on core,
175     //  We detect `core` by seeing whether it doesn't have such a dependency.
176     let tt = if cg[krate].dependencies.iter().any(|dep| &*dep.name == "core") {
177         quote! { core }
178     } else {
179         quote! { crate }
180     };
181
182     tt.token_trees[0].clone()
183 }
184
185 fn copy_expand(
186     db: &dyn AstDatabase,
187     id: MacroCallId,
188     tt: &tt::Subtree,
189 ) -> ExpandResult<tt::Subtree> {
190     let krate = find_builtin_crate(db, id);
191     expand_simple_derive(tt, quote! { #krate::marker::Copy })
192 }
193
194 fn clone_expand(
195     db: &dyn AstDatabase,
196     id: MacroCallId,
197     tt: &tt::Subtree,
198 ) -> ExpandResult<tt::Subtree> {
199     let krate = find_builtin_crate(db, id);
200     expand_simple_derive(tt, quote! { #krate::clone::Clone })
201 }
202
203 fn default_expand(
204     db: &dyn AstDatabase,
205     id: MacroCallId,
206     tt: &tt::Subtree,
207 ) -> ExpandResult<tt::Subtree> {
208     let krate = find_builtin_crate(db, id);
209     expand_simple_derive(tt, quote! { #krate::default::Default })
210 }
211
212 fn debug_expand(
213     db: &dyn AstDatabase,
214     id: MacroCallId,
215     tt: &tt::Subtree,
216 ) -> ExpandResult<tt::Subtree> {
217     let krate = find_builtin_crate(db, id);
218     expand_simple_derive(tt, quote! { #krate::fmt::Debug })
219 }
220
221 fn hash_expand(
222     db: &dyn AstDatabase,
223     id: MacroCallId,
224     tt: &tt::Subtree,
225 ) -> ExpandResult<tt::Subtree> {
226     let krate = find_builtin_crate(db, id);
227     expand_simple_derive(tt, quote! { #krate::hash::Hash })
228 }
229
230 fn eq_expand(db: &dyn AstDatabase, id: MacroCallId, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> {
231     let krate = find_builtin_crate(db, id);
232     expand_simple_derive(tt, quote! { #krate::cmp::Eq })
233 }
234
235 fn partial_eq_expand(
236     db: &dyn AstDatabase,
237     id: MacroCallId,
238     tt: &tt::Subtree,
239 ) -> ExpandResult<tt::Subtree> {
240     let krate = find_builtin_crate(db, id);
241     expand_simple_derive(tt, quote! { #krate::cmp::PartialEq })
242 }
243
244 fn ord_expand(
245     db: &dyn AstDatabase,
246     id: MacroCallId,
247     tt: &tt::Subtree,
248 ) -> ExpandResult<tt::Subtree> {
249     let krate = find_builtin_crate(db, id);
250     expand_simple_derive(tt, quote! { #krate::cmp::Ord })
251 }
252
253 fn partial_ord_expand(
254     db: &dyn AstDatabase,
255     id: MacroCallId,
256     tt: &tt::Subtree,
257 ) -> ExpandResult<tt::Subtree> {
258     let krate = find_builtin_crate(db, id);
259     expand_simple_derive(tt, quote! { #krate::cmp::PartialOrd })
260 }
261
262 #[cfg(test)]
263 mod tests {
264     use base_db::{fixture::WithFixture, CrateId, SourceDatabase};
265     use expect_test::{expect, Expect};
266     use name::AsName;
267
268     use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc};
269
270     use super::*;
271
272     fn expand_builtin_derive(ra_fixture: &str) -> String {
273         let fixture = format!(
274             r#"//- /main.rs crate:main deps:core
275 $0
276 {}
277 //- /lib.rs crate:core
278 // empty
279 "#,
280             ra_fixture
281         );
282
283         let (db, file_pos) = TestDB::with_position(&fixture);
284         let file_id = file_pos.file_id;
285         let ast_id_map = db.ast_id_map(file_id.into());
286         let parsed = db.parse(file_id);
287         let macros: Vec<_> =
288             parsed.syntax_node().descendants().filter_map(ast::Macro::cast).collect();
289         let items: Vec<_> = parsed
290             .syntax_node()
291             .descendants()
292             .filter(|node| !ast::Macro::can_cast(node.kind()))
293             .filter_map(ast::Item::cast)
294             .collect();
295
296         assert_eq!(macros.len(), 1, "test must contain exactly 1 macro definition");
297         assert_eq!(items.len(), 1, "test must contain exactly 1 item");
298
299         let macro_ast_id = AstId::new(file_id.into(), ast_id_map.ast_id(&macros[0]));
300         let name = match &macros[0] {
301             ast::Macro::MacroRules(rules) => rules.name().unwrap().as_name(),
302             ast::Macro::MacroDef(def) => def.name().unwrap().as_name(),
303         };
304
305         let expander = BuiltinDeriveExpander::find_by_name(&name).unwrap();
306
307         let ast_id = AstId::new(file_id.into(), ast_id_map.ast_id(&items[0]));
308
309         let loc = MacroCallLoc {
310             def: MacroDefId {
311                 krate: CrateId(0),
312                 kind: MacroDefKind::BuiltInDerive(expander, macro_ast_id),
313                 local_inner: false,
314             },
315             krate: CrateId(0),
316             eager: None,
317             kind: MacroCallKind::Derive {
318                 ast_id,
319                 derive_name: name.to_string(),
320                 derive_attr_index: 0,
321             },
322         };
323
324         let id: MacroCallId = db.intern_macro(loc);
325         let parsed = db.parse_or_expand(id.as_file()).unwrap();
326
327         // FIXME text() for syntax nodes parsed from token tree looks weird
328         // because there's no whitespace, see below
329         parsed.text().to_string()
330     }
331
332     fn check_derive(ra_fixture: &str, expected: Expect) {
333         let expanded = expand_builtin_derive(ra_fixture);
334         expected.assert_eq(&expanded);
335     }
336
337     #[test]
338     fn test_copy_expand_simple() {
339         check_derive(
340             r#"
341             macro Copy {}
342             #[derive(Copy)]
343             struct Foo;
344             "#,
345             expect![["impl< >core::marker::CopyforFoo< >{}"]],
346         );
347     }
348
349     #[test]
350     fn test_copy_expand_with_type_params() {
351         check_derive(
352             r#"
353             macro Copy {}
354             #[derive(Copy)]
355             struct Foo<A, B>;
356             "#,
357             expect![["impl<T0:core::marker::Copy,T1:core::marker::Copy>core::marker::CopyforFoo<T0,T1>{}"]],
358         );
359     }
360
361     #[test]
362     fn test_copy_expand_with_lifetimes() {
363         check_derive(
364             r#"
365             macro Copy {}
366             #[derive(Copy)]
367             struct Foo<A, B, 'a, 'b>;
368             "#,
369             // We currently just ignore lifetimes
370             expect![["impl<T0:core::marker::Copy,T1:core::marker::Copy>core::marker::CopyforFoo<T0,T1>{}"]],
371         );
372     }
373
374     #[test]
375     fn test_clone_expand() {
376         check_derive(
377             r#"
378             macro Clone {}
379             #[derive(Clone)]
380             struct Foo<A, B>;
381             "#,
382             expect![["impl<T0:core::clone::Clone,T1:core::clone::Clone>core::clone::CloneforFoo<T0,T1>{}"]],
383         );
384     }
385 }