about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTerry Sun <terrynsun@gmail.com>2021-10-24 13:34:02 -0700
committerTerry Sun <terrynsun@gmail.com>2021-10-24 17:38:45 -0700
commit324d7d33e829cf8fd73ce27aebe48f64876fadda (patch)
tree5f3b3381e39a05ac12f2cb9f02e16c76eecb4a4f
parent1f47693e02809c97db61b51247ae4e4d46744c61 (diff)
downloadrust-324d7d33e829cf8fd73ce27aebe48f64876fadda.tar.gz
rust-324d7d33e829cf8fd73ce27aebe48f64876fadda.zip
Add assist for replacing turbofish with explicit type.
Converts `::<_>` to an explicit type assignment.

```
let args = args.collect::<Vec<String>>();
```
->
```
let args: Vec<String> = args.collect();
```

Closes #10285
-rw-r--r--crates/ide_assists/src/handlers/replace_turbofish_with_explicit_type.rs184
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests/generated.rs19
3 files changed, 205 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/replace_turbofish_with_explicit_type.rs b/crates/ide_assists/src/handlers/replace_turbofish_with_explicit_type.rs
new file mode 100644
index 00000000000..b02596daede
--- /dev/null
+++ b/crates/ide_assists/src/handlers/replace_turbofish_with_explicit_type.rs
@@ -0,0 +1,184 @@
+use syntax::{
+    ast::Expr,
+    ast::{LetStmt, Type::InferType},
+    AstNode, TextRange,
+};
+
+use crate::{
+    assist_context::{AssistContext, Assists},
+    AssistId, AssistKind,
+};
+
+// Assist: replace_turbofish_with_explicit_type
+//
+// Converts `::<_>` to an explicit type assignment.
+//
+// ```
+// fn make<T>() -> T { ) }
+// fn main() {
+//     let a = make$0::<i32>();
+// }
+// ```
+// ->
+// ```
+// fn make<T>() -> T { ) }
+// fn main() {
+//     let a: i32 = make();
+// }
+// ```
+pub(crate) fn replace_turbofish_with_explicit_type(
+    acc: &mut Assists,
+    ctx: &AssistContext,
+) -> Option<()> {
+    let let_stmt = ctx.find_node_at_offset::<LetStmt>()?;
+
+    let initializer = let_stmt.initializer()?;
+
+    let (turbofish_start, turbofish_type, turbofish_end) = if let Expr::CallExpr(ce) = initializer {
+        if let Expr::PathExpr(pe) = ce.expr()? {
+            let path = pe.path()?;
+
+            let generic_args = path.segment()?.generic_arg_list()?;
+
+            let colon2 = generic_args.coloncolon_token()?;
+            let r_angle = generic_args.r_angle_token()?;
+
+            let turbofish_args_as_string = generic_args
+                .generic_args()
+                .into_iter()
+                .map(|a| -> String { a.to_string() })
+                .collect::<Vec<String>>()
+                .join(", ");
+
+            (colon2.text_range().start(), turbofish_args_as_string, r_angle.text_range().end())
+        } else {
+            cov_mark::hit!(not_applicable_if_non_path_function_call);
+            return None;
+        }
+    } else {
+        cov_mark::hit!(not_applicable_if_non_function_call_initializer);
+        return None;
+    };
+
+    let turbofish_range = TextRange::new(turbofish_start, turbofish_end);
+
+    if let None = let_stmt.colon_token() {
+        // If there's no colon in a let statement, then there is no explicit type.
+        // let x = fn::<...>();
+        let ident_range = let_stmt.pat()?.syntax().text_range();
+
+        return acc.add(
+            AssistId("replace_turbofish_with_explicit_type", AssistKind::RefactorRewrite),
+            format!("Replace turbofish with explicit type `: <{}>`", turbofish_type),
+            turbofish_range,
+            |builder| {
+                builder.insert(ident_range.end(), format!(": {}", turbofish_type));
+                builder.delete(turbofish_range);
+            },
+        );
+    } else if let Some(InferType(t)) = let_stmt.ty() {
+        // If there's a type inferrence underscore, we can offer to replace it with the type in
+        // the turbofish.
+        // let x: _ = fn::<...>();
+        let underscore_range = t.syntax().text_range();
+
+        return acc.add(
+            AssistId("replace_turbofish_with_explicit_type", AssistKind::RefactorRewrite),
+            format!("Replace `_` with turbofish type `{}`", turbofish_type),
+            turbofish_range,
+            |builder| {
+                builder.replace(underscore_range, turbofish_type);
+                builder.delete(turbofish_range);
+            },
+        );
+    }
+
+    None
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
+
+    #[test]
+    fn replaces_turbofish_for_vec_string() {
+        check_assist(
+            replace_turbofish_with_explicit_type,
+            r#"
+fn make<T>() -> T {}
+fn main() {
+    let a = make$0::<Vec<String>>();
+}
+"#,
+            r#"
+fn make<T>() -> T {}
+fn main() {
+    let a: Vec<String> = make();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn replace_turbofish_target() {
+        check_assist_target(
+            replace_turbofish_with_explicit_type,
+            r#"
+fn make<T>() -> T {}
+fn main() {
+    let a = $0make::<Vec<String>>();
+}
+"#,
+            r#"::<Vec<String>>"#,
+        );
+    }
+
+    #[test]
+    fn replace_inferred_type_placeholder() {
+        check_assist(
+            replace_turbofish_with_explicit_type,
+            r#"
+fn make<T>() -> T {}
+fn main() {
+    let a: _ = make$0::<Vec<String>>();
+}
+"#,
+            r#"
+fn make<T>() -> T {}
+fn main() {
+    let a: Vec<String> = make();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn not_applicable_constant_initializer() {
+        cov_mark::check!(not_applicable_if_non_function_call_initializer);
+        check_assist_not_applicable(
+            replace_turbofish_with_explicit_type,
+            r#"
+fn make<T>() -> T {}
+fn main() {
+    let a = "foo"$0;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn not_applicable_non_path_function_call() {
+        cov_mark::check!(not_applicable_if_non_path_function_call);
+        check_assist_not_applicable(
+            replace_turbofish_with_explicit_type,
+            r#"
+fn make<T>() -> T {}
+fn main() {
+    $0let a = (|| {})();
+}
+"#,
+        );
+    }
+}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 8f9ae6c1540..5d4c1532dbe 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -175,6 +175,7 @@ mod handlers {
     mod replace_let_with_if_let;
     mod replace_qualified_name_with_use;
     mod replace_string_with_char;
+    mod replace_turbofish_with_explicit_type;
     mod split_import;
     mod sort_items;
     mod toggle_ignore;
@@ -257,6 +258,7 @@ mod handlers {
             replace_if_let_with_match::replace_if_let_with_match,
             replace_if_let_with_match::replace_match_with_if_let,
             replace_let_with_if_let::replace_let_with_if_let,
+            replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type,
             replace_qualified_name_with_use::replace_qualified_name_with_use,
             sort_items::sort_items,
             split_import::split_import,
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 43d30e84b3b..e30f98bcd13 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -1877,6 +1877,25 @@ fn handle() {
 }
 
 #[test]
+fn doctest_replace_turbofish_with_explicit_type() {
+    check_doc_test(
+        "replace_turbofish_with_explicit_type",
+        r#####"
+fn make<T>() -> T { ) }
+fn main() {
+    let a = make$0::<i32>();
+}
+"#####,
+        r#####"
+fn make<T>() -> T { ) }
+fn main() {
+    let a: i32 = make();
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_sort_items() {
     check_doc_test(
         "sort_items",