From 324d7d33e829cf8fd73ce27aebe48f64876fadda Mon Sep 17 00:00:00 2001 From: Terry Sun Date: Sun, 24 Oct 2021 13:34:02 -0700 Subject: [PATCH] Add assist for replacing turbofish with explicit type. Converts `::<_>` to an explicit type assignment. ``` let args = args.collect::>(); ``` -> ``` let args: Vec = args.collect(); ``` Closes #10285 --- .../replace_turbofish_with_explicit_type.rs | 184 ++++++++++++++++++ crates/ide_assists/src/lib.rs | 2 + crates/ide_assists/src/tests/generated.rs | 19 ++ 3 files changed, 205 insertions(+) create mode 100644 crates/ide_assists/src/handlers/replace_turbofish_with_explicit_type.rs 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 000000000000..b02596daede7 --- /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 { ) } +// fn main() { +// let a = make$0::(); +// } +// ``` +// -> +// ``` +// fn make() -> 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::()?; + + 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::>() + .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 {} +fn main() { + let a = make$0::>(); +} +"#, + r#" +fn make() -> T {} +fn main() { + let a: Vec = make(); +} +"#, + ); + } + + #[test] + fn replace_turbofish_target() { + check_assist_target( + replace_turbofish_with_explicit_type, + r#" +fn make() -> T {} +fn main() { + let a = $0make::>(); +} +"#, + r#"::>"#, + ); + } + + #[test] + fn replace_inferred_type_placeholder() { + check_assist( + replace_turbofish_with_explicit_type, + r#" +fn make() -> T {} +fn main() { + let a: _ = make$0::>(); +} +"#, + r#" +fn make() -> T {} +fn main() { + let a: Vec = 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 {} +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 {} +fn main() { + $0let a = (|| {})(); +} +"#, + ); + } +} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 8f9ae6c15405..5d4c1532dbe2 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 43d30e84b3b0..e30f98bcd136 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -1876,6 +1876,25 @@ fn handle() { ) } +#[test] +fn doctest_replace_turbofish_with_explicit_type() { + check_doc_test( + "replace_turbofish_with_explicit_type", + r#####" +fn make() -> T { ) } +fn main() { + let a = make$0::(); +} +"#####, + r#####" +fn make() -> T { ) } +fn main() { + let a: i32 = make(); +} +"#####, + ) +} + #[test] fn doctest_sort_items() { check_doc_test(