--- /dev/null
+use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: replace_impl_trait_with_generic
+//
+// Replaces `impl Trait` function argument with the named generic.
+pub(crate) fn replace_impl_trait_with_generic(
+ acc: &mut Assists,
+ ctx: &AssistContext,
+) -> Option<()> {
+ let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
+ let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?;
+ let type_fn = type_param.syntax().ancestors().nth(2).and_then(ast::Fn::cast)?;
+
+ let generic_param_list =
+ type_fn.generic_param_list().unwrap_or_else(|| make::generic_param_list(None));
+
+ let impl_trait_ty = type_impl_trait
+ .syntax()
+ .descendants()
+ .last()
+ .and_then(ast::NameRef::cast)?
+ .text()
+ .to_string();
+
+ let target = type_fn.syntax().text_range();
+ acc.add(
+ AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
+ "Replace impl trait with generic",
+ target,
+ |edit| {
+ let generic_letter = impl_trait_ty[..1].to_string();
+ edit.replace_ast::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter));
+
+ let new_params = generic_param_list
+ .append_param(make::generic_param(generic_letter, Some(impl_trait_ty)));
+ let new_type_fn = type_fn.replace_descendant(generic_param_list, new_params);
+ edit.replace_ast(type_fn.clone(), new_type_fn);
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::tests::check_assist;
+
+ #[test]
+ fn replace_with_generic_params() {
+ check_assist(
+ replace_impl_trait_with_generic,
+ r#"
+ fn foo<G>(bar: <|>impl Bar) {}
+ "#,
+ r#"
+ fn foo<G, B: Bar>(bar: B) {}
+ "#,
+ );
+ }
+}
mod remove_unused_param;
mod reorder_fields;
mod replace_if_let_with_match;
+ mod replace_impl_trait_with_generic;
mod replace_let_with_if_let;
mod replace_qualified_name_with_use;
mod replace_unwrap_with_match;
remove_unused_param::remove_unused_param,
reorder_fields::reorder_fields,
replace_if_let_with_match::replace_if_let_with_match,
+ replace_impl_trait_with_generic::replace_impl_trait_with_generic,
replace_let_with_if_let::replace_let_with_if_let,
replace_qualified_name_with_use::replace_qualified_name_with_use,
replace_unwrap_with_match::replace_unwrap_with_match,
}
}
+impl ast::GenericParamList {
+ #[must_use]
+ pub fn append_params(&self, params: impl IntoIterator<Item = ast::GenericParam>) -> Self {
+ let mut res = self.clone();
+ params.into_iter().for_each(|it| res = res.append_param(it));
+ res
+ }
+
+ #[must_use]
+ pub fn append_param(&self, item: ast::GenericParam) -> Self {
+ let is_multiline = self.syntax().text().contains_char('\n');
+ let ws;
+ let space = if is_multiline {
+ ws = tokens::WsBuilder::new(&format!(
+ "\n{} ",
+ leading_indent(self.syntax()).unwrap_or_default()
+ ));
+ ws.ws()
+ } else {
+ tokens::single_space()
+ };
+
+ let mut to_insert: ArrayVec<[SyntaxElement; 4]> = ArrayVec::new();
+ to_insert.push(space.into());
+ to_insert.push(item.syntax().clone().into());
+ to_insert.push(make::token(T![,]).into());
+
+ macro_rules! after_l_angle {
+ () => {{
+ let anchor = match self.l_angle_token() {
+ Some(it) => it.into(),
+ None => return self.clone(),
+ };
+ InsertPosition::After(anchor)
+ }};
+ }
+
+ macro_rules! after_field {
+ ($anchor:expr) => {
+ if let Some(comma) = $anchor
+ .syntax()
+ .siblings_with_tokens(Direction::Next)
+ .find(|it| it.kind() == T![,])
+ {
+ InsertPosition::After(comma)
+ } else {
+ to_insert.insert(0, make::token(T![,]).into());
+ InsertPosition::After($anchor.syntax().clone().into())
+ }
+ };
+ };
+
+ if !is_multiline {
+ // don't insert comma before angle
+ to_insert.pop();
+ }
+
+ let position = match self.generic_params().last() {
+ Some(it) => after_field!(it),
+ None => after_l_angle!(),
+ };
+
+ self.insert_children(position, to_insert)
+ }
+}
+
#[must_use]
pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()
ast_from_text(&format!("fn f({}) {{ }}", args))
}
+pub fn generic_param(name: String, ty: Option<String>) -> ast::GenericParam {
+ let bound = match ty {
+ Some(it) => format!(": {}", it),
+ None => String::new(),
+ };
+ ast_from_text(&format!("fn f<{}{}>() {{ }}", name, bound))
+}
+
+pub fn generic_param_list(
+ pats: impl IntoIterator<Item = ast::GenericParam>,
+) -> ast::GenericParamList {
+ let args = pats.into_iter().join(", ");
+ ast_from_text(&format!("fn f<{}>() {{ }}", args))
+}
+
pub fn visibility_pub_crate() -> ast::Visibility {
ast_from_text("pub(crate) struct S")
}