aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_expand/src/builtin_derive.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_expand/src/builtin_derive.rs')
-rw-r--r--crates/ra_hir_expand/src/builtin_derive.rs301
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
3use log::debug;
4
5use ra_parser::FragmentKind;
6use ra_syntax::{
7 ast::{self, AstNode, ModuleItemOwner, NameOwner, TypeParamsOwner},
8 match_ast,
9};
10
11use crate::db::AstDatabase;
12use crate::{name, quote, MacroCallId, MacroDefId, MacroDefKind};
13
14macro_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
46register_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
58struct BasicAdtInfo {
59 name: tt::Ident,
60 type_params: usize,
61}
62
63fn 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
98fn 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
119fn 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
136fn 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
144fn 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
152fn 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
160fn 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
168fn 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
176fn 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
184fn 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
192fn 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
200fn 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)]
209mod 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}