Skip to content

Commit 6abdbdd

Browse files
committed
fixup! narrow range; method calls; check for only one type
1 parent 324d7d3 commit 6abdbdd

File tree

1 file changed

+97
-26
lines changed

1 file changed

+97
-26
lines changed

crates/ide_assists/src/handlers/replace_turbofish_with_explicit_type.rs

Lines changed: 97 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use syntax::{
2-
ast::Expr,
2+
ast::{Expr, GenericArg, GenericArgList},
33
ast::{LetStmt, Type::InferType},
44
AstNode, TextRange,
55
};
@@ -34,33 +34,31 @@ pub(crate) fn replace_turbofish_with_explicit_type(
3434

3535
let initializer = let_stmt.initializer()?;
3636

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);
37+
let (turbofish_range, turbofish_type) = match &initializer {
38+
Expr::MethodCallExpr(ce) => {
39+
let generic_args = ce.generic_arg_list()?;
40+
(turbofish_range(&generic_args)?, turbofish_type(&generic_args)?)
41+
}
42+
Expr::CallExpr(ce) => {
43+
if let Expr::PathExpr(pe) = ce.expr()? {
44+
let generic_args = pe.path()?.segment()?.generic_arg_list()?;
45+
(turbofish_range(&generic_args)?, turbofish_type(&generic_args)?)
46+
} else {
47+
cov_mark::hit!(not_applicable_if_non_path_function_call);
48+
return None;
49+
}
50+
}
51+
_ => {
52+
cov_mark::hit!(not_applicable_if_non_function_call_initializer);
5653
return None;
5754
}
58-
} else {
59-
cov_mark::hit!(not_applicable_if_non_function_call_initializer);
60-
return None;
6155
};
6256

63-
let turbofish_range = TextRange::new(turbofish_start, turbofish_end);
57+
let initializer_start = initializer.syntax().text_range().start();
58+
if ctx.offset() > turbofish_range.end() || ctx.offset() < initializer_start {
59+
cov_mark::hit!(not_applicable_outside_turbofish);
60+
return None;
61+
}
6462

6563
if let None = let_stmt.colon_token() {
6664
// If there's no colon in a let statement, then there is no explicit type.
@@ -70,7 +68,7 @@ pub(crate) fn replace_turbofish_with_explicit_type(
7068
return acc.add(
7169
AssistId("replace_turbofish_with_explicit_type", AssistKind::RefactorRewrite),
7270
format!("Replace turbofish with explicit type `: <{}>`", turbofish_type),
73-
turbofish_range,
71+
TextRange::new(initializer_start, turbofish_range.end()),
7472
|builder| {
7573
builder.insert(ident_range.end(), format!(": {}", turbofish_type));
7674
builder.delete(turbofish_range);
@@ -96,6 +94,31 @@ pub(crate) fn replace_turbofish_with_explicit_type(
9694
None
9795
}
9896

97+
/// Returns the type of the turbofish as a String.
98+
/// Returns None if there are 0 or >1 arguments.
99+
fn turbofish_type(generic_args: &GenericArgList) -> Option<String> {
100+
let turbofish_args: Vec<GenericArg> = generic_args.generic_args().into_iter().collect();
101+
102+
if turbofish_args.len() != 1 {
103+
cov_mark::hit!(not_applicable_if_not_single_arg);
104+
return None;
105+
}
106+
107+
// An improvement would be to check that this is correctly part of the return value of the
108+
// function call, or sub in the actual return type.
109+
let turbofish_type = turbofish_args[0].to_string();
110+
111+
Some(turbofish_type)
112+
}
113+
114+
/// Returns the TextRange of the whole turbofish expression, and the generic argument as a String.
115+
fn turbofish_range(generic_args: &GenericArgList) -> Option<TextRange> {
116+
let colon2 = generic_args.coloncolon_token()?;
117+
let r_angle = generic_args.r_angle_token()?;
118+
119+
Some(TextRange::new(colon2.text_range().start(), r_angle.text_range().end()))
120+
}
121+
99122
#[cfg(test)]
100123
mod tests {
101124
use super::*;
@@ -121,6 +144,26 @@ fn main() {
121144
);
122145
}
123146

147+
#[test]
148+
fn replaces_method_calls() {
149+
// foo.make() is a method call which uses a different expr in the let initializer
150+
check_assist(
151+
replace_turbofish_with_explicit_type,
152+
r#"
153+
fn make<T>() -> T {}
154+
fn main() {
155+
let a = foo.make$0::<Vec<String>>();
156+
}
157+
"#,
158+
r#"
159+
fn make<T>() -> T {}
160+
fn main() {
161+
let a: Vec<String> = foo.make();
162+
}
163+
"#,
164+
);
165+
}
166+
124167
#[test]
125168
fn replace_turbofish_target() {
126169
check_assist_target(
@@ -131,7 +174,21 @@ fn main() {
131174
let a = $0make::<Vec<String>>();
132175
}
133176
"#,
134-
r#"::<Vec<String>>"#,
177+
r#"make::<Vec<String>>"#,
178+
);
179+
}
180+
181+
#[test]
182+
fn not_applicable_outside_turbofish() {
183+
cov_mark::check!(not_applicable_outside_turbofish);
184+
check_assist_not_applicable(
185+
replace_turbofish_with_explicit_type,
186+
r#"
187+
fn make<T>() -> T {}
188+
fn main() {
189+
let $0a = make::<Vec<String>>();
190+
}
191+
"#,
135192
);
136193
}
137194

@@ -178,6 +235,20 @@ fn make<T>() -> T {}
178235
fn main() {
179236
$0let a = (|| {})();
180237
}
238+
"#,
239+
);
240+
}
241+
242+
#[test]
243+
fn non_applicable_multiple_generic_args() {
244+
cov_mark::check!(not_applicable_if_not_single_arg);
245+
check_assist_not_applicable(
246+
replace_turbofish_with_explicit_type,
247+
r#"
248+
fn make<T>() -> T {}
249+
fn main() {
250+
let a = make$0::<Vec<String>, i32>();
251+
}
181252
"#,
182253
);
183254
}

0 commit comments

Comments
 (0)