aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
authorjake <[email protected]>2021-04-11 08:31:20 +0100
committerjake <[email protected]>2021-04-11 08:42:05 +0100
commita624e2ea8d6fc97df03f663581e082541fd0348f (patch)
tree452586c51fda1939f6af09fa5b2bd71889333511 /crates/ide_assists
parent5b40342d2d5bc19445e6abccef6931bdd3a03c3b (diff)
Adds impl Deref assist
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/handlers/generate_deref.rs143
-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
4 files changed, 173 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..a8126e414
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_deref.rs
@@ -0,0 +1,143 @@
1use ide_db::{helpers::FamousDefs, RootDatabase};
2use syntax::{
3 ast::{self, NameOwner},
4 AstNode,
5};
6
7use 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// ```
38pub(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
76fn 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)]
94mod 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 { }
104struct B { $0a: A }"#,
105 r#"struct A { }
106struct B { a: A }
107
108impl 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 { }
132struct B { $0a: A }
133
134impl std::ops::Deref for B {
135 type Target = A;
136
137 fn deref(&self) -> &Self::Target {
138 &self.a
139 }
140}"#,
141 )
142 }
143}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 3e2c82dac..d6a083e1a 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -132,6 +132,7 @@ mod handlers {
132 mod generate_default_from_enum_variant; 132 mod generate_default_from_enum_variant;
133 mod generate_default_from_new; 133 mod generate_default_from_new;
134 mod generate_is_empty_from_len; 134 mod generate_is_empty_from_len;
135 mod generate_deref;
135 mod generate_derive; 136 mod generate_derive;
136 mod generate_enum_is_method; 137 mod generate_enum_is_method;
137 mod generate_enum_projection_method; 138 mod generate_enum_projection_method;
@@ -199,6 +200,7 @@ mod handlers {
199 generate_default_from_enum_variant::generate_default_from_enum_variant, 200 generate_default_from_enum_variant::generate_default_from_enum_variant,
200 generate_default_from_new::generate_default_from_new, 201 generate_default_from_new::generate_default_from_new,
201 generate_is_empty_from_len::generate_is_empty_from_len, 202 generate_is_empty_from_len::generate_is_empty_from_len,
203 generate_deref::generate_deref,
202 generate_derive::generate_derive, 204 generate_derive::generate_derive,
203 generate_enum_is_method::generate_enum_is_method, 205 generate_enum_is_method::generate_enum_is_method,
204 generate_enum_projection_method::generate_enum_as_method, 206 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 a7a923beb..6f25d3227 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -191,6 +191,7 @@ fn assist_order_field_struct() {
191 let mut assists = assists.iter(); 191 let mut assists = assists.iter();
192 192
193 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); 193 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`");
194 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); 195 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
195 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); 196 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
196 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); 197 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",