mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 14:03:35 +00:00
Add assist to generate a type alias for a function
This commit is contained in:
parent
f9935be013
commit
7b72a82bcb
3 changed files with 474 additions and 0 deletions
442
crates/ide-assists/src/handlers/generate_fn_type_alias.rs
Normal file
442
crates/ide-assists/src/handlers/generate_fn_type_alias.rs
Normal file
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -1548,6 +1548,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(
|
||||
|
|
Loading…
Reference in a new issue