diff options
Diffstat (limited to 'crates/hir_expand/src/builtin_derive.rs')
-rw-r--r-- | crates/hir_expand/src/builtin_derive.rs | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/crates/hir_expand/src/builtin_derive.rs b/crates/hir_expand/src/builtin_derive.rs new file mode 100644 index 000000000..988a60d56 --- /dev/null +++ b/crates/hir_expand/src/builtin_derive.rs | |||
@@ -0,0 +1,361 @@ | |||
1 | //! Builtin derives. | ||
2 | |||
3 | use log::debug; | ||
4 | |||
5 | use parser::FragmentKind; | ||
6 | use 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 base_db::{fixture::WithFixture, CrateId, SourceDatabase}; | ||
257 | use name::{known, Name}; | ||
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 | } | ||