diff options
-rw-r--r-- | crates/ra_hir_expand/src/builtin_derive.rs | 187 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/quote.rs | 10 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests/macros.rs | 51 |
3 files changed, 241 insertions, 7 deletions
diff --git a/crates/ra_hir_expand/src/builtin_derive.rs b/crates/ra_hir_expand/src/builtin_derive.rs index 0a70c63c0..fde50f7e6 100644 --- a/crates/ra_hir_expand/src/builtin_derive.rs +++ b/crates/ra_hir_expand/src/builtin_derive.rs | |||
@@ -1,8 +1,15 @@ | |||
1 | //! Builtin derives. | 1 | //! Builtin derives. |
2 | use crate::db::AstDatabase; | ||
3 | use crate::{name, MacroCallId, MacroDefId, MacroDefKind}; | ||
4 | 2 | ||
5 | use crate::quote; | 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}; | ||
6 | 13 | ||
7 | macro_rules! register_builtin { | 14 | macro_rules! register_builtin { |
8 | ( $(($name:ident, $kind: ident) => $expand:ident),* ) => { | 15 | ( $(($name:ident, $kind: ident) => $expand:ident),* ) => { |
@@ -41,13 +48,79 @@ register_builtin! { | |||
41 | (CLONE_TRAIT, Clone) => clone_expand | 48 | (CLONE_TRAIT, Clone) => clone_expand |
42 | } | 49 | } |
43 | 50 | ||
51 | struct BasicAdtInfo { | ||
52 | name: tt::Ident, | ||
53 | type_params: usize, | ||
54 | } | ||
55 | |||
56 | fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, mbe::ExpandError> { | ||
57 | let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, FragmentKind::Items)?; // FragmentKind::Items doesn't parse attrs? | ||
58 | let macro_items = ast::MacroItems::cast(parsed.syntax_node()).ok_or_else(|| { | ||
59 | debug!("derive node didn't parse"); | ||
60 | mbe::ExpandError::UnexpectedToken | ||
61 | })?; | ||
62 | let item = macro_items.items().next().ok_or_else(|| { | ||
63 | debug!("no module item parsed"); | ||
64 | mbe::ExpandError::NoMatchingRule | ||
65 | })?; | ||
66 | let node = item.syntax(); | ||
67 | let (name, params) = match_ast! { | ||
68 | match node { | ||
69 | ast::StructDef(it) => { (it.name(), it.type_param_list()) }, | ||
70 | ast::EnumDef(it) => { (it.name(), it.type_param_list()) }, | ||
71 | ast::UnionDef(it) => { (it.name(), it.type_param_list()) }, | ||
72 | _ => { | ||
73 | debug!("unexpected node is {:?}", node); | ||
74 | return Err(mbe::ExpandError::ConversionError) | ||
75 | }, | ||
76 | } | ||
77 | }; | ||
78 | let name = name.ok_or_else(|| { | ||
79 | debug!("parsed item has no name"); | ||
80 | mbe::ExpandError::NoMatchingRule | ||
81 | })?; | ||
82 | let name_token_id = token_map.token_by_range(name.syntax().text_range()).ok_or_else(|| { | ||
83 | debug!("name token not found"); | ||
84 | mbe::ExpandError::ConversionError | ||
85 | })?; | ||
86 | let name_token = tt::Ident { id: name_token_id, text: name.text().clone() }; | ||
87 | let type_params = params.map_or(0, |type_param_list| type_param_list.type_params().count()); | ||
88 | Ok(BasicAdtInfo { name: name_token, type_params }) | ||
89 | } | ||
90 | |||
91 | fn make_type_args(n: usize, bound: Vec<tt::TokenTree>) -> Vec<tt::TokenTree> { | ||
92 | let mut result = Vec::<tt::TokenTree>::new(); | ||
93 | result.push(tt::Leaf::Punct(tt::Punct { char: '<', spacing: tt::Spacing::Alone }).into()); | ||
94 | for i in 0..n { | ||
95 | if i > 0 { | ||
96 | result | ||
97 | .push(tt::Leaf::Punct(tt::Punct { char: ',', spacing: tt::Spacing::Alone }).into()); | ||
98 | } | ||
99 | result.push( | ||
100 | tt::Leaf::Ident(tt::Ident { | ||
101 | id: tt::TokenId::unspecified(), | ||
102 | text: format!("T{}", i).into(), | ||
103 | }) | ||
104 | .into(), | ||
105 | ); | ||
106 | result.extend(bound.iter().cloned()); | ||
107 | } | ||
108 | result.push(tt::Leaf::Punct(tt::Punct { char: '>', spacing: tt::Spacing::Alone }).into()); | ||
109 | result | ||
110 | } | ||
111 | |||
44 | fn copy_expand( | 112 | fn copy_expand( |
45 | _db: &dyn AstDatabase, | 113 | _db: &dyn AstDatabase, |
46 | _id: MacroCallId, | 114 | _id: MacroCallId, |
47 | _tt: &tt::Subtree, | 115 | tt: &tt::Subtree, |
48 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 116 | ) -> Result<tt::Subtree, mbe::ExpandError> { |
117 | let info = parse_adt(tt)?; | ||
118 | let name = info.name; | ||
119 | let bound = (quote! { : std::marker::Copy }).token_trees; | ||
120 | let type_params = make_type_args(info.type_params, bound); | ||
121 | let type_args = make_type_args(info.type_params, Vec::new()); | ||
49 | let expanded = quote! { | 122 | let expanded = quote! { |
50 | impl Copy for Foo {} | 123 | impl ##type_params std::marker::Copy for #name ##type_args {} |
51 | }; | 124 | }; |
52 | Ok(expanded) | 125 | Ok(expanded) |
53 | } | 126 | } |
@@ -55,10 +128,110 @@ fn copy_expand( | |||
55 | fn clone_expand( | 128 | fn clone_expand( |
56 | _db: &dyn AstDatabase, | 129 | _db: &dyn AstDatabase, |
57 | _id: MacroCallId, | 130 | _id: MacroCallId, |
58 | _tt: &tt::Subtree, | 131 | tt: &tt::Subtree, |
59 | ) -> Result<tt::Subtree, mbe::ExpandError> { | 132 | ) -> Result<tt::Subtree, mbe::ExpandError> { |
133 | let info = parse_adt(tt)?; | ||
134 | let name = info.name; | ||
135 | let bound = (quote! { : std::clone::Clone }).token_trees; | ||
136 | let type_params = make_type_args(info.type_params, bound); | ||
137 | let type_args = make_type_args(info.type_params, Vec::new()); | ||
60 | let expanded = quote! { | 138 | let expanded = quote! { |
61 | impl Clone for Foo {} | 139 | impl ##type_params std::clone::Clone for #name ##type_args {} |
62 | }; | 140 | }; |
63 | Ok(expanded) | 141 | Ok(expanded) |
64 | } | 142 | } |
143 | |||
144 | #[cfg(test)] | ||
145 | mod tests { | ||
146 | use super::*; | ||
147 | use crate::{test_db::TestDB, AstId, MacroCallKind, MacroCallLoc, MacroFileKind}; | ||
148 | use ra_db::{fixture::WithFixture, SourceDatabase}; | ||
149 | |||
150 | fn expand_builtin_derive(s: &str, expander: BuiltinDeriveExpander) -> String { | ||
151 | let (db, file_id) = TestDB::with_single_file(&s); | ||
152 | let parsed = db.parse(file_id); | ||
153 | let items: Vec<_> = | ||
154 | parsed.syntax_node().descendants().filter_map(|it| ast::ModuleItem::cast(it)).collect(); | ||
155 | |||
156 | let ast_id_map = db.ast_id_map(file_id.into()); | ||
157 | |||
158 | // the first one should be a macro_rules | ||
159 | let def = | ||
160 | MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(expander) }; | ||
161 | |||
162 | let loc = MacroCallLoc { | ||
163 | def, | ||
164 | kind: MacroCallKind::Attr(AstId::new(file_id.into(), ast_id_map.ast_id(&items[0]))), | ||
165 | }; | ||
166 | |||
167 | let id = db.intern_macro(loc); | ||
168 | let parsed = db.parse_or_expand(id.as_file(MacroFileKind::Items)).unwrap(); | ||
169 | |||
170 | // FIXME text() for syntax nodes parsed from token tree looks weird | ||
171 | // because there's no whitespace, see below | ||
172 | parsed.text().to_string() | ||
173 | } | ||
174 | |||
175 | #[test] | ||
176 | fn test_copy_expand_simple() { | ||
177 | let expanded = expand_builtin_derive( | ||
178 | r#" | ||
179 | #[derive(Copy)] | ||
180 | struct Foo; | ||
181 | "#, | ||
182 | BuiltinDeriveExpander::Copy, | ||
183 | ); | ||
184 | |||
185 | assert_eq!(expanded, "impl <>std::marker::CopyforFoo <>{}"); | ||
186 | } | ||
187 | |||
188 | #[test] | ||
189 | fn test_copy_expand_with_type_params() { | ||
190 | let expanded = expand_builtin_derive( | ||
191 | r#" | ||
192 | #[derive(Copy)] | ||
193 | struct Foo<A, B>; | ||
194 | "#, | ||
195 | BuiltinDeriveExpander::Copy, | ||
196 | ); | ||
197 | |||
198 | assert_eq!( | ||
199 | expanded, | ||
200 | "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}" | ||
201 | ); | ||
202 | } | ||
203 | |||
204 | #[test] | ||
205 | fn test_copy_expand_with_lifetimes() { | ||
206 | let expanded = expand_builtin_derive( | ||
207 | r#" | ||
208 | #[derive(Copy)] | ||
209 | struct Foo<A, B, 'a, 'b>; | ||
210 | "#, | ||
211 | BuiltinDeriveExpander::Copy, | ||
212 | ); | ||
213 | |||
214 | // We currently just ignore lifetimes | ||
215 | |||
216 | assert_eq!( | ||
217 | expanded, | ||
218 | "impl<T0:std::marker::Copy,T1:std::marker::Copy>std::marker::CopyforFoo<T0,T1>{}" | ||
219 | ); | ||
220 | } | ||
221 | |||
222 | #[test] | ||
223 | fn test_clone_expand() { | ||
224 | let expanded = expand_builtin_derive( | ||
225 | r#" | ||
226 | #[derive(Clone)] | ||
227 | struct Foo<A, B>; | ||
228 | "#, | ||
229 | BuiltinDeriveExpander::Clone, | ||
230 | ); | ||
231 | |||
232 | assert_eq!( | ||
233 | expanded, | ||
234 | "impl<T0:std::clone::Clone,T1:std::clone::Clone>std::clone::CloneforFoo<T0,T1>{}" | ||
235 | ); | ||
236 | } | ||
237 | } | ||
diff --git a/crates/ra_hir_expand/src/quote.rs b/crates/ra_hir_expand/src/quote.rs index 65a35e52f..4f698ff13 100644 --- a/crates/ra_hir_expand/src/quote.rs +++ b/crates/ra_hir_expand/src/quote.rs | |||
@@ -60,6 +60,15 @@ macro_rules! __quote { | |||
60 | } | 60 | } |
61 | }; | 61 | }; |
62 | 62 | ||
63 | ( ## $first:ident $($tail:tt)* ) => { | ||
64 | { | ||
65 | let mut tokens = $first.into_iter().map($crate::quote::ToTokenTree::to_token).collect::<Vec<tt::TokenTree>>(); | ||
66 | let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*)); | ||
67 | tokens.append(&mut tail_tokens); | ||
68 | tokens | ||
69 | } | ||
70 | }; | ||
71 | |||
63 | // Brace | 72 | // Brace |
64 | ( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) }; | 73 | ( { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE Brace $($tt)*) }; |
65 | // Bracket | 74 | // Bracket |
@@ -85,6 +94,7 @@ macro_rules! __quote { | |||
85 | ( & ) => {$crate::__quote!(@PUNCT '&')}; | 94 | ( & ) => {$crate::__quote!(@PUNCT '&')}; |
86 | ( , ) => {$crate::__quote!(@PUNCT ',')}; | 95 | ( , ) => {$crate::__quote!(@PUNCT ',')}; |
87 | ( : ) => {$crate::__quote!(@PUNCT ':')}; | 96 | ( : ) => {$crate::__quote!(@PUNCT ':')}; |
97 | ( :: ) => {$crate::__quote!(@PUNCT ':', ':')}; | ||
88 | ( . ) => {$crate::__quote!(@PUNCT '.')}; | 98 | ( . ) => {$crate::__quote!(@PUNCT '.')}; |
89 | 99 | ||
90 | ( $first:tt $($tail:tt)+ ) => { | 100 | ( $first:tt $($tail:tt)+ ) => { |
diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs index 0d9a35ce0..9c29a054e 100644 --- a/crates/ra_hir_ty/src/tests/macros.rs +++ b/crates/ra_hir_ty/src/tests/macros.rs | |||
@@ -266,3 +266,54 @@ fn main() { | |||
266 | "### | 266 | "### |
267 | ); | 267 | ); |
268 | } | 268 | } |
269 | |||
270 | #[test] | ||
271 | fn infer_derive_clone_simple() { | ||
272 | let (db, pos) = TestDB::with_position( | ||
273 | r#" | ||
274 | //- /main.rs crate:main deps:std | ||
275 | #[derive(Clone)] | ||
276 | struct S; | ||
277 | fn test() { | ||
278 | S.clone()<|>; | ||
279 | } | ||
280 | |||
281 | //- /lib.rs crate:std | ||
282 | #[prelude_import] | ||
283 | use clone::*; | ||
284 | mod clone { | ||
285 | trait Clone { | ||
286 | fn clone(&self) -> Self; | ||
287 | } | ||
288 | } | ||
289 | "#, | ||
290 | ); | ||
291 | assert_eq!("S", type_at_pos(&db, pos)); | ||
292 | } | ||
293 | |||
294 | #[test] | ||
295 | fn infer_derive_clone_with_params() { | ||
296 | let (db, pos) = TestDB::with_position( | ||
297 | r#" | ||
298 | //- /main.rs crate:main deps:std | ||
299 | #[derive(Clone)] | ||
300 | struct S; | ||
301 | #[derive(Clone)] | ||
302 | struct Wrapper<T>(T); | ||
303 | struct NonClone; | ||
304 | fn test() { | ||
305 | (Wrapper(S).clone(), Wrapper(NonClone).clone())<|>; | ||
306 | } | ||
307 | |||
308 | //- /lib.rs crate:std | ||
309 | #[prelude_import] | ||
310 | use clone::*; | ||
311 | mod clone { | ||
312 | trait Clone { | ||
313 | fn clone(&self) -> Self; | ||
314 | } | ||
315 | } | ||
316 | "#, | ||
317 | ); | ||
318 | assert_eq!("(Wrapper<S>, {unknown})", type_at_pos(&db, pos)); | ||
319 | } | ||