Skip to content

Commit fe3170d

Browse files
Initial implementation of the #5085 issue
1 parent 74e7422 commit fe3170d

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
pub(crate) fn replace_impl_trait_with_generic(
9+
acc: &mut Assists,
10+
ctx: &AssistContext,
11+
) -> Option<()> {
12+
let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
13+
let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?;
14+
let type_fn = type_param.syntax().ancestors().nth(2).and_then(ast::Fn::cast)?;
15+
16+
let generic_param_list =
17+
type_fn.generic_param_list().unwrap_or_else(|| make::generic_param_list(None));
18+
19+
let impl_trait_ty = type_impl_trait
20+
.syntax()
21+
.descendants()
22+
.last()
23+
.and_then(ast::NameRef::cast)?
24+
.text()
25+
.to_string();
26+
27+
let target = type_fn.syntax().text_range();
28+
acc.add(
29+
AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
30+
"Replace impl trait with generic",
31+
target,
32+
|edit| {
33+
let generic_letter = impl_trait_ty[..1].to_string();
34+
edit.replace_ast::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter));
35+
36+
let new_params = generic_param_list
37+
.append_param(make::generic_param(generic_letter, Some(impl_trait_ty)));
38+
let new_type_fn = type_fn.replace_descendant(generic_param_list, new_params);
39+
edit.replace_ast(type_fn.clone(), new_type_fn);
40+
},
41+
)
42+
}
43+
44+
#[cfg(test)]
45+
mod tests {
46+
use super::*;
47+
48+
use crate::tests::check_assist;
49+
50+
#[test]
51+
fn replace_with_generic_params() {
52+
check_assist(
53+
replace_impl_trait_with_generic,
54+
r#"
55+
fn foo<G>(bar: <|>impl Bar) {}
56+
"#,
57+
r#"
58+
fn foo<G, B: Bar>(bar: B) {}
59+
"#,
60+
);
61+
}
62+
}

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/syntax/src/ast/edit.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,72 @@ impl ast::MatchArmList {
459459
}
460460
}
461461

462+
impl ast::GenericParamList {
463+
#[must_use]
464+
pub fn append_params(&self, params: impl IntoIterator<Item = ast::GenericParam>) -> Self {
465+
let mut res = self.clone();
466+
params.into_iter().for_each(|it| res = res.append_param(it));
467+
res
468+
}
469+
470+
#[must_use]
471+
pub fn append_param(&self, item: ast::GenericParam) -> Self {
472+
let is_multiline = self.syntax().text().contains_char('\n');
473+
let ws;
474+
let space = if is_multiline {
475+
ws = tokens::WsBuilder::new(&format!(
476+
"\n{} ",
477+
leading_indent(self.syntax()).unwrap_or_default()
478+
));
479+
ws.ws()
480+
} else {
481+
tokens::single_space()
482+
};
483+
484+
let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
485+
to_insert.push(space.into());
486+
to_insert.push(item.syntax().clone().into());
487+
to_insert.push(make::token(T![,]).into());
488+
489+
macro_rules! after_l_angle {
490+
() => {{
491+
let anchor = match self.l_angle_token() {
492+
Some(it) => it.into(),
493+
None => return self.clone(),
494+
};
495+
InsertPosition::After(anchor)
496+
}};
497+
}
498+
499+
macro_rules! after_field {
500+
($anchor:expr) => {
501+
if let Some(comma) = $anchor
502+
.syntax()
503+
.siblings_with_tokens(Direction::Next)
504+
.find(|it| it.kind() == T![,])
505+
{
506+
InsertPosition::After(comma)
507+
} else {
508+
to_insert.insert(0, make::token(T![,]).into());
509+
InsertPosition::After($anchor.syntax().clone().into())
510+
}
511+
};
512+
};
513+
514+
if !is_multiline {
515+
// don't insert comma before angle
516+
to_insert.pop();
517+
}
518+
519+
let position = match self.generic_params().last() {
520+
Some(it) => after_field!(it),
521+
None => after_l_angle!(),
522+
};
523+
524+
self.insert_children(position, to_insert)
525+
}
526+
}
527+
462528
#[must_use]
463529
pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
464530
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<String>) -> 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)