Skip to content

Commit a624e2e

Browse files
committed
Adds impl Deref assist
1 parent 5b40342 commit a624e2e

File tree

6 files changed

+185
-0
lines changed

6 files changed

+185
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use ide_db::{helpers::FamousDefs, RootDatabase};
2+
use syntax::{
3+
ast::{self, NameOwner},
4+
AstNode,
5+
};
6+
7+
use crate::{
8+
assist_context::{AssistContext, Assists},
9+
utils::generate_trait_impl_text,
10+
AssistId, AssistKind,
11+
};
12+
13+
// Assist: generate_deref
14+
//
15+
// Generate `Deref` impl using the given struct field.
16+
//
17+
// ```
18+
// struct A;
19+
// struct B {
20+
// $0a: A
21+
// }
22+
// ```
23+
// ->
24+
// ```
25+
// struct A;
26+
// struct B {
27+
// a: A
28+
// }
29+
//
30+
// impl std::ops::Deref for B {
31+
// type Target = A;
32+
//
33+
// fn deref(&self) -> &Self::Target {
34+
// &self.a
35+
// }
36+
// }
37+
// ```
38+
pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39+
let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
40+
let field = ctx.find_node_at_offset::<ast::RecordField>()?;
41+
42+
if existing_deref_impl(&ctx.sema, &strukt).is_some() {
43+
cov_mark::hit!(test_add_deref_impl_already_exists);
44+
return None;
45+
}
46+
47+
let field_type = field.ty()?;
48+
let field_name = field.name()?;
49+
let target = field.syntax().text_range();
50+
acc.add(
51+
AssistId("generate_deref", AssistKind::Generate),
52+
format!("Generate `Deref` impl using `{}`", field_name),
53+
target,
54+
|edit| {
55+
let start_offset = strukt.syntax().text_range().end();
56+
let impl_code = format!(
57+
r#" type Target = {0};
58+
59+
fn deref(&self) -> &Self::Target {{
60+
&self.{1}
61+
}}"#,
62+
field_type.syntax(),
63+
field_name.syntax()
64+
);
65+
let strukt_adt = ast::Adt::Struct(strukt);
66+
// Q for reviewer: Is there a better way to specify the trait_text, e.g.
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+
}
75+
76+
fn existing_deref_impl(
77+
sema: &'_ hir::Semantics<'_, RootDatabase>,
78+
strukt: &ast::Struct,
79+
) -> Option<()> {
80+
let strukt = sema.to_def(strukt)?;
81+
let krate = strukt.module(sema.db).krate();
82+
83+
let deref_trait = FamousDefs(sema, Some(krate)).core_ops_Deref()?;
84+
let strukt_type = strukt.ty(sema.db);
85+
86+
if strukt_type.impls_trait(sema.db, deref_trait, &[]) {
87+
Some(())
88+
} else {
89+
None
90+
}
91+
}
92+
93+
#[cfg(test)]
94+
mod tests {
95+
use crate::tests::{check_assist, check_assist_not_applicable};
96+
97+
use super::*;
98+
99+
#[test]
100+
fn test_generate_deref() {
101+
check_assist(
102+
generate_deref,
103+
r#"struct A { }
104+
struct B { $0a: A }"#,
105+
r#"struct A { }
106+
struct B { a: A }
107+
108+
impl std::ops::Deref for B {
109+
type Target = A;
110+
111+
fn deref(&self) -> &Self::Target {
112+
&self.a
113+
}
114+
}"#,
115+
);
116+
}
117+
118+
fn check_not_applicable(ra_fixture: &str) {
119+
let fixture = format!(
120+
"//- /main.rs crate:main deps:core,std\n{}\n{}",
121+
ra_fixture,
122+
FamousDefs::FIXTURE
123+
);
124+
check_assist_not_applicable(generate_deref, &fixture)
125+
}
126+
127+
#[test]
128+
fn test_generate_deref_not_applicable_if_already_impl() {
129+
cov_mark::check!(test_add_deref_impl_already_exists);
130+
check_not_applicable(
131+
r#"struct A { }
132+
struct B { $0a: A }
133+
134+
impl std::ops::Deref for B {
135+
type Target = A;
136+
137+
fn deref(&self) -> &Self::Target {
138+
&self.a
139+
}
140+
}"#,
141+
)
142+
}
143+
}

crates/ide_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ mod handlers {
132132
mod generate_default_from_enum_variant;
133133
mod generate_default_from_new;
134134
mod generate_is_empty_from_len;
135+
mod generate_deref;
135136
mod generate_derive;
136137
mod generate_enum_is_method;
137138
mod generate_enum_projection_method;
@@ -199,6 +200,7 @@ mod handlers {
199200
generate_default_from_enum_variant::generate_default_from_enum_variant,
200201
generate_default_from_new::generate_default_from_new,
201202
generate_is_empty_from_len::generate_is_empty_from_len,
203+
generate_deref::generate_deref,
202204
generate_derive::generate_derive,
203205
generate_enum_is_method::generate_enum_is_method,
204206
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
@@ -191,6 +191,7 @@ fn assist_order_field_struct() {
191191
let mut assists = assists.iter();
192192

193193
assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
194+
assert_eq!(assists.next().expect("expected assist").label, "Generate `Deref` impl using `bar`");
194195
assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
195196
assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
196197
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)