aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Diebold <[email protected]>2019-12-05 18:29:57 +0000
committerFlorian Diebold <[email protected]>2019-12-05 18:29:57 +0000
commitdb8a00bd99cdc10ae8166fca3827eefebf791471 (patch)
treeb6149545fecab05613466689f6e21b5be3e8bd21
parentab4ecca210d8d280a4e216c2b6edfff303269144 (diff)
Implement derive(Copy, Clone) properly (well, kind of)
-rw-r--r--crates/ra_hir_expand/src/builtin_derive.rs187
-rw-r--r--crates/ra_hir_expand/src/quote.rs10
-rw-r--r--crates/ra_hir_ty/src/tests/macros.rs51
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.
2use crate::db::AstDatabase;
3use crate::{name, MacroCallId, MacroDefId, MacroDefKind};
4 2
5use crate::quote; 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};
6 13
7macro_rules! register_builtin { 14macro_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
51struct BasicAdtInfo {
52 name: tt::Ident,
53 type_params: usize,
54}
55
56fn 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
91fn 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
44fn copy_expand( 112fn 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(
55fn clone_expand( 128fn 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)]
145mod 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]
271fn infer_derive_clone_simple() {
272 let (db, pos) = TestDB::with_position(
273 r#"
274//- /main.rs crate:main deps:std
275#[derive(Clone)]
276struct S;
277fn test() {
278 S.clone()<|>;
279}
280
281//- /lib.rs crate:std
282#[prelude_import]
283use clone::*;
284mod 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]
295fn infer_derive_clone_with_params() {
296 let (db, pos) = TestDB::with_position(
297 r#"
298//- /main.rs crate:main deps:std
299#[derive(Clone)]
300struct S;
301#[derive(Clone)]
302struct Wrapper<T>(T);
303struct NonClone;
304fn test() {
305 (Wrapper(S).clone(), Wrapper(NonClone).clone())<|>;
306}
307
308//- /lib.rs crate:std
309#[prelude_import]
310use clone::*;
311mod 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}