ide_assists/handlers/
introduce_named_generic.rsuse ide_db::syntax_helpers::suggest_name;
use itertools::Itertools;
use syntax::ast::{self, syntax_factory::SyntaxFactory, AstNode, HasGenericParams, HasName};
use crate::{AssistContext, AssistId, AssistKind, Assists};
pub(crate) fn introduce_named_generic(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let impl_trait_type = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
let param = impl_trait_type.syntax().ancestors().find_map(ast::Param::cast)?;
let fn_ = param.syntax().ancestors().find_map(ast::Fn::cast)?;
let type_bound_list = impl_trait_type.type_bound_list()?;
let make = SyntaxFactory::new();
let target = fn_.syntax().text_range();
acc.add(
AssistId("introduce_named_generic", AssistKind::RefactorRewrite),
"Replace impl trait with generic",
target,
|builder| {
let mut editor = builder.make_editor(fn_.syntax());
let existing_names = match fn_.generic_param_list() {
Some(generic_param_list) => generic_param_list
.generic_params()
.flat_map(|param| match param {
ast::GenericParam::TypeParam(t) => t.name().map(|name| name.to_string()),
p => Some(p.to_string()),
})
.collect_vec(),
None => Vec::new(),
};
let type_param_name = suggest_name::NameGenerator::new_with_names(
existing_names.iter().map(|s| s.as_str()),
)
.for_impl_trait_as_generic(&impl_trait_type);
let type_param = make.type_param(make.name(&type_param_name), Some(type_bound_list));
let new_ty = make.ty(&type_param_name);
editor.replace(impl_trait_type.syntax(), new_ty.syntax());
editor.add_generic_param(&fn_, type_param.clone().into());
if let Some(cap) = ctx.config.snippet_cap {
editor.add_annotation(type_param.syntax(), builder.make_tabstop_before(cap));
}
editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.file_id(), editor);
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::check_assist;
#[test]
fn introduce_named_generic_params() {
check_assist(
introduce_named_generic,
r#"fn foo<G>(bar: $0impl Bar) {}"#,
r#"fn foo<G, $0B: Bar>(bar: B) {}"#,
);
}
#[test]
fn replace_impl_trait_without_generic_params() {
check_assist(
introduce_named_generic,
r#"fn foo(bar: $0impl Bar) {}"#,
r#"fn foo<$0B: Bar>(bar: B) {}"#,
);
}
#[test]
fn replace_two_impl_trait_with_generic_params() {
check_assist(
introduce_named_generic,
r#"fn foo<G>(foo: impl Foo, bar: $0impl Bar) {}"#,
r#"fn foo<G, $0B: Bar>(foo: impl Foo, bar: B) {}"#,
);
}
#[test]
fn replace_impl_trait_with_empty_generic_params() {
check_assist(
introduce_named_generic,
r#"fn foo<>(bar: $0impl Bar) {}"#,
r#"fn foo<$0B: Bar>(bar: B) {}"#,
);
}
#[test]
fn replace_impl_trait_with_empty_multiline_generic_params() {
check_assist(
introduce_named_generic,
r#"
fn foo<
>(bar: $0impl Bar) {}
"#,
r#"
fn foo<$0B: Bar
>(bar: B) {}
"#,
);
}
#[test]
fn replace_impl_trait_with_exist_generic_letter() {
check_assist(
introduce_named_generic,
r#"fn foo<B>(bar: $0impl Bar) {}"#,
r#"fn foo<B, $0B1: Bar>(bar: B1) {}"#,
);
}
#[test]
fn replace_impl_trait_with_more_exist_generic_letter() {
check_assist(
introduce_named_generic,
r#"fn foo<B, B0, B1, B3>(bar: $0impl Bar) {}"#,
r#"fn foo<B, B0, B1, B3, $0B4: Bar>(bar: B4) {}"#,
);
}
#[test]
fn replace_impl_trait_with_multiline_generic_params() {
check_assist(
introduce_named_generic,
r#"
fn foo<
G: Foo,
F,
H,
>(bar: $0impl Bar) {}
"#,
r#"
fn foo<
G: Foo,
F,
H, $0B: Bar,
>(bar: B) {}
"#,
);
}
#[test]
fn replace_impl_trait_multiple() {
check_assist(
introduce_named_generic,
r#"fn foo(bar: $0impl Foo + Bar) {}"#,
r#"fn foo<$0F: Foo + Bar>(bar: F) {}"#,
);
}
#[test]
fn replace_impl_with_mut() {
check_assist(
introduce_named_generic,
r#"fn f(iter: &mut $0impl Iterator<Item = i32>) {}"#,
r#"fn f<$0I: Iterator<Item = i32>>(iter: &mut I) {}"#,
);
}
#[test]
fn replace_impl_inside() {
check_assist(
introduce_named_generic,
r#"fn f(x: &mut Vec<$0impl Iterator<Item = i32>>) {}"#,
r#"fn f<$0I: Iterator<Item = i32>>(x: &mut Vec<I>) {}"#,
);
}
}