aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-04-19 05:54:04 +0100
committerGitHub <[email protected]>2021-04-19 05:54:04 +0100
commit3f432730dfc7df83fed2d78a97d3d0cf97ffc282 (patch)
tree6b284cf75eccc23a2dcaf398767bb939ea5f077e /crates
parent7570212a544b8e973a7d57be3657aae6465028a7 (diff)
parent3d1ca786f6355041de9205cadd0a235581dd5af3 (diff)
Merge #8467
8467: Adds impl Deref assist r=jhgg a=jhgg This PR adds a new `generate_deref` assist that automatically generates a deref impl for a given struct field. Check out this gif: ![2021-04-11_00-33-33](https://user-images.githubusercontent.com/5489149/114296006-b38e1000-9a5d-11eb-9112-807c01b8fd0a.gif) -- I have a few Q's: - [x] Should I write more tests, if so, what precisely should I test for? - [x] I have an inline question on line 65, can someone provide guidance? :) - [x] I can implement this for `ast::TupleField` too. But should it be a separate assist fn, or should I try and jam both into the `generate_deref`? - [x] I want to follow this up with an assist on `impl $0Deref for T {` which would automatically generate a `DerefMut` impl that mirrors the Deref as well, however, I could probably use some pointers on how to do that, since I'll have to reach into the ast of `fn deref` to grab the field that it's referencing for the `DerefMut` impl. Co-authored-by: jake <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ide_assists/src/handlers/generate_deref.rs227
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests.rs1
-rw-r--r--crates/ide_assists/src/tests/generated.rs27
-rw-r--r--crates/ide_db/src/helpers.rs4
-rw-r--r--crates/ide_db/src/helpers/famous_defs_fixture.rs8
6 files changed, 269 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/generate_deref.rs b/crates/ide_assists/src/handlers/generate_deref.rs
new file mode 100644
index 000000000..4998ff7a4
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_deref.rs
@@ -0,0 +1,227 @@
1use std::fmt::Display;
2
3use ide_db::{helpers::FamousDefs, RootDatabase};
4use syntax::{
5 ast::{self, NameOwner},
6 AstNode, SyntaxNode,
7};
8
9use crate::{
10 assist_context::{AssistBuilder, AssistContext, Assists},
11 utils::generate_trait_impl_text,
12 AssistId, AssistKind,
13};
14
15// Assist: generate_deref
16//
17// Generate `Deref` impl using the given struct field.
18//
19// ```
20// struct A;
21// struct B {
22// $0a: A
23// }
24// ```
25// ->
26// ```
27// struct A;
28// struct B {
29// a: A
30// }
31//
32// impl std::ops::Deref for B {
33// type Target = A;
34//
35// fn deref(&self) -> &Self::Target {
36// &self.a
37// }
38// }
39// ```
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<()> {
45 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
46 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
47
48 if existing_deref_impl(&ctx.sema, &strukt).is_some() {
49 cov_mark::hit!(test_add_record_deref_impl_already_exists);
50 return None;
51 }
52
53 let field_type = field.ty()?;
54 let field_name = field.name()?;
55 let target = field.syntax().text_range();
56 acc.add(
57 AssistId("generate_deref", AssistKind::Generate),
58 format!("Generate `Deref` impl using `{}`", field_name),
59 target,
60 |edit| generate_edit(edit, strukt, field_type.syntax(), field_name.syntax()),
61 )
62}
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};
95
96 fn deref(&self) -> &Self::Target {{
97 &self.{1}
98 }}"#,
99 field_type_syntax, field_name
100 );
101 let strukt_adt = ast::Adt::Struct(strukt);
102 let deref_impl = generate_trait_impl_text(&strukt_adt, "std::ops::Deref", &impl_code);
103 edit.insert(start_offset, deref_impl);
104}
105
106fn existing_deref_impl(
107 sema: &'_ hir::Semantics<'_, RootDatabase>,
108 strukt: &ast::Struct,
109) -> Option<()> {
110 let strukt = sema.to_def(strukt)?;
111 let krate = strukt.module(sema.db).krate();
112
113 let deref_trait = FamousDefs(sema, Some(krate)).core_ops_Deref()?;
114 let strukt_type = strukt.ty(sema.db);
115
116 if strukt_type.impls_trait(sema.db, deref_trait, &[]) {
117 Some(())
118 } else {
119 None
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{check_assist, check_assist_not_applicable};
126
127 use super::*;
128
129 #[test]
130 fn test_generate_record_deref() {
131 check_assist(
132 generate_deref,
133 r#"struct A { }
134struct B { $0a: A }"#,
135 r#"struct A { }
136struct B { a: A }
137
138impl std::ops::Deref for B {
139 type Target = A;
140
141 fn deref(&self) -> &Self::Target {
142 &self.a
143 }
144}"#,
145 );
146 }
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
185 fn check_not_applicable(ra_fixture: &str) {
186 let fixture = format!(
187 "//- /main.rs crate:main deps:core,std\n{}\n{}",
188 ra_fixture,
189 FamousDefs::FIXTURE
190 );
191 check_assist_not_applicable(generate_deref, &fixture)
192 }
193
194 #[test]
195 fn test_generate_record_deref_not_applicable_if_already_impl() {
196 cov_mark::check!(test_add_record_deref_impl_already_exists);
197 check_not_applicable(
198 r#"struct A { }
199struct B { $0a: A }
200
201impl std::ops::Deref for B {
202 type Target = A;
203
204 fn deref(&self) -> &Self::Target {
205 &self.a
206 }
207}"#,
208 )
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 }
227}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 3694f468f..8996c1b61 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -134,6 +134,7 @@ mod handlers {
134 mod generate_default_from_enum_variant; 134 mod generate_default_from_enum_variant;
135 mod generate_default_from_new; 135 mod generate_default_from_new;
136 mod generate_is_empty_from_len; 136 mod generate_is_empty_from_len;
137 mod generate_deref;
137 mod generate_derive; 138 mod generate_derive;
138 mod generate_enum_is_method; 139 mod generate_enum_is_method;
139 mod generate_enum_projection_method; 140 mod generate_enum_projection_method;
@@ -201,6 +202,7 @@ mod handlers {
201 generate_default_from_enum_variant::generate_default_from_enum_variant, 202 generate_default_from_enum_variant::generate_default_from_enum_variant,
202 generate_default_from_new::generate_default_from_new, 203 generate_default_from_new::generate_default_from_new,
203 generate_is_empty_from_len::generate_is_empty_from_len, 204 generate_is_empty_from_len::generate_is_empty_from_len,
205 generate_deref::generate_deref,
204 generate_derive::generate_derive, 206 generate_derive::generate_derive,
205 generate_enum_is_method::generate_enum_is_method, 207 generate_enum_is_method::generate_enum_is_method,
206 generate_enum_projection_method::generate_enum_as_method, 208 generate_enum_projection_method::generate_enum_as_method,
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index 60e3a1474..49533e7d2 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -193,6 +193,7 @@ fn assist_order_field_struct() {
193 let mut assists = assists.iter(); 193 let mut assists = assists.iter();
194 194
195 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); 195 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
196 assert_eq!(assists.next().expect("expected assist").label, "Generate `Deref` impl using `bar`");
196 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); 197 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
197 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); 198 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
198 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); 199 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 27a22ca10..41559b43a 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -552,6 +552,33 @@ impl Default for Example {
552} 552}
553 553
554#[test] 554#[test]
555fn doctest_generate_deref() {
556 check_doc_test(
557 "generate_deref",
558 r#####"
559struct A;
560struct B {
561 $0a: A
562}
563"#####,
564 r#####"
565struct A;
566struct B {
567 a: A
568}
569
570impl std::ops::Deref for B {
571 type Target = A;
572
573 fn deref(&self) -> &Self::Target {
574 &self.a
575 }
576}
577"#####,
578 )
579}
580
581#[test]
555fn doctest_generate_derive() { 582fn doctest_generate_derive() {
556 check_doc_test( 583 check_doc_test(
557 "generate_derive", 584 "generate_derive",
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index 66798ea3a..83a665b37 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -113,6 +113,10 @@ impl FamousDefs<'_, '_> {
113 self.find_module("core:iter") 113 self.find_module("core:iter")
114 } 114 }
115 115
116 pub fn core_ops_Deref(&self) -> Option<Trait> {
117 self.find_trait("core:ops:Deref")
118 }
119
116 fn find_trait(&self, path: &str) -> Option<Trait> { 120 fn find_trait(&self, path: &str) -> Option<Trait> {
117 match self.find_def(path)? { 121 match self.find_def(path)? {
118 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), 122 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
diff --git a/crates/ide_db/src/helpers/famous_defs_fixture.rs b/crates/ide_db/src/helpers/famous_defs_fixture.rs
index 4d79e064e..29ae12dcf 100644
--- a/crates/ide_db/src/helpers/famous_defs_fixture.rs
+++ b/crates/ide_db/src/helpers/famous_defs_fixture.rs
@@ -112,6 +112,12 @@ pub mod ops {
112 type Output; 112 type Output;
113 extern "rust-call" fn call_once(self, args: Args) -> Self::Output; 113 extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
114 } 114 }
115
116 #[lang = "deref"]
117 pub trait Deref {
118 type Target: ?Sized;
119 fn deref(&self) -> &Self::Target;
120 }
115} 121}
116 122
117pub mod option { 123pub mod option {
@@ -141,3 +147,5 @@ mod return_keyword {}
141 147
142/// Docs for prim_str 148/// Docs for prim_str
143mod prim_str {} 149mod prim_str {}
150
151pub use core::ops; \ No newline at end of file