Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | crates/ide_assists/src/handlers/replace_turbofish_with_explicit_type.rs | 243 | ||||
| -rw-r--r-- | crates/ide_assists/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 19 |
3 files changed, 264 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..dc3c860d7e --- /dev/null +++ b/crates/ide_assists/src/handlers/replace_turbofish_with_explicit_type.rs @@ -0,0 +1,243 @@ +use syntax::{ + ast::{Expr, GenericArg}, + 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 generic_args = match &initializer { + Expr::MethodCallExpr(ce) => ce.generic_arg_list()?, + Expr::CallExpr(ce) => { + if let Expr::PathExpr(pe) = ce.expr()? { + pe.path()?.segment()?.generic_arg_list()? + } else { + cov_mark::hit!(not_applicable_if_non_path_function_call); + return None; + } + } + _ => { + cov_mark::hit!(not_applicable_if_non_function_call_initializer); + return None; + } + }; + + // Find range of ::<_> + let colon2 = generic_args.coloncolon_token()?; + let r_angle = generic_args.r_angle_token()?; + let turbofish_range = TextRange::new(colon2.text_range().start(), r_angle.text_range().end()); + + let turbofish_args: Vec<GenericArg> = generic_args.generic_args().into_iter().collect(); + + // Find type of ::<_> + if turbofish_args.len() != 1 { + cov_mark::hit!(not_applicable_if_not_single_arg); + return None; + } + + // An improvement would be to check that this is correctly part of the return value of the + // function call, or sub in the actual return type. + let turbofish_type = &turbofish_args[0]; + + let initializer_start = initializer.syntax().text_range().start(); + if ctx.offset() > turbofish_range.end() || ctx.offset() < initializer_start { + cov_mark::hit!(not_applicable_outside_turbofish); + return None; + } + + 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), + "Replace turbofish with explicit type", + TextRange::new(initializer_start, turbofish_range.end()), + |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), + "Replace `_` with turbofish type", + turbofish_range, + |builder| { + builder.replace(underscore_range, turbofish_type.to_string()); + 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 replaces_method_calls() { + // foo.make() is a method call which uses a different expr in the let initializer + check_assist( + replace_turbofish_with_explicit_type, + r#" +fn make<T>() -> T {} +fn main() { + let a = foo.make$0::<Vec<String>>(); +} +"#, + r#" +fn make<T>() -> T {} +fn main() { + let a: Vec<String> = foo.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#"make::<Vec<String>>"#, + ); + } + + #[test] + fn not_applicable_outside_turbofish() { + cov_mark::check!(not_applicable_outside_turbofish); + check_assist_not_applicable( + replace_turbofish_with_explicit_type, + r#" +fn make<T>() -> T {} +fn main() { + let $0a = make::<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 = (|| {})(); +} +"#, + ); + } + + #[test] + fn non_applicable_multiple_generic_args() { + cov_mark::check!(not_applicable_if_not_single_arg); + check_assist_not_applicable( + replace_turbofish_with_explicit_type, + r#" +fn make<T>() -> T {} +fn main() { + let a = make$0::<Vec<String>, i32>(); +} +"#, + ); + } +} 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", |