Skip to content

Commit 324d7d3

Browse files
committed
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 rust-lang#10285
1 parent 1f47693 commit 324d7d3

File tree

3 files changed

+205
-0
lines changed

3 files changed

+205
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
use syntax::{
2+
ast::Expr,
3+
ast::{LetStmt, Type::InferType},
4+
AstNode, TextRange,
5+
};
6+
7+
use crate::{
8+
assist_context::{AssistContext, Assists},
9+
AssistId, AssistKind,
10+
};
11+
12+
// Assist: replace_turbofish_with_explicit_type
13+
//
14+
// Converts `::<_>` to an explicit type assignment.
15+
//
16+
// ```
17+
// fn make<T>() -> T { ) }
18+
// fn main() {
19+
// let a = make$0::<i32>();
20+
// }
21+
// ```
22+
// ->
23+
// ```
24+
// fn make<T>() -> T { ) }
25+
// fn main() {
26+
// let a: i32 = make();
27+
// }
28+
// ```
29+
pub(crate) fn replace_turbofish_with_explicit_type(
30+
acc: &mut Assists,
31+
ctx: &AssistContext,
32+
) -> Option<()> {
33+
let let_stmt = ctx.find_node_at_offset::<LetStmt>()?;
34+
35+
let initializer = let_stmt.initializer()?;
36+
37+
let (turbofish_start, turbofish_type, turbofish_end) = if let Expr::CallExpr(ce) = initializer {
38+
if let Expr::PathExpr(pe) = ce.expr()? {
39+
let path = pe.path()?;
40+
41+
let generic_args = path.segment()?.generic_arg_list()?;
42+
43+
let colon2 = generic_args.coloncolon_token()?;
44+
let r_angle = generic_args.r_angle_token()?;
45+
46+
let turbofish_args_as_string = generic_args
47+
.generic_args()
48+
.into_iter()
49+
.map(|a| -> String { a.to_string() })
50+
.collect::<Vec<String>>()
51+
.join(", ");
52+
53+
(colon2.text_range().start(), turbofish_args_as_string, r_angle.text_range().end())
54+
} else {
55+
cov_mark::hit!(not_applicable_if_non_path_function_call);
56+
return None;
57+
}
58+
} else {
59+
cov_mark::hit!(not_applicable_if_non_function_call_initializer);
60+
return None;
61+
};
62+
63+
let turbofish_range = TextRange::new(turbofish_start, turbofish_end);
64+
65+
if let None = let_stmt.colon_token() {
66+
// If there's no colon in a let statement, then there is no explicit type.
67+
// let x = fn::<...>();
68+
let ident_range = let_stmt.pat()?.syntax().text_range();
69+
70+
return acc.add(
71+
AssistId("replace_turbofish_with_explicit_type", AssistKind::RefactorRewrite),
72+
format!("Replace turbofish with explicit type `: <{}>`", turbofish_type),
73+
turbofish_range,
74+
|builder| {
75+
builder.insert(ident_range.end(), format!(": {}", turbofish_type));
76+
builder.delete(turbofish_range);
77+
},
78+
);
79+
} else if let Some(InferType(t)) = let_stmt.ty() {
80+
// If there's a type inferrence underscore, we can offer to replace it with the type in
81+
// the turbofish.
82+
// let x: _ = fn::<...>();
83+
let underscore_range = t.syntax().text_range();
84+
85+
return acc.add(
86+
AssistId("replace_turbofish_with_explicit_type", AssistKind::RefactorRewrite),
87+
format!("Replace `_` with turbofish type `{}`", turbofish_type),
88+
turbofish_range,
89+
|builder| {
90+
builder.replace(underscore_range, turbofish_type);
91+
builder.delete(turbofish_range);
92+
},
93+
);
94+
}
95+
96+
None
97+
}
98+
99+
#[cfg(test)]
100+
mod tests {
101+
use super::*;
102+
103+
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
104+
105+
#[test]
106+
fn replaces_turbofish_for_vec_string() {
107+
check_assist(
108+
replace_turbofish_with_explicit_type,
109+
r#"
110+
fn make<T>() -> T {}
111+
fn main() {
112+
let a = make$0::<Vec<String>>();
113+
}
114+
"#,
115+
r#"
116+
fn make<T>() -> T {}
117+
fn main() {
118+
let a: Vec<String> = make();
119+
}
120+
"#,
121+
);
122+
}
123+
124+
#[test]
125+
fn replace_turbofish_target() {
126+
check_assist_target(
127+
replace_turbofish_with_explicit_type,
128+
r#"
129+
fn make<T>() -> T {}
130+
fn main() {
131+
let a = $0make::<Vec<String>>();
132+
}
133+
"#,
134+
r#"::<Vec<String>>"#,
135+
);
136+
}
137+
138+
#[test]
139+
fn replace_inferred_type_placeholder() {
140+
check_assist(
141+
replace_turbofish_with_explicit_type,
142+
r#"
143+
fn make<T>() -> T {}
144+
fn main() {
145+
let a: _ = make$0::<Vec<String>>();
146+
}
147+
"#,
148+
r#"
149+
fn make<T>() -> T {}
150+
fn main() {
151+
let a: Vec<String> = make();
152+
}
153+
"#,
154+
);
155+
}
156+
157+
#[test]
158+
fn not_applicable_constant_initializer() {
159+
cov_mark::check!(not_applicable_if_non_function_call_initializer);
160+
check_assist_not_applicable(
161+
replace_turbofish_with_explicit_type,
162+
r#"
163+
fn make<T>() -> T {}
164+
fn main() {
165+
let a = "foo"$0;
166+
}
167+
"#,
168+
);
169+
}
170+
171+
#[test]
172+
fn not_applicable_non_path_function_call() {
173+
cov_mark::check!(not_applicable_if_non_path_function_call);
174+
check_assist_not_applicable(
175+
replace_turbofish_with_explicit_type,
176+
r#"
177+
fn make<T>() -> T {}
178+
fn main() {
179+
$0let a = (|| {})();
180+
}
181+
"#,
182+
);
183+
}
184+
}

crates/ide_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ mod handlers {
175175
mod replace_let_with_if_let;
176176
mod replace_qualified_name_with_use;
177177
mod replace_string_with_char;
178+
mod replace_turbofish_with_explicit_type;
178179
mod split_import;
179180
mod sort_items;
180181
mod toggle_ignore;
@@ -257,6 +258,7 @@ mod handlers {
257258
replace_if_let_with_match::replace_if_let_with_match,
258259
replace_if_let_with_match::replace_match_with_if_let,
259260
replace_let_with_if_let::replace_let_with_if_let,
261+
replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type,
260262
replace_qualified_name_with_use::replace_qualified_name_with_use,
261263
sort_items::sort_items,
262264
split_import::split_import,

crates/ide_assists/src/tests/generated.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,6 +1876,25 @@ fn handle() {
18761876
)
18771877
}
18781878

1879+
#[test]
1880+
fn doctest_replace_turbofish_with_explicit_type() {
1881+
check_doc_test(
1882+
"replace_turbofish_with_explicit_type",
1883+
r#####"
1884+
fn make<T>() -> T { ) }
1885+
fn main() {
1886+
let a = make$0::<i32>();
1887+
}
1888+
"#####,
1889+
r#####"
1890+
fn make<T>() -> T { ) }
1891+
fn main() {
1892+
let a: i32 = make();
1893+
}
1894+
"#####,
1895+
)
1896+
}
1897+
18791898
#[test]
18801899
fn doctest_sort_items() {
18811900
check_doc_test(

0 commit comments

Comments
 (0)