Skip to content

Commit 0275b08

Browse files
Merge #5940
5940: Implement "Replace `impl Trait` function argument with the named generic" assist. r=matklad a=alekseysidorov Fixes #5085 Co-authored-by: Aleksei Sidorov <[email protected]>
2 parents 9dfa69a + e1b8d83 commit 0275b08

File tree

5 files changed

+267
-1
lines changed

5 files changed

+267
-1
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner};
2+
3+
use crate::{AssistContext, AssistId, AssistKind, Assists};
4+
5+
// Assist: replace_impl_trait_with_generic
6+
//
7+
// Replaces `impl Trait` function argument with the named generic.
8+
//
9+
// ```
10+
// fn foo(bar: <|>impl Bar) {}
11+
// ```
12+
// ->
13+
// ```
14+
// fn foo<B: Bar>(bar: B) {}
15+
// ```
16+
pub(crate) fn replace_impl_trait_with_generic(
17+
acc: &mut Assists,
18+
ctx: &AssistContext,
19+
) -> Option<()> {
20+
let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
21+
let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?;
22+
let type_fn = type_param.syntax().ancestors().find_map(ast::Fn::cast)?;
23+
24+
let impl_trait_ty = type_impl_trait.type_bound_list()?;
25+
26+
let target = type_fn.syntax().text_range();
27+
acc.add(
28+
AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
29+
"Replace impl trait with generic",
30+
target,
31+
|edit| {
32+
let generic_letter = impl_trait_ty.to_string().chars().next().unwrap().to_string();
33+
34+
let generic_param_list = type_fn
35+
.generic_param_list()
36+
.unwrap_or_else(|| make::generic_param_list(None))
37+
.append_param(make::generic_param(generic_letter.clone(), Some(impl_trait_ty)));
38+
39+
let new_type_fn = type_fn
40+
.replace_descendant::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter))
41+
.with_generic_param_list(generic_param_list);
42+
43+
edit.replace_ast(type_fn.clone(), new_type_fn);
44+
},
45+
)
46+
}
47+
48+
#[cfg(test)]
49+
mod tests {
50+
use super::*;
51+
52+
use crate::tests::check_assist;
53+
54+
#[test]
55+
fn replace_impl_trait_with_generic_params() {
56+
check_assist(
57+
replace_impl_trait_with_generic,
58+
r#"
59+
fn foo<G>(bar: <|>impl Bar) {}
60+
"#,
61+
r#"
62+
fn foo<G, B: Bar>(bar: B) {}
63+
"#,
64+
);
65+
}
66+
67+
#[test]
68+
fn replace_impl_trait_without_generic_params() {
69+
check_assist(
70+
replace_impl_trait_with_generic,
71+
r#"
72+
fn foo(bar: <|>impl Bar) {}
73+
"#,
74+
r#"
75+
fn foo<B: Bar>(bar: B) {}
76+
"#,
77+
);
78+
}
79+
80+
#[test]
81+
fn replace_two_impl_trait_with_generic_params() {
82+
check_assist(
83+
replace_impl_trait_with_generic,
84+
r#"
85+
fn foo<G>(foo: impl Foo, bar: <|>impl Bar) {}
86+
"#,
87+
r#"
88+
fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}
89+
"#,
90+
);
91+
}
92+
93+
#[test]
94+
fn replace_impl_trait_with_empty_generic_params() {
95+
check_assist(
96+
replace_impl_trait_with_generic,
97+
r#"
98+
fn foo<>(bar: <|>impl Bar) {}
99+
"#,
100+
r#"
101+
fn foo<B: Bar>(bar: B) {}
102+
"#,
103+
);
104+
}
105+
106+
#[test]
107+
fn replace_impl_trait_with_empty_multiline_generic_params() {
108+
check_assist(
109+
replace_impl_trait_with_generic,
110+
r#"
111+
fn foo<
112+
>(bar: <|>impl Bar) {}
113+
"#,
114+
r#"
115+
fn foo<B: Bar
116+
>(bar: B) {}
117+
"#,
118+
);
119+
}
120+
121+
#[test]
122+
#[ignore = "This case is very rare but there is no simple solutions to fix it."]
123+
fn replace_impl_trait_with_exist_generic_letter() {
124+
check_assist(
125+
replace_impl_trait_with_generic,
126+
r#"
127+
fn foo<B>(bar: <|>impl Bar) {}
128+
"#,
129+
r#"
130+
fn foo<B, C: Bar>(bar: C) {}
131+
"#,
132+
);
133+
}
134+
135+
#[test]
136+
fn replace_impl_trait_with_multiline_generic_params() {
137+
check_assist(
138+
replace_impl_trait_with_generic,
139+
r#"
140+
fn foo<
141+
G: Foo,
142+
F,
143+
H,
144+
>(bar: <|>impl Bar) {}
145+
"#,
146+
r#"
147+
fn foo<
148+
G: Foo,
149+
F,
150+
H, B: Bar
151+
>(bar: B) {}
152+
"#,
153+
);
154+
}
155+
156+
#[test]
157+
fn replace_impl_trait_multiple() {
158+
check_assist(
159+
replace_impl_trait_with_generic,
160+
r#"
161+
fn foo(bar: <|>impl Foo + Bar) {}
162+
"#,
163+
r#"
164+
fn foo<F: Foo + Bar>(bar: F) {}
165+
"#,
166+
);
167+
}
168+
}

