aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/generate_deref.rs128
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 @@
1use std::fmt::Display;
2
1use ide_db::{helpers::FamousDefs, RootDatabase}; 3use ide_db::{helpers::FamousDefs, RootDatabase};
2use syntax::{ 4use syntax::{
3 ast::{self, NameOwner}, 5 ast::{self, NameOwner},
4 AstNode, 6 AstNode, SyntaxNode,
5}; 7};
6 8
7use crate::{ 9use 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// ```
38pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 40pub(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
44fn 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
64fn 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
86fn 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
76fn existing_deref_impl( 106fn 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 { }
153struct B($0A);"#,
154 r#"struct A { }
155struct B(A);
156
157impl 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 { }
171struct B(u8, $0A);"#,
172 r#"struct A { }
173struct B(u8, A);
174
175impl 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 { }
132struct B { $0a: A } 199struct 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 { }
216struct B($0A)
217
218impl 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}