]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/generate_deref.rs
Support `if let` match guards
[rust.git] / crates / ide_assists / src / handlers / generate_deref.rs
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     #[test]
186     fn test_generate_record_deref_not_applicable_if_already_impl() {
187         cov_mark::check!(test_add_record_deref_impl_already_exists);
188         check_assist_not_applicable(
189             generate_deref,
190             r#"
191 //- minicore: deref
192 struct A { }
193 struct B { $0a: A }
194
195 impl core::ops::Deref for B {
196     type Target = A;
197
198     fn deref(&self) -> &Self::Target {
199         &self.a
200     }
201 }"#,
202         )
203     }
204
205     #[test]
206     fn test_generate_field_deref_not_applicable_if_already_impl() {
207         cov_mark::check!(test_add_field_deref_impl_already_exists);
208         check_assist_not_applicable(
209             generate_deref,
210             r#"
211 //- minicore: deref
212 struct A { }
213 struct B($0A)
214
215 impl core::ops::Deref for B {
216     type Target = A;
217
218     fn deref(&self) -> &Self::Target {
219         &self.0
220     }
221 }"#,
222         )
223     }
224 }