ide_assists/handlers/
generate_deref.rs

1use std::fmt::Display;
2
3use hir::{ModPath, ModuleDef};
4use ide_db::{RootDatabase, famous_defs::FamousDefs};
5use syntax::{
6    AstNode, Edition, SyntaxNode,
7    ast::{self, HasName},
8};
9
10use crate::{
11    AssistId,
12    assist_context::{AssistContext, Assists, SourceChangeBuilder},
13    utils::generate_trait_impl_text_intransitive,
14};
15
16// Assist: generate_deref
17//
18// Generate `Deref` impl using the given struct field.
19//
20// ```
21// # //- minicore: deref, deref_mut
22// struct A;
23// struct B {
24//    $0a: A
25// }
26// ```
27// ->
28// ```
29// struct A;
30// struct B {
31//    a: A
32// }
33//
34// impl core::ops::Deref for B {
35//     type Target = A;
36//
37//     fn deref(&self) -> &Self::Target {
38//         &self.a
39//     }
40// }
41// ```
42pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
43    generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx))
44}
45
46fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
47    let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
48    let field = ctx.find_node_at_offset::<ast::RecordField>()?;
49
50    let deref_type_to_generate = match existing_deref_impl(&ctx.sema, &strukt) {
51        None => DerefType::Deref,
52        Some(DerefType::Deref) => DerefType::DerefMut,
53        Some(DerefType::DerefMut) => {
54            cov_mark::hit!(test_add_record_deref_impl_already_exists);
55            return None;
56        }
57    };
58
59    let module = ctx.sema.to_def(&strukt)?.module(ctx.db());
60    let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.db())));
61    let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate(ctx.db()))?;
62    let trait_path = module.find_path(ctx.db(), ModuleDef::Trait(trait_), cfg)?;
63
64    let field_type = field.ty()?;
65    let field_name = field.name()?;
66    let target = field.syntax().text_range();
67    acc.add(
68        AssistId::generate("generate_deref"),
69        format!("Generate `{deref_type_to_generate:?}` impl using `{field_name}`"),
70        target,
71        |edit| {
72            generate_edit(
73                ctx.db(),
74                edit,
75                strukt,
76                field_type.syntax(),
77                field_name.syntax(),
78                deref_type_to_generate,
79                trait_path,
80                module.krate(ctx.db()).edition(ctx.db()),
81            )
82        },
83    )
84}
85
86fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
87    let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
88    let field = ctx.find_node_at_offset::<ast::TupleField>()?;
89    let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
90    let field_list_index = field_list.syntax().children().position(|s| &s == field.syntax())?;
91
92    let deref_type_to_generate = match existing_deref_impl(&ctx.sema, &strukt) {
93        None => DerefType::Deref,
94        Some(DerefType::Deref) => DerefType::DerefMut,
95        Some(DerefType::DerefMut) => {
96            cov_mark::hit!(test_add_field_deref_impl_already_exists);
97            return None;
98        }
99    };
100
101    let module = ctx.sema.to_def(&strukt)?.module(ctx.db());
102    let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db)));
103    let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate(ctx.db()))?;
104    let trait_path = module.find_path(ctx.db(), ModuleDef::Trait(trait_), cfg)?;
105
106    let field_type = field.ty()?;
107    let target = field.syntax().text_range();
108    acc.add(
109        AssistId::generate("generate_deref"),
110        format!("Generate `{deref_type_to_generate:?}` impl using `{field}`"),
111        target,
112        |edit| {
113            generate_edit(
114                ctx.db(),
115                edit,
116                strukt,
117                field_type.syntax(),
118                field_list_index,
119                deref_type_to_generate,
120                trait_path,
121                module.krate(ctx.db()).edition(ctx.db()),
122            )
123        },
124    )
125}
126
127fn generate_edit(
128    db: &RootDatabase,
129    edit: &mut SourceChangeBuilder,
130    strukt: ast::Struct,
131    field_type_syntax: &SyntaxNode,
132    field_name: impl Display,
133    deref_type: DerefType,
134    trait_path: ModPath,
135    edition: Edition,
136) {
137    let start_offset = strukt.syntax().text_range().end();
138    let impl_code = match deref_type {
139        DerefType::Deref => format!(
140            r#"    type Target = {field_type_syntax};
141
142    fn deref(&self) -> &Self::Target {{
143        &self.{field_name}
144    }}"#,
145        ),
146        DerefType::DerefMut => format!(
147            r#"    fn deref_mut(&mut self) -> &mut Self::Target {{
148        &mut self.{field_name}
149    }}"#,
150        ),
151    };
152    let strukt_adt = ast::Adt::Struct(strukt);
153    let deref_impl = generate_trait_impl_text_intransitive(
154        &strukt_adt,
155        &trait_path.display(db, edition).to_string(),
156        &impl_code,
157    );
158    edit.insert(start_offset, deref_impl);
159}
160
161fn existing_deref_impl(
162    sema: &hir::Semantics<'_, RootDatabase>,
163    strukt: &ast::Struct,
164) -> Option<DerefType> {
165    let strukt = sema.to_def(strukt)?;
166    let krate = strukt.module(sema.db).krate(sema.db);
167
168    let deref_trait = FamousDefs(sema, krate).core_ops_Deref()?;
169    let deref_mut_trait = FamousDefs(sema, krate).core_ops_DerefMut()?;
170    let strukt_type = strukt.ty(sema.db);
171
172    if strukt_type.impls_trait(sema.db, deref_trait, &[]) {
173        if strukt_type.impls_trait(sema.db, deref_mut_trait, &[]) {
174            Some(DerefType::DerefMut)
175        } else {
176            Some(DerefType::Deref)
177        }
178    } else {
179        None
180    }
181}
182
183#[derive(Debug)]
184enum DerefType {
185    Deref,
186    DerefMut,
187}
188
189impl DerefType {
190    fn to_trait(
191        &self,
192        sema: &hir::Semantics<'_, RootDatabase>,
193        krate: hir::Crate,
194    ) -> Option<hir::Trait> {
195        match self {
196            DerefType::Deref => FamousDefs(sema, krate).core_ops_Deref(),
197            DerefType::DerefMut => FamousDefs(sema, krate).core_ops_DerefMut(),
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use crate::tests::{check_assist, check_assist_not_applicable};
205
206    use super::*;
207
208    #[test]
209    fn test_generate_record_deref() {
210        check_assist(
211            generate_deref,
212            r#"
213//- minicore: deref
214struct A { }
215struct B { $0a: A }"#,
216            r#"
217struct A { }
218struct B { a: A }
219
220impl core::ops::Deref for B {
221    type Target = A;
222
223    fn deref(&self) -> &Self::Target {
224        &self.a
225    }
226}"#,
227        );
228    }
229
230    #[test]
231    fn test_generate_record_deref_with_generic() {
232        check_assist(
233            generate_deref,
234            r#"
235//- minicore: deref
236struct A<T>($0T);
237"#,
238            r#"
239struct A<T>(T);
240
241impl<T> core::ops::Deref for A<T> {
242    type Target = T;
243
244    fn deref(&self) -> &Self::Target {
245        &self.0
246    }
247}
248"#,
249        );
250    }
251
252    #[test]
253    fn test_generate_record_deref_short_path() {
254        check_assist(
255            generate_deref,
256            r#"
257//- minicore: deref
258use core::ops::Deref;
259struct A { }
260struct B { $0a: A }"#,
261            r#"
262use core::ops::Deref;
263struct A { }
264struct B { a: A }
265
266impl Deref for B {
267    type Target = A;
268
269    fn deref(&self) -> &Self::Target {
270        &self.a
271    }
272}"#,
273        );
274    }
275
276    #[test]
277    fn test_generate_field_deref_idx_0() {
278        check_assist(
279            generate_deref,
280            r#"
281//- minicore: deref
282struct A { }
283struct B($0A);"#,
284            r#"
285struct A { }
286struct B(A);
287
288impl core::ops::Deref for B {
289    type Target = A;
290
291    fn deref(&self) -> &Self::Target {
292        &self.0
293    }
294}"#,
295        );
296    }
297    #[test]
298    fn test_generate_field_deref_idx_1() {
299        check_assist(
300            generate_deref,
301            r#"
302//- minicore: deref
303struct A { }
304struct B(u8, $0A);"#,
305            r#"
306struct A { }
307struct B(u8, A);
308
309impl core::ops::Deref for B {
310    type Target = A;
311
312    fn deref(&self) -> &Self::Target {
313        &self.1
314    }
315}"#,
316        );
317    }
318
319    #[test]
320    fn test_generates_derefmut_when_deref_present() {
321        check_assist(
322            generate_deref,
323            r#"
324//- minicore: deref, deref_mut
325struct B { $0a: u8 }
326
327impl core::ops::Deref for B {}
328"#,
329            r#"
330struct B { a: u8 }
331
332impl core::ops::DerefMut for B {
333    fn deref_mut(&mut self) -> &mut Self::Target {
334        &mut self.a
335    }
336}
337
338impl core::ops::Deref for B {}
339"#,
340        );
341    }
342
343    #[test]
344    fn test_generate_record_deref_not_applicable_if_already_impl() {
345        cov_mark::check!(test_add_record_deref_impl_already_exists);
346        check_assist_not_applicable(
347            generate_deref,
348            r#"
349//- minicore: deref, deref_mut
350struct A { }
351struct B { $0a: A }
352
353impl core::ops::Deref for B {}
354impl core::ops::DerefMut for B {}
355"#,
356        )
357    }
358
359    #[test]
360    fn test_generate_field_deref_not_applicable_if_already_impl() {
361        cov_mark::check!(test_add_field_deref_impl_already_exists);
362        check_assist_not_applicable(
363            generate_deref,
364            r#"
365//- minicore: deref, deref_mut
366struct A { }
367struct B($0A)
368
369impl core::ops::Deref for B {}
370impl core::ops::DerefMut for B {}
371"#,
372        )
373    }
374}