diff options
Diffstat (limited to 'crates/ra_hir_expand/src/builtin_derive.rs')
-rw-r--r-- | crates/ra_hir_expand/src/builtin_derive.rs | 301 |
1 files changed, 301 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..78fa9b09a --- /dev/null +++ b/crates/ra_hir_expand/src/builtin_derive.rs | |||
@@ -0,0 +1,301 @@ | |||
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 | ( $(($name:ident, $kind: ident) => $expand:ident),* ) => { | ||
16 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
17 | pub enum BuiltinDeriveExpander { | ||
18 | $($kind),* | ||
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::$kind => $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 => BuiltinDeriveExpander::$kind, )* | ||
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_TRAIT, Copy) => copy_expand, | ||
48 | (CLONE_TRAIT, Clone) => clone_expand, | ||
49 | (DEFAULT_TRAIT, Default) => default_expand, | ||
50 | (DEBUG_TRAIT, Debug) => debug_expand, | ||
51 | (HASH_TRAIT, Hash) => hash_expand, | ||
52 | (ORD_TRAIT, Ord) => ord_expand, | ||
53 | (PARTIAL_ORD_TRAIT, PartialOrd) => partial_ord_expand, | ||
54 | (EQ_TRAIT, Eq) => eq_expand, | ||
55 | (PARTIAL_EQ_TRAIT, 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(tt::Leaf::Punct(tt::Punct { char: '<', spacing: tt::Spacing::Alone }).into()); | ||
101 | for i in 0..n { | ||
102 | if i > 0 { | ||
103 | result | ||
104 | .push(tt::Leaf::Punct(tt::Punct { char: ',', spacing: tt::Spacing::Alone }).into()); | ||
105 | } | ||
106 | result.push( | ||
107 | tt::Leaf::Ident(tt::Ident { | ||
108 | id: tt::TokenId::unspecified(), | ||
109 | text: format!("T{}", i).into(), | ||
110 | }) | ||
111 | .into(), | ||
112 | ); | ||
113 | result.extend(bound.iter().cloned()); | ||
114 | } | ||
115 | result.push(tt::Leaf::Punct(tt::Punct { char: '>', spacing: tt::Spacing::Alone }).into()); | ||
116 | result | ||
117 | } | ||
118 | |||
119 | fn expand_simple_derive( | ||
120 | tt: &tt::Subtree, | ||
121 | trait_path: tt::Subtree, | ||
122 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
123 | let info = parse_adt(tt)?; | ||
124 | let name = info.name; | ||
125 | let trait_path_clone = trait_path.token_trees.clone(); | ||
126 | let bound = (quote! { : ##trait_path_clone }).token_trees; | ||
127 | let type_params = make_type_args(info.type_params, bound); | ||
128 | let type_args = make_type_args(info.type_params, Vec::new()); | ||
129 | let trait_path = trait_path.token_trees; | ||
130 | let expanded = quote! { | ||
131 | impl ##type_params ##trait_path for #name ##type_args {} | ||
132 | }; | ||
133 | Ok(expanded) | ||
134 | } | ||
135 | |||
136 | fn copy_expand( | ||
137 | _db: &dyn AstDatabase, | ||
138 | _id: MacroCallId, | ||
139 | tt: &tt::Subtree, | ||
140 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
141 | expand_simple_derive(tt, quote! { std::marker::Copy }) | ||
142 | } | ||
143 | |||
144 | fn clone_expand( | ||
145 | _db: &dyn AstDatabase, | ||
146 | _id: MacroCallId, | ||
147 | tt: &tt::Subtree, | ||
148 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
149 | expand_simple_derive(tt, quote! { std::clone::Clone }) | ||
150 | } | ||
151 | |||
152 | fn default_expand( | ||
153 | _db: &dyn AstDatabase, | ||
154 | _id: MacroCallId, | ||
155 | tt: &tt::Subtree, | ||
156 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
157 | expand_simple_derive(tt, quote! { std::default::Default }) | ||
158 | } | ||
159 | |||
160 | fn debug_expand( | ||
161 | _db: &dyn AstDatabase, | ||
162 | _id: MacroCallId, | ||
163 | tt: &tt::Subtree, | ||
164 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
165 | expand_simple_derive(tt, quote! { std::fmt::Debug }) | ||
166 | } | ||
167 | |||
168 | fn hash_expand( | ||
169 | _db: &dyn AstDatabase, | ||
170 | _id: MacroCallId, | ||
171 | tt: &tt::Subtree, | ||
172 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
173 | expand_simple_derive(tt, quote! { std::hash::Hash }) | ||
174 | } | ||
175 | |||
176 | fn eq_expand( | ||
177 | _db: &dyn AstDatabase, | ||
178 | _id: MacroCallId, | ||
179 | tt: &tt::Subtree, | ||
180 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
181 | expand_simple_derive(tt, quote! { std::cmp::Eq }) | ||
182 | } | ||
183 | |||
184 | fn partial_eq_expand( | ||
185 | _db: &dyn AstDatabase, | ||
186 | _id: MacroCallId, | ||
187 | tt: &tt::Subtree, | ||
188 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
189 | expand_simple_derive(tt, quote! { std::cmp::PartialEq }) | ||
190 | } | ||
191 | |||
192 | fn ord_expand( | ||
193 | _db: &dyn AstDatabase, | ||
194 | _id: MacroCallId, | ||
195 | tt: &tt::Subtree, | ||
196 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
197 | expand_simple_derive(tt, quote! { std::cmp::Ord }) | ||
198 | } | ||
199 | |||
200 | fn partial_ord_expand( | ||
201 | _db: &dyn AstDatabase, | ||
202 | _id: MacroCallId, | ||
203 | tt: &tt::Subtree, | ||
204 | ) -> Result<tt::Subtree, mbe::ExpandError> { | ||
205 | expand_simple_derive(tt, quote! { std::cmp::PartialOrd }) | ||
206 | } | ||
207 | |||
208 | #[cfg(test)] | ||
209 | mod tests { | ||
210 | use super::*; | ||
211 | use crate::{test_db::TestDB, AstId, MacroCallKind, MacroCallLoc, MacroFileKind}; | ||
212 | use ra_db::{fixture::WithFixture, SourceDatabase}; | ||
213 | |||
214 | fn expand_builtin_derive(s: &str, expander: BuiltinDeriveExpander) -> String { | ||
215 | let (db, file_id) = TestDB::with_single_file(&s); | ||
216 | let parsed = db.parse(file_id); | ||
217 | let items: Vec<_> = | ||
218 | parsed.syntax_node().descendants().filter_map(|it| ast::ModuleItem::cast(it)).collect(); | ||
219 | |||
220 | let ast_id_map = db.ast_id_map(file_id.into()); | ||
221 | |||
222 | // the first one should be a macro_rules | ||
223 | let def = | ||
224 | MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(expander) }; | ||
225 | |||
226 | let loc = MacroCallLoc { | ||
227 | def, | ||
228 | kind: MacroCallKind::Attr(AstId::new(file_id.into(), ast_id_map.ast_id(&items[0]))), | ||
229 | }; | ||
230 | |||
231 | let id = db.intern_macro(loc); | ||
232 | let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Items)).unwrap(); | ||
233 | |||
234 | // FIXME text() for syntax nodes parsed from token tree looks weird | ||
235 | // because there's no whitespace, see below | ||
236 | parsed.text().to_string() | ||
237 | } | ||
238 | |||
239 | #[test] | ||
240 | fn test_copy_expand_simple() { | ||
241 | let expanded = expand_builtin_derive( | ||
242 | r#" | ||
243 | #[derive(Copy)] | ||
244 | struct Foo; | ||
245 | "#, | ||
246 | BuiltinDeriveExpander::Copy, | ||
247 | ); | ||
248 | |||
249 | assert_eq!(expanded, "impl <>std::marker::CopyforFoo <>{}"); | ||
250 | } | ||
251 | |||
252 | #[test] | ||
253 | fn test_copy_expand_with_type_params() { | ||
254 | let expanded = expand_builtin_derive( | ||
255 | r#" | ||
256 | #[derive(Copy)] | ||
257 | struct Foo<A, B>; | ||
258 | "#, | ||
259 | BuiltinDeriveExpander::Copy, | ||
260 | ); | ||
261 | |||
262 | assert_eq!( | ||
263 | expanded, | ||
264 | "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}" | ||
265 | ); | ||
266 | } | ||
267 | |||
268 | #[test] | ||
269 | fn test_copy_expand_with_lifetimes() { | ||
270 | let expanded = expand_builtin_derive( | ||
271 | r#" | ||
272 | #[derive(Copy)] | ||
273 | struct Foo<A, B, 'a, 'b>; | ||
274 | "#, | ||
275 | BuiltinDeriveExpander::Copy, | ||
276 | ); | ||
277 | |||
278 | // We currently just ignore lifetimes | ||
279 | |||
280 | assert_eq!( | ||
281 | expanded, | ||
282 | "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}" | ||
283 | ); | ||
284 | } | ||
285 | |||
286 | #[test] | ||
287 | fn test_clone_expand() { | ||
288 | let expanded = expand_builtin_derive( | ||
289 | r#" | ||
290 | #[derive(Clone)] | ||
291 | struct Foo<A, B>; | ||
292 | "#, | ||
293 | BuiltinDeriveExpander::Clone, | ||
294 | ); | ||
295 | |||
296 | assert_eq!( | ||
297 | expanded, | ||
298 | "impl<T0:std::clone::Clone,T1:std::clone::Clone>std::clone::CloneforFoo<T0,T1>{}" | ||
299 | ); | ||
300 | } | ||
301 | } | ||