Skip to content

Commit 210a1d5

Browse files
bors[bot]terrynsun
andauthored
Merge #10629
10629: Add assist for replacing turbofish with explicit type. r=Veykril a=terrynsun Converts `::<_>` to an explicit type assignment. ``` let args = args.collect::<Vec<String>>(); ``` -> ``` let args: Vec<String> = args.collect(); ``` Closes #10285 Co-authored-by: Terry Sun <terrynsun@gmail.com>
2 parents 9d1f150 + d800a1b commit 210a1d5

File tree

3 files changed

+264
-0
lines changed

3 files changed

+264
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
use syntax::{
2+
ast::{Expr, GenericArg},
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 generic_args = match &initializer {
38+
Expr::MethodCallExpr(ce) => ce.generic_arg_list()?,
39+
Expr::CallExpr(ce) => {
40+
if let Expr::PathExpr(pe) = ce.expr()? {
41+
pe.path()?.segment()?.generic_arg_list()?
42+
} else {
43+
cov_mark::hit!(not_applicable_if_non_path_function_call);
44+
return None;
45+
}
46+
}
47+
_ => {
48+
cov_mark::hit!(not_applicable_if_non_function_call_initializer);
49+
return None;
50+
}
51+
};
52+
53+
// Find range of ::<_>
54+
let colon2 = generic_args.coloncolon_token()?;
55+
let r_angle = generic_args.r_angle_token()?;
56+
let turbofish_range = TextRange::new(colon2.text_range().start(), r_angle.text_range().end());
57+
58+
let turbofish_args: Vec<GenericArg> = generic_args.generic_args().into_iter().collect();
59+
60+
// Find type of ::<_>
61+
if turbofish_args.len() != 1 {
62+
cov_mark::hit!(not_applicable_if_not_single_arg);
63+
return None;
64+
}
65+
66+
// An improvement would be to check that this is correctly part of the return value of the
67+
// function call, or sub in the actual return type.
68+
let turbofish_type = &turbofish_args[0];
69+
70+
let initializer_start = initializer.syntax().text_range().start();
71+
if ctx.offset() > turbofish_range.end() || ctx.offset() < initializer_start {
72+
cov_mark::hit!(not_applicable_outside_turbofish);
73+
return None;
74+
}
75+
76+
if let None = let_stmt.colon_token() {
77+
// If there's no colon in a let statement, then there is no explicit type.
78+
// let x = fn::<...>();
79+
let ident_range = let_stmt.pat()?.syntax().text_range();
80+
81+
return acc.add(
82+
AssistId("replace_turbofish_with_explicit_type", AssistKind::RefactorRewrite),
83+
"Replace turbofish with explicit type",
84+
TextRange::new(initializer_start, turbofish_range.end()),
85+
|builder| {
86+
builder.insert(ident_range.end(), format!(": {}", turbofish_type));
87+
builder.delete(turbofish_range);
88+
},
89+
);
90+
} else if let Some(InferType(t)) = let_stmt.ty() {
91+
// If there's a type inferrence underscore, we can offer to replace it with the type in
92+
// the turbofish.
93+
// let x: _ = fn::<...>();
94+
let underscore_range = t.syntax().text_range();
95+
96+
return acc.add(
97+
AssistId("replace_turbofish_with_explicit_type", AssistKind::RefactorRewrite),
98+
"Replace `_` with turbofish type",
99+
turbofish_range,
100+
|builder| {
101+
builder.replace(underscore_range, turbofish_type.to_string());
102+
builder.delete(turbofish_range);
103+
},
104+
);
105+
}
106+
107+
None
108+
}
109+
110+
#[cfg(test)]
111+
mod tests {
112+
use super::*;
113+
114+
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
115+
116+
#[test]
117+
fn replaces_turbofish_for_vec_string() {
118+
check_assist(
119+
replace_turbofish_with_explicit_type,
120+
r#"
121+
fn make<T>() -> T {}
122+
fn main() {
123+
let a = make$0::<Vec<String>>();
124+
}
125+
"#,
126+
r#"
127+
fn make<T>() -> T {}
128+
fn main() {
129+
let a: Vec<String> = make();
130+
}
131+
"#,
132+
);
133+
}
134+
135+
#[test]
136+
fn replaces_method_calls() {
137+
// foo.make() is a method call which uses a different expr in the let initializer
138+
check_assist(
139+
replace_turbofish_with_explicit_type,
140+
r#"
141+
fn make<T>() -> T {}
142+
fn main() {
143+
let a = foo.make$0::<Vec<String>>();
144+
}
145+
"#,
146+
r#"
147+
fn make<T>() -> T {}
148+
fn main() {
149+
let a: Vec<String> = foo.make();
150+
}
151+
"#,
152+
);
153+
}
154+
155+
#[test]
156+
fn replace_turbofish_target() {
157+
check_assist_target(
158+
replace_turbofish_with_explicit_type,
159+
r#"
160+
fn make<T>() -> T {}
161+
fn main() {
162+
let a = $0make::<Vec<String>>();
163+
}
164+
"#,
165+
r#"make::<Vec<String>>"#,
166+
);
167+
}
168+
169+
#[test]
170+
fn not_applicable_outside_turbofish() {
171+
cov_mark::check!(not_applicable_outside_turbofish);
172+
check_assist_not_applicable(
173+
replace_turbofish_with_explicit_type,
174+
r#"
175+
fn make<T>() -> T {}
176+
fn main() {
177+
let $0a = make::<Vec<String>>();
178+
}
179+
"#,
180+
);
181+
}
182+
183+
#[test]
184+
fn replace_inferred_type_placeholder() {
185+
check_assist(
186+
replace_turbofish_with_explicit_type,
187+
r#"
188+
fn make<T>() -> T {}
189+
fn main() {
190+
let a: _ = make$0::<Vec<String>>();
191+
}
192+
"#,
193+
r#"
194+
fn make<T>() -> T {}
195+
fn main() {
196+
let a: Vec<String> = make();
197+
}
198+
"#,
199+
);
200+
}
201+
202+
#[test]
203+
fn not_applicable_constant_initializer() {
204+
cov_mark::check!(not_applicable_if_non_function_call_initializer);
205+
check_assist_not_applicable(
206+
replace_turbofish_with_explicit_type,
207+
r#"
208+
fn make<T>() -> T {}
209+
fn main() {
210+
let a = "foo"$0;
211+
}
212+
"#,
213+
);
214+
}
215+
216+
#[test]
217+
fn not_applicable_non_path_function_call() {
218+
cov_mark::check!(not_applicable_if_non_path_function_call);
219+
check_assist_not_applicable(
220+
replace_turbofish_with_explicit_type,
221+
r#"
222+
fn make<T>() -> T {}
223+
fn main() {
224+
$0let a = (|| {})();
225+
}
226+
"#,
227+
);
228+
}
229+
230+
#[test]
231+
fn non_applicable_multiple_generic_args() {
232+
cov_mark::check!(not_applicable_if_not_single_arg);
233+
check_assist_not_applicable(
234+
replace_turbofish_with_explicit_type,
235+
r#"
236+
fn make<T>() -> T {}
237+
fn main() {
238+
let a = make$0::<Vec<String>, i32>();
239+
}
240+
"#,
241+
);
242+
}
243+
}

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)