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