Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130<{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387

  // NOTE: --disable-extensions
  // Disable all installed extensions to increase performance of the debug instance
  // and prevent potential conflicts with other installed extensions.

  "version": "0.2.0",
  "configurations": [
    {
      // Used for testing the extension with the installed LSP server.
      "name": "Run Installed Extension",
      "type": "extensionHost",
      "request": "launch",
      "runtimeExecutable": "${execPath}",
      "args": [
        // "--user-data-dir=${workspaceFolder}/target/code",
        "--disable-extensions",
        "--extensionDevelopmentPath=${workspaceFolder}/editors/code"
      ],
      "outFiles": [
        "${workspaceFolder}/editors/code/out/**/*.js"
      ],
      "preLaunchTask": "Build Extension",
      "skipFiles": [
        "<node_internals>/**/*.js"
      ]
    },
    {
      // Used for testing the extension with a local build of the LSP server (in `target/debug`).
      "name": "Run Extension (Debug Build)",
      "type": "extensionHost",
      "request": "launch",
      "runtimeExecutable": "${execPath}",
      "args": [
        "--disable-extensions",
        "--extensionDevelopmentPath=${workspaceFolder}/editors/code"
      ],
      "outFiles": [
        "${workspaceFolder}/editors/code/out/**/*.js"
      ],
      "preLaunchTask": "Build Server and Extension",
      "skipFiles": [
        "<node_internals>/**/*.js"
      ],
      "env": {
        "__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/debug/rust-analyzer"
      }
    },
    {
      // Used for testing the extension with a local build of the LSP server (in `target/release`).
      "name": "Run Extension (Release Build)",
      "type": "extensionHost",
      "request": "launch",
      "runtimeExecutable": "${execPath}",
      "args": [
        "--disable-extensions",
        "--extensionDevelopmentPath=${workspaceFolder}/editors/code"
      ],
      "outFiles": [
        "${workspaceFolder}/editors/code/out/**/*.js"
      ],
      "preLaunchTask": "Build Server (Release) and Extension",
      "skipFiles": [
        "<node_internals>/**/*.js"
      ],
      "env": {
        "__RA_LSP_SERVER_DEBUG": "${workspaceFolder}/target/release/rust-analyzer"
      }
    },
    {
      // Used for testing the extension with a local build of the LSP server (in `target/release`)
      // with all other extensions loaded.
      "name": "Run With Extensions",
      "type": "extensionHost",
      "request": "launch",
      "runtimeExecutable": "${execPath}",
      "args": [
        "--disable-extension", "rust-lang.rust-analyzer",
        "--extensionDevelopmentPath=${workspaceFolder}/editors/code"
      ],
      "outFiles": [
        "${workspaceFolder}/editors/code/out/**/*.js"
      ],
      "preLaunchTask": "Build Server (Release) and Extension",
      "skipFiles": [
        "<node_internals>/**/*.js"
      ],
      "env": {
        "__RA_LSP_SERVER_DEBUG"247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292


>328 329 330 331 332 333 334
use hir::{self, HasCrate, HasSource, HasVisibility};
use syntax::ast::{self, make, AstNode, HasGenericParams, HasName, HasVisibility as _};

use crate::{
    utils::{convert_param_list_to_arg_list, find_struct_impl, render_snippet, Cursor},
    AssistContext, AssistId, AssistKind, Assists, GroupLabel,
};
use syntax::ast::edit::AstNodeEdit;

// Assist: generate_delegate_methods
//
// Generate delegate methods.
//
// ```
// struct Age(u8);
// impl Age {
//     fn age(&self) -> u8 {
//         self.0
//     }
// }
//
// struct Person {
//     ag$0e: Age,
// }
// ```
// ->
// ```
// struct Age(u8);
// impl Age {
//     fn age(&self) -> u8 {
//         self.0
//     }
// }
//
// struct Person {
//     age: Age,
// }
//
// impl Person {
//     $0fn age(&self) -> u8 {
//         self.age.age()
//     }
// }
// ```
pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
    let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
    let strukt_name = strukt.name()?;
    let current_module = ctx.sema.scope(strukt.syntax())?.module();

    let (field_name, field_ty, target) = match ctx.find_node_at_offset::<ast::RecordField>() {
        Some(field) => {
            let field_name = field.name()?;
            let field_ty = field.ty()?;
            (format!("{}", field_name), field_ty, field.syntax().text_range())
        }
        None => {
            let field = ctx.find_node_at_offset::<ast::TupleField>()?;
            let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
            let field_list_index = field_list.fields().position(|it| it == field)?;
            let field_ty = field.ty()?;
            (format!("{}", field_list_index), field_ty, field.syntax().text_range())
        }
    };

    let sema_field_ty = ctx.sema.resolve_type(&field_ty)?;
    let krate = sema_field_ty.krate(ctx.db());
    let mut methods = vec![];
    sema_field_ty.iterate_assoc_items(ctx.db(), krate, |item| {
        if let hir::AssocItem::Function(f) = item {
            if f.self_param(ctx.db()).is_some() && f.is_visible_from(ctx.db(), current_module) {
                methods.push(f)
            }
        }
        Option::<()>::None
    });

    for method in methods {
        let adt = ast::Adt::Struct(strukt.clone());
        let name = method.name(ctx.db()).to_string();
        let impl_def = find_struct_impl(ctx, &adt, &name).flatten();
        acc.add_group(
            &GroupLabel("Generate delegate methods…".to_owned()),
            AssistId("generate_delegate_methods", AssistKind::Generate),
            format!("Generate delegate for `{}.{}()`", field_name, method.name(ctx.db())),
            target,
            |builder| {
                // Create the function
                let method_source = match method.source(ctx.db()) {
                    Some(source) => source.value,
                    None => return,
                };
                let method_name = method.name(ctx.db());
                let vis = method_source.visibility();
                let name = make::name(&method.name(ctx.db()).to_string());
                let params =
                    method_source.param_list().unwrap_or_else(|| make::param_list(None, []));
                let type_params = method_source.generic_param_list();
                let arg_list = match method_source.param_list() {
                    Some(list) => convert_param_list_to_arg_list(list),
                    None => make::arg_list([]),
                };
                let tail_expr = make::expr_method_call(
                    make::ext::field_from_idents(["self", &field_name]).unwrap(), // This unwrap is ok because we have at least 1 arg in the list
                    make::name_ref(&method_name.to_string()),
                    arg_list,
                );
                let body = make::block_expr([], Some(tail_expr));
                let ret_type = method_source.ret_type();
                let is_async = method_source.async_token().is_some();
                let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async)
                    .indent(ast::edit::IndentLevel(1))
                    .clone_for_update();

                let cursor = Cursor::Before(f.syntax());

                // Create or update an impl block, attach the function to it,
                // then insert into our code.
                match impl_def {
                    Some(impl_def) => {
                        // Remember where in our source our `impl` block lives.
                        let impl_def = impl_def.clone_for_update();
                        let old_range = impl_def.syntax().text_range();

                        // Attach the function to the impl block
                        let assoc_items = impl_def.get_or_create_assoc_item_list();
                        assoc_items.add_item(f.clone().into());

                        // Update the impl block.
                        match ctx.config.snippet_cap {
                            Some(cap) => {
                                let snippet = render_snippet(cap, impl_def.syntax(), cursor);
                                builder.replace_snippet(cap, old_range, snippet);
                            }
                            None => {
                                builder.replace(old_range, impl_def.syntax().to_string());
                            }
                        }
                    }
                    None => {
                        // Attach the function to the impl block
                        let name = &strukt_name.to_string();
                        let params = strukt.generic_param_list();
                        let ty_params = params.clone();
                        let impl_def = make::impl_(make::ext::ident_path(name), params, ty_params)
                            .clone_for_update();
                        let assoc_items = impl_def.get_or_create_assoc_item_list();
                        assoc_items.add_item(f.clone().into());

                        // Insert the impl block.
                        match ctx.config.snippet_cap {
                            Some(cap) => {
                                let offset = strukt.syntax().text_range().end();
                                let snippet = render_snippet(cap, impl_def.syntax(), cursor);
                                let snippet = format!("\n\n{}", snippet);
                                builder.insert_snippet(cap, offset, snippet);
                            }
                            None => {
                                let offset = strukt.syntax().text_range().end();
                                let snippet = format!("\n\n{}", impl_def.syntax());
                                builder.insert(offset, snippet);
                            }
                        }
                    }
                }
            },
        )?;
    }
    Some(())
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn test_generate_delegate_create_impl_block() {
        check_assist(
            generate_delegate_methods,
            r#"
struct Age(u8);
impl Age {
    fn age(&self) -> u8 {
        self.0
    }
}

struct Person {
    ag$0e: Age,
}"#,
            r#"
struct Age(u8);
impl Age {
    fn age(&self) -> u8 {
        self.0
    }
}

struct Person {
    age: Age,
}

impl Person {
    $0fn age(&self) -> u8 {
        self.age.age()
    }
}"#,
        );
    }

    #[test]
    fn test_generate_delegate_update_impl_block() {
        check_assist(
            generate_delegate_methods,
            r#"
struct Age(u8);
impl Age {
    fn age(&self) -> u8 {
        self.0
    }
}

struct Person {
    ag$0e: Age,
}

impl Person {}"#,
            r#"
struct Age(u8);
impl Age {
    fn age(&self) -> u8 {
        self.0
    }
}

struct Person {
    age: Age,
}

impl Person {
    $0fn age(&self) -> u8 {
        self.age.age()
    }
}"#,
        );
    }

    #[test]
    fn test_generate_delegate_tuple_struct() {
        check_assist(
            generate_delegate_methods,
            r#"
struct Age(u8);
impl Age {
    fn age(&self) -> u8 {
        self.0
    }
}

struct Person(A$0ge);"#,
            r#"
struct Age(u8);
impl Age {
    fn age(&self) -> u8 {
        self.0
    }
}

struct Person(Age);

impl Person {
    $0fn age(&self) -> u8 {
        self.0.age()
    }
}"#,
        );
    }

    #[test]
    fn test_generate_delegate_enable_all_attributes() {
        check_assist(
            generate_delegate_methods,
            r#"
struct Age<T>(T);
impl<T> Age<T> {
    pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T {
        self.0
    }
}

struct Person<T> {
    ag$0e: Age<T>,
}"#,
            r#"
struct Age<T>(T);
impl<T> Age<T> {
    pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T {
        self.0
    }
}

struct Person<T> {
    age: Age<T>,
}

impl<T> Person<T> {
    $0pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T {
        self.age.age(ty, arg)
    }
}"#,
        );
    }

    #[test]
    fn test_generate_delegate_visibility() {
        check_assist_not_applicable(
            generate_delegate_methods,
            r#"
mod m {
    pub struct Age(u8);
    impl Age {
        fn age(&self) -> u8 {
            self.0
        }
    }
}

struct Person {
    ag$0e: m::Age,
}"#,
        )
    }
}