Skip to content

Commit 3f43273

Browse files
bors[bot]jhgg
andauthored
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 <jh@discordapp.com>
2 parents 7570212 + 3d1ca78 commit 3f43273

File tree

6 files changed

+269
-0
lines changed

6 files changed

+269
-0
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
use std::fmt::Display;
2+
3+
use ide_db::{helpers::FamousDefs, RootDatabase};
4+
use syntax::{
5+
ast::{self, NameOwner},
6+
AstNode, SyntaxNode,
7+
};
8+
9+
use 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+
// ```
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<()> {
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+
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};
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+
106+
fn 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)]
124+
mod 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 { }
134+
struct B { $0a: A }"#,
135+
r#"struct A { }
136+
struct B { a: A }
137+
138+
impl 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 { }
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+
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 { }
199+
struct B { $0a: A }
200+
201+
impl 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 { }
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+
}
227+
}

crates/ide_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ mod handlers {
134134
mod generate_default_from_enum_variant;
135135
mod generate_default_from_new;
136136
mod generate_is_empty_from_len;
137+
mod generate_deref;
137138
mod generate_derive;
138139
mod generate_enum_is_method;
139140
mod generate_enum_projection_method;
@@ -201,6 +202,7 @@ mod handlers {
201202
generate_default_from_enum_variant::generate_default_from_enum_variant,
202203
generate_default_from_new::generate_default_from_new,
203204
generate_is_empty_from_len::generate_is_empty_from_len,
205+
generate_deref::generate_deref,
204206
generate_derive::generate_derive,
205207
generate_enum_is_method::generate_enum_is_method,
206208
generate_enum_projection_method::generate_enum_as_method,

crates/ide_assists/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ fn assist_order_field_struct() {
193193
let mut assists = assists.iter();
194194

195195
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`");
196197
assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
197198
assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
198199
assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");

crates/ide_assists/src/tests/generated.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,33 @@ impl Default for Example {
551551
)
552552
}
553553

554+
#[test]
555+
fn doctest_generate_deref() {
556+
check_doc_test(
557+
"generate_deref",
558+
r#####"
559+
struct A;
560+
struct B {
561+
$0a: A
562+
}
563+
"#####,
564+
r#####"
565+
struct A;
566+
struct B {
567+
a: A
568+
}
569+
570+
impl 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+
554581
#[test]
555582
fn doctest_generate_derive() {
556583
check_doc_test(

crates/ide_db/src/helpers.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ impl FamousDefs<'_, '_> {
113113
self.find_module("core:iter")
114114
}
115115

116+
pub fn core_ops_Deref(&self) -> Option<Trait> {
117+
self.find_trait("core:ops:Deref")
118+
}
119+
116120
fn find_trait(&self, path: &str) -> Option<Trait> {
117121
match self.find_def(path)? {
118122
hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),

crates/ide_db/src/helpers/famous_defs_fixture.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ pub mod ops {
112112
type Output;
113113
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
114114
}
115+
116+
#[lang = "deref"]
117+
pub trait Deref {
118+
type Target: ?Sized;
119+
fn deref(&self) -> &Self::Target;
120+
}
115121
}
116122

117123
pub mod option {
@@ -141,3 +147,5 @@ mod return_keyword {}
141147

142148
/// Docs for prim_str
143149
mod prim_str {}
150+
151+
pub use core::ops;

0 commit comments

Comments
 (0)