diff options
-rw-r--r-- | crates/ide_assists/src/handlers/generate_deref.rs | 128 |
1 files changed, 106 insertions, 22 deletions
diff --git a/crates/ide_assists/src/handlers/generate_deref.rs b/crates/ide_assists/src/handlers/generate_deref.rs index a8126e414..4998ff7a4 100644 --- a/crates/ide_assists/src/handlers/generate_deref.rs +++ b/crates/ide_assists/src/handlers/generate_deref.rs | |||
@@ -1,11 +1,13 @@ | |||
1 | use std::fmt::Display; | ||
2 | |||
1 | use ide_db::{helpers::FamousDefs, RootDatabase}; | 3 | use ide_db::{helpers::FamousDefs, RootDatabase}; |
2 | use syntax::{ | 4 | use syntax::{ |
3 | ast::{self, NameOwner}, | 5 | ast::{self, NameOwner}, |
4 | AstNode, | 6 | AstNode, SyntaxNode, |
5 | }; | 7 | }; |
6 | 8 | ||
7 | use crate::{ | 9 | use crate::{ |
8 | assist_context::{AssistContext, Assists}, | 10 | assist_context::{AssistBuilder, AssistContext, Assists}, |
9 | utils::generate_trait_impl_text, | 11 | utils::generate_trait_impl_text, |
10 | AssistId, AssistKind, | 12 | AssistId, AssistKind, |
11 | }; | 13 | }; |
@@ -36,11 +38,15 @@ use crate::{ | |||
36 | // } | 38 | // } |
37 | // ``` | 39 | // ``` |
38 | pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 40 | pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
41 | generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx)) | ||
42 | } | ||
43 | |||
44 | fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
39 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | 45 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; |
40 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; | 46 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; |
41 | 47 | ||
42 | if existing_deref_impl(&ctx.sema, &strukt).is_some() { | 48 | if existing_deref_impl(&ctx.sema, &strukt).is_some() { |
43 | cov_mark::hit!(test_add_deref_impl_already_exists); | 49 | cov_mark::hit!(test_add_record_deref_impl_already_exists); |
44 | return None; | 50 | return None; |
45 | } | 51 | } |
46 | 52 | ||
@@ -51,26 +57,50 @@ pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
51 | AssistId("generate_deref", AssistKind::Generate), | 57 | AssistId("generate_deref", AssistKind::Generate), |
52 | format!("Generate `Deref` impl using `{}`", field_name), | 58 | format!("Generate `Deref` impl using `{}`", field_name), |
53 | target, | 59 | target, |
54 | |edit| { | 60 | |edit| generate_edit(edit, strukt, field_type.syntax(), field_name.syntax()), |
55 | let start_offset = strukt.syntax().text_range().end(); | 61 | ) |
56 | let impl_code = format!( | 62 | } |
57 | r#" type Target = {0}; | 63 | |
64 | fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
65 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
66 | let field = ctx.find_node_at_offset::<ast::TupleField>()?; | ||
67 | let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?; | ||
68 | let field_list_index = | ||
69 | field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?; | ||
70 | |||
71 | if existing_deref_impl(&ctx.sema, &strukt).is_some() { | ||
72 | cov_mark::hit!(test_add_field_deref_impl_already_exists); | ||
73 | return None; | ||
74 | } | ||
75 | |||
76 | let field_type = field.ty()?; | ||
77 | let target = field.syntax().text_range(); | ||
78 | acc.add( | ||
79 | AssistId("generate_deref", AssistKind::Generate), | ||
80 | format!("Generate `Deref` impl using `{}`", field.syntax()), | ||
81 | target, | ||
82 | |edit| generate_edit(edit, strukt, field_type.syntax(), field_list_index), | ||
83 | ) | ||
84 | } | ||
85 | |||
86 | fn generate_edit( | ||
87 | edit: &mut AssistBuilder, | ||
88 | strukt: ast::Struct, | ||
89 | field_type_syntax: &SyntaxNode, | ||
90 | field_name: impl Display, | ||
91 | ) { | ||
92 | let start_offset = strukt.syntax().text_range().end(); | ||
93 | let impl_code = format!( | ||
94 | r#" type Target = {0}; | ||
58 | 95 | ||
59 | fn deref(&self) -> &Self::Target {{ | 96 | fn deref(&self) -> &Self::Target {{ |
60 | &self.{1} | 97 | &self.{1} |
61 | }}"#, | 98 | }}"#, |
62 | field_type.syntax(), | 99 | field_type_syntax, field_name |
63 | field_name.syntax() | 100 | ); |
64 | ); | 101 | let strukt_adt = ast::Adt::Struct(strukt); |
65 | let strukt_adt = ast::Adt::Struct(strukt); | 102 | let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code); |
66 | // Q for reviewer: Is there a better way to specify the trait_text, e.g. | 103 | edit.insert(start_offset, deref_impl); |
67 | // - can I have it auto `use std::ops::Deref`, and then just use `Deref` as the trait text? | ||
68 | // Or is there a helper that might detect if `std::ops::Deref` has been used, and pick `Deref`, | ||
69 | // otherwise, pick `std::ops::Deref` for the trait_text. | ||
70 | let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code); | ||
71 | edit.insert(start_offset, deref_impl); | ||
72 | }, | ||
73 | ) | ||
74 | } | 104 | } |
75 | 105 | ||
76 | fn existing_deref_impl( | 106 | fn existing_deref_impl( |
@@ -97,7 +127,7 @@ mod tests { | |||
97 | use super::*; | 127 | use super::*; |
98 | 128 | ||
99 | #[test] | 129 | #[test] |
100 | fn test_generate_deref() { | 130 | fn test_generate_record_deref() { |
101 | check_assist( | 131 | check_assist( |
102 | generate_deref, | 132 | generate_deref, |
103 | r#"struct A { } | 133 | r#"struct A { } |
@@ -115,6 +145,43 @@ impl std::ops::Deref for B { | |||
115 | ); | 145 | ); |
116 | } | 146 | } |
117 | 147 | ||
148 | #[test] | ||
149 | fn test_generate_field_deref_idx_0() { | ||
150 | check_assist( | ||
151 | generate_deref, | ||
152 | r#"struct A { } | ||
153 | struct B($0A);"#, | ||
154 | r#"struct A { } | ||
155 | struct B(A); | ||
156 | |||
157 | impl std::ops::Deref for B { | ||
158 | type Target = A; | ||
159 | |||
160 | fn deref(&self) -> &Self::Target { | ||
161 | &self.0 | ||
162 | } | ||
163 | }"#, | ||
164 | ); | ||
165 | } | ||
166 | #[test] | ||
167 | fn test_generate_field_deref_idx_1() { | ||
168 | check_assist( | ||
169 | generate_deref, | ||
170 | r#"struct A { } | ||
171 | struct B(u8, $0A);"#, | ||
172 | r#"struct A { } | ||
173 | struct B(u8, A); | ||
174 | |||
175 | impl std::ops::Deref for B { | ||
176 | type Target = A; | ||
177 | |||
178 | fn deref(&self) -> &Self::Target { | ||
179 | &self.1 | ||
180 | } | ||
181 | }"#, | ||
182 | ); | ||
183 | } | ||
184 | |||
118 | fn check_not_applicable(ra_fixture: &str) { | 185 | fn check_not_applicable(ra_fixture: &str) { |
119 | let fixture = format!( | 186 | let fixture = format!( |
120 | "//- /main.rs crate:main deps:core,std\n{}\n{}", | 187 | "//- /main.rs crate:main deps:core,std\n{}\n{}", |
@@ -125,8 +192,8 @@ impl std::ops::Deref for B { | |||
125 | } | 192 | } |
126 | 193 | ||
127 | #[test] | 194 | #[test] |
128 | fn test_generate_deref_not_applicable_if_already_impl() { | 195 | fn test_generate_record_deref_not_applicable_if_already_impl() { |
129 | cov_mark::check!(test_add_deref_impl_already_exists); | 196 | cov_mark::check!(test_add_record_deref_impl_already_exists); |
130 | check_not_applicable( | 197 | check_not_applicable( |
131 | r#"struct A { } | 198 | r#"struct A { } |
132 | struct B { $0a: A } | 199 | struct B { $0a: A } |
@@ -140,4 +207,21 @@ impl std::ops::Deref for B { | |||
140 | }"#, | 207 | }"#, |
141 | ) | 208 | ) |
142 | } | 209 | } |
210 | |||
211 | #[test] | ||
212 | fn test_generate_field_deref_not_applicable_if_already_impl() { | ||
213 | cov_mark::check!(test_add_field_deref_impl_already_exists); | ||
214 | check_not_applicable( | ||
215 | r#"struct A { } | ||
216 | struct B($0A) | ||
217 | |||
218 | impl std::ops::Deref for B { | ||
219 | type Target = A; | ||
220 | |||
221 | fn deref(&self) -> &Self::Target { | ||
222 | &self.0 | ||
223 | } | ||
224 | }"#, | ||
225 | ) | ||
226 | } | ||
143 | } | 227 | } |