Skip to content

Commit 59f8827

Browse files
committed
Implement assist to replace named generic with impl
This adds a new assist named "replace named generic with impl" to move the generic param type from the generic param list into the function signature. ```rust fn new<T: ToString>(input: T) -> Self {} ``` becomes ```rust fn new(input: impl ToString) -> Self {} ``` The first step is to determine if the assist can be applied, there has to be a match between generic trait param & function paramter types. * replace function parameter type(s) with impl * add new `impl_trait_type` function to generate the new trait bounds with `impl` keyword for use in the function signature
1 parent 21e5dc2 commit 59f8827

File tree

4 files changed

+162
-0
lines changed

4 files changed

+162
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use syntax::{
2+
ast::{
3+
self,
4+
make::{self, impl_trait_type},
5+
HasGenericParams, HasName, HasTypeBounds,
6+
},
7+
ted, AstNode,
8+
};
9+
10+
use crate::{AssistContext, AssistId, AssistKind, Assists};
11+
12+
// Assist: replace_named_generic_with_impl
13+
//
14+
// Replaces named generic with an `impl Trait` in function argument.
15+
//
16+
// ```
17+
// fn new<P$0: AsRef<Path>>(location: P) -> Self {}
18+
// ```
19+
// ->
20+
// ```
21+
// fn new(location: impl AsRef<Path>) -> Self {}
22+
// ```
23+
pub(crate) fn replace_named_generic_with_impl(
24+
acc: &mut Assists,
25+
ctx: &AssistContext<'_>,
26+
) -> Option<()> {
27+
// finds `<P: AsRef<Path>>`
28+
let type_param = ctx.find_node_at_offset::<ast::TypeParam>()?;
29+
30+
// The list of type bounds / traits for generic name `P`
31+
let type_bound_list = type_param.type_bound_list()?;
32+
33+
// returns `P`
34+
let type_param_name = type_param.name()?;
35+
36+
let fn_ = type_param.syntax().ancestors().find_map(ast::Fn::cast)?;
37+
let params = fn_
38+
.param_list()?
39+
.params()
40+
.filter_map(|param| {
41+
// function parameter type needs to match generic type name
42+
if let ast::Type::PathType(path_type) = param.ty()? {
43+
let left = path_type.path()?.segment()?.name_ref()?.ident_token()?.to_string();
44+
let right = type_param_name.to_string();
45+
if left == right {
46+
Some(param)
47+
} else {
48+
None
49+
}
50+
} else {
51+
None
52+
}
53+
})
54+
.collect::<Vec<_>>();
55+
56+
if params.is_empty() {
57+
return None;
58+
}
59+
60+
let target = type_param.syntax().text_range();
61+
62+
acc.add(
63+
AssistId("replace_named_generic_with_impl", AssistKind::RefactorRewrite),
64+
"Replace named generic with impl",
65+
target,
66+
|edit| {
67+
let type_param = edit.make_mut(type_param);
68+
let fn_ = edit.make_mut(fn_);
69+
70+
// Replace generic type in `<P: AsRef<Path>>` to `<P>`
71+
let new_ty = make::ty(&type_param_name.to_string()).clone_for_update();
72+
ted::replace(type_param.syntax(), new_ty.syntax());
73+
74+
if let Some(generic_params) = fn_.generic_param_list() {
75+
if generic_params.generic_params().count() == 0 {
76+
ted::remove(generic_params.syntax());
77+
}
78+
}
79+
80+
// Replace generic type parameter: `foo(p: P)` -> `foo(p: impl AsRef<Path>)`
81+
let new_bounds = impl_trait_type(type_bound_list).clone_for_update();
82+
83+
for param in params {
84+
if let Some(ast::Type::PathType(param_type)) = param.ty() {
85+
let param_type = edit.make_mut(param_type).clone_for_update();
86+
ted::replace(param_type.syntax(), new_bounds.syntax());
87+
}
88+
}
89+
},
90+
)
91+
}
92+
93+
#[cfg(test)]
94+
mod tests {
95+
use super::*;
96+
97+
use crate::tests::check_assist;
98+
99+
#[test]
100+
fn replace_generic_moves_into_function() {
101+
check_assist(
102+
replace_named_generic_with_impl,
103+
r#"fn new<T$0: ToString>(input: T) -> Self {}"#,
104+
r#"fn new(input: impl ToString) -> Self {}"#,
105+
);
106+
}
107+
108+
#[test]
109+
fn replace_generic_with_inner_associated_type() {
110+
check_assist(
111+
replace_named_generic_with_impl,
112+
r#"fn new<P$0: AsRef<Path>>(input: P) -> Self {}"#,
113+
r#"fn new(input: impl AsRef<Path>) -> Self {}"#,
114+
);
115+
}
116+
117+
#[test]
118+
fn replace_generic_trait_applies_to_all_matching_params() {
119+
check_assist(
120+
replace_named_generic_with_impl,
121+
r#"fn new<T$0: ToString>(a: T, b: T) -> Self {}"#,
122+
r#"fn new(a: impl ToString, b: impl ToString) -> Self {}"#,
123+
);
124+
}
125+
126+
#[test]
127+
fn replace_generic_with_multiple_generic_names() {
128+
check_assist(
129+
replace_named_generic_with_impl,
130+
r#"fn new<P: AsRef<Path>, T$0: ToString>(t: T, p: P) -> Self {}"#,
131+
r#"fn new<P: AsRef<Path>>(t: impl ToString, p: P) -> Self {}"#,
132+
);
133+
}
134+
135+
#[test]
136+
fn replace_generic_with_multiple_trait_bounds() {
137+
check_assist(
138+
replace_named_generic_with_impl,
139+
r#"fn new<P$0: Send + Sync>(p: P) -> Self {}"#,
140+
r#"fn new(p: impl Send + Sync) -> Self {}"#,
141+
);
142+
}
143+
}

crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ mod handlers {
193193
mod replace_arith_op;
194194
mod introduce_named_generic;
195195
mod replace_let_with_if_let;
196+
mod replace_named_generic_with_impl;
196197
mod replace_qualified_name_with_use;
197198
mod replace_string_with_char;
198199
mod replace_turbofish_with_explicit_type;
@@ -299,6 +300,7 @@ mod handlers {
299300
replace_let_with_if_let::replace_let_with_if_let,
300301
replace_method_eager_lazy::replace_with_eager_method,
301302
replace_method_eager_lazy::replace_with_lazy_method,
303+
replace_named_generic_with_impl::replace_named_generic_with_impl,
302304
replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type,
303305
replace_qualified_name_with_use::replace_qualified_name_with_use,
304306
replace_arith_op::replace_arith_with_wrapping,

crates/ide-assists/src/tests/generated.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,6 +2338,19 @@ fn handle(action: Action) {
23382338
)
23392339
}
23402340

2341+
#[test]
2342+
fn doctest_replace_named_generic_with_impl() {
2343+
check_doc_test(
2344+
"replace_named_generic_with_impl",
2345+
r#####"
2346+
fn new<P$0: AsRef<Path>>(location: P) -> Self {}
2347+
"#####,
2348+
r#####"
2349+
fn new(location: impl AsRef<Path>) -> Self {}
2350+
"#####,
2351+
)
2352+
}
2353+
23412354
#[test]
23422355
fn doctest_replace_qualified_name_with_use() {
23432356
check_doc_test(

crates/syntax/src/ast/make.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ pub fn impl_trait(
232232
ast_from_text(&format!("impl{ty_params_str} {trait_} for {ty}{ty_genargs_str} {{}}"))
233233
}
234234

235+
pub fn impl_trait_type(bounds: ast::TypeBoundList) -> ast::ImplTraitType {
236+
ast_from_text(&format!("fn f(x: impl {bounds}) {{}}"))
237+
}
238+
235239
pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
236240
ast_from_text(&format!("type __ = {name_ref};"))
237241
}

0 commit comments

Comments
 (0)