diff options
Diffstat (limited to 'crates/ra_hir_expand/src/builtin_derive.rs')
-rw-r--r-- | crates/ra_hir_expand/src/builtin_derive.rs | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/crates/ra_hir_expand/src/builtin_derive.rs b/crates/ra_hir_expand/src/builtin_derive.rs new file mode 100644 index 000000000..62c60e336 --- /dev/null +++ b/crates/ra_hir_expand/src/builtin_derive.rs | |||
@@ -0,0 +1,321 @@ | |||
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, 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: MacroCallId, | ||
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 copy_expand( | ||
157 | _db: &dyn AstDatabase, | ||
158 | _id: MacroCallId, | ||
159 | tt: &tt::Subtree, | ||
160 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
161 | expand_simple_derive(tt, quote! { std::marker::Copy }) | ||
162 | } | ||
163 | |||
164 | fn clone_expand( | ||
165 | _db: &dyn AstDatabase, | ||
166 | _id: MacroCallId, | ||
167 | tt: &tt::Subtree, | ||
168 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
169 | expand_simple_derive(tt, quote! { std::clone::Clone }) | ||
170 | } | ||
171 | |||
172 | fn default_expand( | ||
173 | _db: &dyn AstDatabase, | ||
174 | _id: MacroCallId, | ||
175 | tt: &tt::Subtree, | ||
176 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
177 | expand_simple_derive(tt, quote! { std::default::Default }) | ||
178 | } | ||
179 | |||
180 | fn debug_expand( | ||
181 | _db: &dyn AstDatabase, | ||
182 | _id: MacroCallId, | ||
183 | tt: &tt::Subtree, | ||
184 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
185 | expand_simple_derive(tt, quote! { std::fmt::Debug }) | ||
186 | } | ||
187 | |||
188 | fn hash_expand( | ||
189 | _db: &dyn AstDatabase, | ||
190 | _id: MacroCallId, | ||
191 | tt: &tt::Subtree, | ||
192 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
193 | expand_simple_derive(tt, quote! { std::hash::Hash }) | ||
194 | } | ||
195 | |||
196 | fn eq_expand( | ||
197 | _db: &dyn AstDatabase, | ||
198 | _id: MacroCallId, | ||
199 | tt: &tt::Subtree, | ||
200 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
201 | expand_simple_derive(tt, quote! { std::cmp::Eq }) | ||
202 | } | ||
203 | |||
204 | fn partial_eq_expand( | ||
205 | _db: &dyn AstDatabase, | ||
206 | _id: MacroCallId, | ||
207 | tt: &tt::Subtree, | ||
208 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
209 | expand_simple_derive(tt, quote! { std::cmp::PartialEq }) | ||
210 | } | ||
211 | |||
212 | fn ord_expand( | ||
213 | _db: &dyn AstDatabase, | ||
214 | _id: MacroCallId, | ||
215 | tt: &tt::Subtree, | ||
216 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
217 | expand_simple_derive(tt, quote! { std::cmp::Ord }) | ||
218 | } | ||
219 | |||
220 | fn partial_ord_expand( | ||
221 | _db: &dyn AstDatabase, | ||
222 | _id: MacroCallId, | ||
223 | tt: &tt::Subtree, | ||
224 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
225 | expand_simple_derive(tt, quote! { std::cmp::PartialOrd }) | ||
226 | } | ||
227 | |||
228 | #[cfg(test)] | ||
229 | mod tests { | ||
230 | use super::*; | ||
231 | use crate::{test_db::TestDB, AstId, MacroCallKind, MacroCallLoc}; | ||
232 | use ra_db::{fixture::WithFixture, SourceDatabase}; | ||
233 | |||
234 | fn expand_builtin_derive(s: &str, expander: BuiltinDeriveExpander) -> String { | ||
235 | let (db, file_id) = TestDB::with_single_file(&s); | ||
236 | let parsed = db.parse(file_id); | ||
237 | let items: Vec<_> = | ||
238 | parsed.syntax_node().descendants().filter_map(|it| ast::ModuleItem::cast(it)).collect(); | ||
239 | |||
240 | let ast_id_map = db.ast_id_map(file_id.into()); | ||
241 | |||
242 | // the first one should be a macro_rules | ||
243 | let def = | ||
244 | MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(expander) }; | ||
245 | |||
246 | let loc = MacroCallLoc { | ||
247 | def, | ||
248 | kind: MacroCallKind::Attr(AstId::new(file_id.into(), ast_id_map.ast_id(&items[0]))), | ||
249 | }; | ||
250 | |||
251 | let id = db.intern_macro(loc); | ||
252 | let parsed = db.parse_or_expand(id.as_file()).unwrap(); | ||
253 | |||
254 | // FIXME text() for syntax nodes parsed from token tree looks weird | ||
255 | // because there's no whitespace, see below | ||
256 | parsed.text().to_string() | ||
257 | } | ||
258 | |||
259 | #[test] | ||
260 | fn test_copy_expand_simple() { | ||
261 | let expanded = expand_builtin_derive( | ||
262 | r#" | ||
263 | #[derive(Copy)] | ||
264 | struct Foo; | ||
265 | "#, | ||
266 | BuiltinDeriveExpander::Copy, | ||
267 | ); | ||
268 | |||
269 | assert_eq!(expanded, "impl <>std::marker::CopyforFoo <>{}"); | ||
270 | } | ||
271 | |||
272 | #[test] | ||
273 | fn test_copy_expand_with_type_params() { | ||
274 | let expanded = expand_builtin_derive( | ||
275 | r#" | ||
276 | #[derive(Copy)] | ||
277 | struct Foo<A, B>; | ||
278 | "#, | ||
279 | BuiltinDeriveExpander::Copy, | ||
280 | ); | ||
281 | |||
282 | assert_eq!( | ||
283 | expanded, | ||
284 | "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}" | ||
285 | ); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn test_copy_expand_with_lifetimes() { | ||
290 | let expanded = expand_builtin_derive( | ||
291 | r#" | ||
292 | #[derive(Copy)] | ||
293 | struct Foo<A, B, 'a, 'b>; | ||
294 | "#, | ||
295 | BuiltinDeriveExpander::Copy, | ||
296 | ); | ||
297 | |||
298 | // We currently just ignore lifetimes | ||
299 | |||
300 | assert_eq!( | ||
301 | expanded, | ||
302 | "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}" | ||
303 | ); | ||
304 | } | ||
305 | |||
306 | #[test] | ||
307 | fn test_clone_expand() { | ||
308 | let expanded = expand_builtin_derive( | ||
309 | r#" | ||
310 | #[derive(Clone)] | ||
311 | struct Foo<A, B>; | ||
312 | "#, | ||
313 | BuiltinDeriveExpander::Clone, | ||
314 | ); | ||
315 | |||
316 | assert_eq!( | ||
317 | expanded, | ||
318 | "impl<T0:std::clone::Clone,T1:std::clone::Clone>std::clone::CloneforFoo<T0,T1>{}" | ||
319 | ); | ||
320 | } | ||
321 | } | ||