about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs87
1 files changed, 82 insertions, 5 deletions
diff --git a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
index f658ba768d8..4e7011b5ca3 100644
--- a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
@@ -1,3 +1,10 @@
+use hir::{Semantics, TypeParam};
+use ide_db::{
+    base_db::{FileId, FileRange},
+    defs::Definition,
+    search::SearchScope,
+    RootDatabase,
+};
 use syntax::{
     ast::{self, make::impl_trait_type, HasGenericParams, HasName, HasTypeBounds},
     ted, AstNode,
@@ -22,13 +29,12 @@ pub(crate) fn replace_named_generic_with_impl(
 ) -> Option<()> {
     // finds `<P: AsRef<Path>>`
     let type_param = ctx.find_node_at_offset::<ast::TypeParam>()?;
+    // returns `P`
+    let type_param_name = type_param.name()?;
 
     // The list of type bounds / traits: `AsRef<Path>`
     let type_bound_list = type_param.type_bound_list()?;
 
-    // returns `P`
-    let type_param_name = type_param.name()?;
-
     let fn_ = type_param.syntax().ancestors().find_map(ast::Fn::cast)?;
     let params = fn_
         .param_list()?
@@ -53,6 +59,11 @@ pub(crate) fn replace_named_generic_with_impl(
         return None;
     }
 
+    let type_param_hir_def = ctx.sema.to_def(&type_param)?;
+    if is_referenced_outside(ctx.db(), type_param_hir_def, &fn_, ctx.file_id()) {
+        return None;
+    }
+
     let target = type_param.syntax().text_range();
 
     acc.add(
@@ -88,11 +99,36 @@ pub(crate) fn replace_named_generic_with_impl(
     )
 }
 
+fn is_referenced_outside(
+    db: &RootDatabase,
+    type_param: TypeParam,
+    fn_: &ast::Fn,
+    file_id: FileId,
+) -> bool {
+    let semantics = Semantics::new(db);
+    let type_param_def = Definition::GenericParam(hir::GenericParam::TypeParam(type_param));
+
+    // limit search scope to function body & return type
+    let search_ranges = vec![
+        fn_.body().map(|body| body.syntax().text_range()),
+        fn_.ret_type().map(|ret_type| ret_type.syntax().text_range()),
+    ];
+
+    search_ranges.into_iter().filter_map(|search_range| search_range).any(|search_range| {
+        let file_range = FileRange { file_id, range: search_range };
+        !type_param_def
+            .usages(&semantics)
+            .in_scope(SearchScope::file_range(file_range))
+            .all()
+            .is_empty()
+    })
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
 
-    use crate::tests::check_assist;
+    use crate::tests::{check_assist, check_assist_not_applicable};
 
     #[test]
     fn replace_generic_moves_into_function() {
@@ -122,12 +158,22 @@ mod tests {
     }
 
     #[test]
-    fn replace_generic_with_multiple_generic_names() {
+    fn replace_generic_with_multiple_generic_params() {
         check_assist(
             replace_named_generic_with_impl,
             r#"fn new<P: AsRef<Path>, T$0: ToString>(t: T, p: P) -> Self {}"#,
             r#"fn new<P: AsRef<Path>>(t: impl ToString, p: P) -> Self {}"#,
         );
+        check_assist(
+            replace_named_generic_with_impl,
+            r#"fn new<T$0: ToString, P: AsRef<Path>>(t: T, p: P) -> Self {}"#,
+            r#"fn new<P: AsRef<Path>>(t: impl ToString, p: P) -> Self {}"#,
+        );
+        check_assist(
+            replace_named_generic_with_impl,
+            r#"fn new<A: Send, B$0: ToString, C: Debug>(a: A, b: B, c: C) -> Self {}"#,
+            r#"fn new<A: Send, C: Debug>(a: A, b: impl ToString, c: C) -> Self {}"#,
+        );
     }
 
     #[test]
@@ -138,4 +184,35 @@ mod tests {
             r#"fn new(p: impl Send + Sync) -> Self {}"#,
         );
     }
+
+    #[test]
+    fn replace_generic_not_applicable_if_param_used_as_return_type() {
+        check_assist_not_applicable(
+            replace_named_generic_with_impl,
+            r#"fn new<P$0: Send + Sync>(p: P) -> P {}"#,
+        );
+    }
+
+    #[test]
+    fn replace_generic_not_applicable_if_param_used_in_fn_body() {
+        check_assist_not_applicable(
+            replace_named_generic_with_impl,
+            r#"fn new<P$0: ToString>(p: P) { let x: &dyn P = &O; }"#,
+        );
+    }
+
+    #[test]
+    fn replace_generic_ignores_another_function_with_same_param_type() {
+        check_assist(
+            replace_named_generic_with_impl,
+            r#"
+            fn new<P$0: Send + Sync>(p: P) {}
+            fn hello<P: Debug>(p: P) { println!("{:?}", p); }
+            "#,
+            r#"
+            fn new(p: impl Send + Sync) {}
+            fn hello<P: Debug>(p: P) { println!("{:?}", p); }
+            "#,
+        );
+    }
 }