Skip to main content

ide_assists/handlers/
generate_deref.rs

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