Unnamed repository; edit this file 'description' to name the repository.
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
Terry Sun 2021-10-25
parent 1f47693 · commit 324d7d3
-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 0000000000..b02596daed
--- /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 8f9ae6c154..5d4c1532db 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 43d30e84b3..e30f98bcd1 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",