blob: 543b7f7ab632989c156fc9370cf540e5d8df5fd9 [file] [log] [blame]
use syntax::{
ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, HasGenericParams},
ted,
};
use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
// Assist: introduce_named_generic
//
// Replaces `impl Trait` function argument with the named generic.
//
// ```
// fn foo(bar: $0impl Bar) {}
// ```
// ->
// ```
// fn foo<$0B: Bar>(bar: B) {}
// ```
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 target = fn_.syntax().text_range();
acc.add(
AssistId("introduce_named_generic", AssistKind::RefactorRewrite),
"Replace impl trait with generic",
target,
|edit| {
let impl_trait_type = edit.make_mut(impl_trait_type);
let fn_ = edit.make_mut(fn_);
let fn_generic_param_list = fn_.get_or_create_generic_param_list();
let type_param_name =
suggest_name::for_impl_trait_as_generic(&impl_trait_type, &fn_generic_param_list);
let type_param = make::type_param(make::name(&type_param_name), Some(type_bound_list))
.clone_for_update();
let new_ty = make::ty(&type_param_name).clone_for_update();
ted::replace(impl_trait_type.syntax(), new_ty.syntax());
fn_generic_param_list.add_generic_param(type_param.into());
if let Some(cap) = ctx.config.snippet_cap {
if let Some(generic_param) =
fn_.generic_param_list().and_then(|it| it.generic_params().last())
{
edit.add_tabstop_before(cap, generic_param);
}
}
},
)
}
#[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, $0B0: Bar>(bar: B0) {}"#,
);
}
#[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, $0B2: Bar>(bar: B2) {}"#,
);
}
#[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>) {}"#,
);
}
}