about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs442
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/lib.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs30
3 files changed, 474 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs
new file mode 100644
index 00000000000..597d94d3fc6
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_fn_type_alias.rs
@@ -0,0 +1,442 @@
+use either::Either;
+use hir::HirDisplay;
+use ide_db::assists::{AssistId, AssistKind, GroupLabel};
+use syntax::{
+    ast::{self, edit::IndentLevel, make, HasGenericParams, HasName},
+    syntax_editor, AstNode,
+};
+
+use crate::{AssistContext, Assists};
+
+// Assist: generate_fn_type_alias_named
+//
+// Generate a type alias for the function with named parameters.
+//
+// ```
+// unsafe fn fo$0o(n: i32) -> i32 { 42i32 }
+// ```
+// ->
+// ```
+// type ${0:FooFn} = unsafe fn(n: i32) -> i32;
+//
+// unsafe fn foo(n: i32) -> i32 { 42i32 }
+// ```
+
+// Assist: generate_fn_type_alias_unnamed
+//
+// Generate a type alias for the function with unnamed parameters.
+//
+// ```
+// unsafe fn fo$0o(n: i32) -> i32 { 42i32 }
+// ```
+// ->
+// ```
+// type ${0:FooFn} = unsafe fn(i32) -> i32;
+//
+// unsafe fn foo(n: i32) -> i32 { 42i32 }
+// ```
+
+pub(crate) fn generate_fn_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+    let name = ctx.find_node_at_offset::<ast::Name>()?;
+    let func = &name.syntax().parent()?;
+    let item = func.ancestors().find_map(ast::Item::cast)?;
+    let assoc_owner =
+        item.syntax().ancestors().nth(2).and_then(Either::<ast::Trait, ast::Impl>::cast);
+    let node = assoc_owner.as_ref().map_or_else(
+        || item.syntax(),
+        |impl_| impl_.as_ref().either(AstNode::syntax, AstNode::syntax),
+    );
+    let func_node = ast::Fn::cast(func.clone())?;
+    let param_list = func_node.param_list()?;
+
+    for style in ParamStyle::ALL {
+        let generic_params = func_node.generic_param_list();
+        let module = match ctx.sema.scope(node) {
+            Some(scope) => scope.module(),
+            None => continue,
+        };
+
+        acc.add_group(
+            &GroupLabel("Generate a type alias for function...".into()),
+            style.assist_id(),
+            style.label(),
+            func_node.syntax().text_range(),
+            |builder| {
+                let mut edit = builder.make_editor(func);
+
+                let alias_name = format!("{}Fn", stdx::to_camel_case(&name.to_string()));
+
+                let fn_abi = match func_node.abi() {
+                    Some(abi) => format!("{} ", abi),
+                    None => "".into(),
+                };
+
+                let fn_unsafe = if func_node.unsafe_token().is_some() { "unsafe " } else { "" };
+
+                let fn_qualifiers = format!("{fn_unsafe}{fn_abi}");
+
+                let fn_type = return_type(&func_node);
+
+                let mut fn_params_vec = Vec::new();
+
+                if let Some(self_param) = param_list.self_param() {
+                    if let Some(local) = ctx.sema.to_def(&self_param) {
+                        let ty = local.ty(ctx.db());
+                        if let Ok(s) = ty.display_source_code(ctx.db(), module.into(), false) {
+                            fn_params_vec.push(s)
+                        }
+                    }
+                }
+
+                match style {
+                    ParamStyle::Named => {
+                        fn_params_vec.extend(param_list.params().map(|p| p.to_string()))
+                    }
+                    ParamStyle::Unnamed => fn_params_vec.extend(
+                        param_list.params().filter_map(|p| p.ty()).map(|ty| ty.to_string()),
+                    ),
+                };
+
+                let fn_params = fn_params_vec.join(", ");
+
+                // FIXME: sometime in the far future when we have `make::ty_func`, we should use that
+                let ty = make::ty(&format!("{fn_qualifiers}fn({fn_params}){fn_type}"))
+                    .clone_for_update();
+
+                // Insert new alias
+                let ty_alias =
+                    make::ty_alias(&alias_name, generic_params, None, None, Some((ty, None)))
+                        .clone_for_update();
+
+                let indent = IndentLevel::from_node(node);
+                edit.insert_all(
+                    syntax_editor::Position::before(node),
+                    vec![
+                        ty_alias.syntax().clone().into(),
+                        make::tokens::whitespace(&format!("\n\n{indent}")).into(),
+                    ],
+                );
+
+                if let Some(cap) = ctx.config.snippet_cap {
+                    if let Some(name) = ty_alias.name() {
+                        edit.add_annotation(name.syntax(), builder.make_placeholder_snippet(cap));
+                    }
+                }
+
+                builder.add_file_edits(ctx.file_id(), edit);
+            },
+        );
+    }
+
+    Some(())
+}
+
+enum ParamStyle {
+    Named,
+    Unnamed,
+}
+
+impl ParamStyle {
+    const ALL: &'static [ParamStyle] = &[ParamStyle::Named, ParamStyle::Unnamed];
+
+    fn assist_id(&self) -> AssistId {
+        let s = match self {
+            ParamStyle::Named => "generate_fn_type_alias_named",
+            ParamStyle::Unnamed => "generate_fn_type_alias_unnamed",
+        };
+
+        AssistId(s, AssistKind::Generate)
+    }
+
+    fn label(&self) -> &'static str {
+        match self {
+            ParamStyle::Named => "Generate a type alias for function with named params",
+            ParamStyle::Unnamed => "Generate a type alias for function with unnamed params",
+        }
+    }
+}
+
+fn return_type(func: &ast::Fn) -> String {
+    func.ret_type()
+        .and_then(|ret_type| ret_type.ty())
+        .map_or("".into(), |ty| format!(" -> {} ", ty))
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::check_assist_by_label;
+
+    use super::*;
+
+    #[test]
+    fn generate_fn_alias_unnamed_simple() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = fn(u32) -> i32;
+
+fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Unnamed.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_unnamed_unsafe() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+unsafe fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = unsafe fn(u32) -> i32;
+
+unsafe fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Unnamed.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_unnamed_extern() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+extern fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = extern fn(u32) -> i32;
+
+extern fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Unnamed.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_type_unnamed_extern_abi() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+extern "FooABI" fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = extern "FooABI" fn(u32) -> i32;
+
+extern "FooABI" fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Unnamed.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_unnamed_unnamed_unsafe_extern_abi() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+unsafe extern "FooABI" fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = unsafe extern "FooABI" fn(u32) -> i32;
+
+unsafe extern "FooABI" fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Unnamed.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_unnamed_generics() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+fn fo$0o<A, B>(a: A, b: B) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn}<A, B> = fn(A, B) -> i32;
+
+fn foo<A, B>(a: A, b: B) -> i32 { return 42; }
+"#,
+            ParamStyle::Unnamed.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_unnamed_generics_bounds() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+fn fo$0o<A: Trait, B: Trait>(a: A, b: B) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn}<A: Trait, B: Trait> = fn(A, B) -> i32;
+
+fn foo<A: Trait, B: Trait>(a: A, b: B) -> i32 { return 42; }
+"#,
+            ParamStyle::Unnamed.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_unnamed_self() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+struct S;
+
+impl S {
+    fn fo$0o(&mut self, param: u32) -> i32 { return 42; }
+}
+"#,
+            r#"
+struct S;
+
+type ${0:FooFn} = fn(&mut S, u32) -> i32;
+
+impl S {
+    fn foo(&mut self, param: u32) -> i32 { return 42; }
+}
+"#,
+            ParamStyle::Unnamed.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_named_simple() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = fn(param: u32) -> i32;
+
+fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Named.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_named_unsafe() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+unsafe fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = unsafe fn(param: u32) -> i32;
+
+unsafe fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Named.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_named_extern() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+extern fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = extern fn(param: u32) -> i32;
+
+extern fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Named.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_type_named_extern_abi() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+extern "FooABI" fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = extern "FooABI" fn(param: u32) -> i32;
+
+extern "FooABI" fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Named.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_named_named_unsafe_extern_abi() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+unsafe extern "FooABI" fn fo$0o(param: u32) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn} = unsafe extern "FooABI" fn(param: u32) -> i32;
+
+unsafe extern "FooABI" fn foo(param: u32) -> i32 { return 42; }
+"#,
+            ParamStyle::Named.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_named_generics() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+fn fo$0o<A, B>(a: A, b: B) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn}<A, B> = fn(a: A, b: B) -> i32;
+
+fn foo<A, B>(a: A, b: B) -> i32 { return 42; }
+"#,
+            ParamStyle::Named.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_named_generics_bounds() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+fn fo$0o<A: Trait, B: Trait>(a: A, b: B) -> i32 { return 42; }
+"#,
+            r#"
+type ${0:FooFn}<A: Trait, B: Trait> = fn(a: A, b: B) -> i32;
+
+fn foo<A: Trait, B: Trait>(a: A, b: B) -> i32 { return 42; }
+"#,
+            ParamStyle::Named.label(),
+        );
+    }
+
+    #[test]
+    fn generate_fn_alias_named_self() {
+        check_assist_by_label(
+            generate_fn_type_alias,
+            r#"
+struct S;
+
+impl S {
+    fn fo$0o(&mut self, param: u32) -> i32 { return 42; }
+}
+"#,
+            r#"
+struct S;
+
+type ${0:FooFn} = fn(&mut S, param: u32) -> i32;
+
+impl S {
+    fn foo(&mut self, param: u32) -> i32 { return 42; }
+}
+"#,
+            ParamStyle::Named.label(),
+        );
+    }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
index 22620816d50..5944c0a2bbe 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
@@ -162,6 +162,7 @@ mod handlers {
     mod generate_enum_projection_method;
     mod generate_enum_variant;
     mod generate_from_impl_for_enum;
+    mod generate_fn_type_alias;
     mod generate_function;
     mod generate_getter_or_setter;
     mod generate_impl;
@@ -290,6 +291,7 @@ mod handlers {
             generate_enum_projection_method::generate_enum_try_into_method,
             generate_enum_variant::generate_enum_variant,
             generate_from_impl_for_enum::generate_from_impl_for_enum,
+            generate_fn_type_alias::generate_fn_type_alias,
             generate_function::generate_function,
             generate_impl::generate_impl,
             generate_impl::generate_trait_impl,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
index 933d45d7508..64b7ab1a123 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
@@ -1549,6 +1549,36 @@ fn main() {
 }
 
 #[test]
+fn doctest_generate_fn_type_alias_named() {
+    check_doc_test(
+        "generate_fn_type_alias_named",
+        r#####"
+unsafe fn fo$0o(n: i32) -> i32 { 42i32 }
+"#####,
+        r#####"
+type ${0:FooFn} = unsafe fn(n: i32) -> i32;
+
+unsafe fn foo(n: i32) -> i32 { 42i32 }
+"#####,
+    )
+}
+
+#[test]
+fn doctest_generate_fn_type_alias_unnamed() {
+    check_doc_test(
+        "generate_fn_type_alias_unnamed",
+        r#####"
+unsafe fn fo$0o(n: i32) -> i32 { 42i32 }
+"#####,
+        r#####"
+type ${0:FooFn} = unsafe fn(i32) -> i32;
+
+unsafe fn foo(n: i32) -> i32 { 42i32 }
+"#####,
+    )
+}
+
+#[test]
 fn doctest_generate_from_impl_for_enum() {
     check_doc_test(
         "generate_from_impl_for_enum",