crates/assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ mod handlers {
155155
mod remove_unused_param;
156156
mod reorder_fields;
157157
mod replace_if_let_with_match;
158+
mod replace_impl_trait_with_generic;
158159
mod replace_let_with_if_let;
159160
mod replace_qualified_name_with_use;
160161
mod replace_unwrap_with_match;
@@ -202,6 +203,7 @@ mod handlers {
202203
remove_unused_param::remove_unused_param,
203204
reorder_fields::reorder_fields,
204205
replace_if_let_with_match::replace_if_let_with_match,
206+
replace_impl_trait_with_generic::replace_impl_trait_with_generic,
205207
replace_let_with_if_let::replace_let_with_if_let,
206208
replace_qualified_name_with_use::replace_qualified_name_with_use,
207209
replace_unwrap_with_match::replace_unwrap_with_match,

crates/assists/src/tests/generated.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,19 @@ fn handle(action: Action) {
814814
)
815815
}
816816

817+
#[test]
818+
fn doctest_replace_impl_trait_with_generic() {
819+
check_doc_test(
820+
"replace_impl_trait_with_generic",
821+
r#####"
822+
fn foo(bar: <|>impl Bar) {}
823+
"#####,
824+
r#####"
825+
fn foo<B: Bar>(bar: B) {}
826+
"#####,
827+
)
828+
}
829+
817830
#[test]
818831
fn doctest_replace_let_with_if_let() {
819832
check_doc_test(

crates/syntax/src/ast/edit.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
ast::{
1414
self,
1515
make::{self, tokens},
16-
AstNode, TypeBoundsOwner,
16+
AstNode, GenericParamsOwner, NameOwner, TypeBoundsOwner,
1717
},
1818
AstToken, Direction, InsertPosition, SmolStr, SyntaxElement, SyntaxKind,
1919
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
@@ -46,6 +46,19 @@ impl ast::Fn {
4646
to_insert.push(body.syntax().clone().into());
4747
self.replace_children(single_node(old_body_or_semi), to_insert)
4848
}
49+
50+
#[must_use]
51+
pub fn with_generic_param_list(&self, generic_args: ast::GenericParamList) -> ast::Fn {
52+
if let Some(old) = self.generic_param_list() {
53+
return self.replace_descendant(old, generic_args);
54+
}
55+
56+
let anchor = self.name().expect("The function must have a name").syntax().clone();
57+
58+
let mut to_insert: ArrayVec<[SyntaxElement; 1]> = ArrayVec::new();
59+
to_insert.push(generic_args.syntax().clone().into());
60+
self.insert_children(InsertPosition::After(anchor.into()), to_insert)
61+
}
4962
}
5063

5164
fn make_multiline<N>(node: N) -> N
@@ -459,6 +472,61 @@ impl ast::MatchArmList {
459472
}
460473
}
461474

475+
impl ast::GenericParamList {
476+
#[must_use]
477+
pub fn append_params(
478+
&self,
479+
params: impl IntoIterator<Item = ast::GenericParam>,
480+
) -> ast::GenericParamList {
481+
let mut res = self.clone();
482+
params.into_iter().for_each(|it| res = res.append_param(it));
483+
res
484+
}
485+
486+
#[must_use]
487+
pub fn append_param(&self, item: ast::GenericParam) -> ast::GenericParamList {
488+
let space = tokens::single_space();
489+
490+
let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
491+
if self.generic_params().next().is_some() {
492+
to_insert.push(space.into());
493+
}
494+
to_insert.push(item.syntax().clone().into());
495+
496+
macro_rules! after_l_angle {
497+
() => {{
498+
let anchor = match self.l_angle_token() {
499+
Some(it) => it.into(),
500+
None => return self.clone(),
501+
};
502+
InsertPosition::After(anchor)
503+
}};
504+
}
505+
506+
macro_rules! after_field {
507+
($anchor:expr) => {
508+
if let Some(comma) = $anchor
509+
.syntax()
510+
.siblings_with_tokens(Direction::Next)
511+
.find(|it| it.kind() == T![,])
512+
{
513+
InsertPosition::After(comma)
514+
} else {
515+
to_insert.insert(0, make::token(T![,]).into());
516+
InsertPosition::After($anchor.syntax().clone().into())
517+
}
518+
};
519+
};
520+
521+
let position = match self.generic_params().last() {
522+
Some(it) => after_field!(it),
523+
None => after_l_angle!(),
524+
};
525+
526+
self.insert_children(position, to_insert)
527+
}
528+
}
529+
462530
#[must_use]
463531
pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
464532
N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()

crates/syntax/src/ast/make.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,21 @@ pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList
294294
ast_from_text(&format!("fn f({}) {{ }}", args))
295295
}
296296

297+
pub fn generic_param(name: String, ty: Option<ast::TypeBoundList>) -> ast::GenericParam {
298+
let bound = match ty {
299+
Some(it) => format!(": {}", it),
300+
None => String::new(),
301+
};
302+
ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound))
303+
}
304+
305+
pub fn generic_param_list(
306+
pats: impl IntoIterator<Item = ast::GenericParam>,
307+
) -> ast::GenericParamList {
308+
let args = pats.into_iter().join(", ");
309+
ast_from_text(&format!("fn f<{}>() {{ }}", args))
310+
}
311+
297312
pub fn visibility_pub_crate() -> ast::Visibility {
298313
ast_from_text("pub(crate) struct S")
299314
}

0 commit comments

Comments
 (0)