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