Skip to content

Commit 535eb0d

Browse files
committed
Auto merge of rust-lang#15874 - DropDemBits:structured-snippet-migrate-4, r=Veykril
internal: Migrate assists to the structured snippet API, part 4 Continuing from rust-lang#15260 Migrates the following assists: - `add_turbo_fish` - `add_type_ascription` - `destructure_tuple_binding` - `destructure_tuple_binding_in_subpattern` I did this a while ago, but forgot to make a PR for the changes until now. 😅
2 parents a473303 + 3f99a56 commit 535eb0d

File tree

5 files changed

+432
-140
lines changed

5 files changed

+432
-140
lines changed

crates/ide-assists/src/handlers/add_turbo_fish.rs

+98-42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use either::Either;
12
use ide_db::defs::{Definition, NameRefClass};
2-
use itertools::Itertools;
3-
use syntax::{ast, AstNode, SyntaxKind, T};
3+
use syntax::{
4+
ast::{self, make, HasArgList},
5+
ted, AstNode,
6+
};
47

58
use crate::{
69
assist_context::{AssistContext, Assists},
@@ -25,21 +28,45 @@ use crate::{
2528
// }
2629
// ```
2730
pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
28-
let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
29-
let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
30-
if arg_list.args().next().is_some() {
31-
return None;
32-
}
33-
cov_mark::hit!(add_turbo_fish_after_call);
34-
cov_mark::hit!(add_type_ascription_after_call);
35-
arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
36-
})?;
37-
let next_token = ident.next_token()?;
38-
if next_token.kind() == T![::] {
31+
let turbofish_target =
32+
ctx.find_node_at_offset::<ast::PathSegment>().map(Either::Left).or_else(|| {
33+
let callable_expr = ctx.find_node_at_offset::<ast::CallableExpr>()?;
34+
35+
if callable_expr.arg_list()?.args().next().is_some() {
36+
return None;
37+
}
38+
39+
cov_mark::hit!(add_turbo_fish_after_call);
40+
cov_mark::hit!(add_type_ascription_after_call);
41+
42+
match callable_expr {
43+
ast::CallableExpr::Call(it) => {
44+
let ast::Expr::PathExpr(path) = it.expr()? else {
45+
return None;
46+
};
47+
48+
Some(Either::Left(path.path()?.segment()?))
49+
}
50+
ast::CallableExpr::MethodCall(it) => Some(Either::Right(it)),
51+
}
52+
})?;
53+
54+
let already_has_turbofish = match &turbofish_target {
55+
Either::Left(path_segment) => path_segment.generic_arg_list().is_some(),
56+
Either::Right(method_call) => method_call.generic_arg_list().is_some(),
57+
};
58+
59+
if already_has_turbofish {
3960
cov_mark::hit!(add_turbo_fish_one_fish_is_enough);
4061
return None;
4162
}
42-
let name_ref = ast::NameRef::cast(ident.parent()?)?;
63+
64+
let name_ref = match &turbofish_target {
65+
Either::Left(path_segment) => path_segment.name_ref()?,
66+
Either::Right(method_call) => method_call.name_ref()?,
67+
};
68+
let ident = name_ref.ident_token()?;
69+
4370
let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
4471
NameRefClass::Definition(def) => def,
4572
NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => {
@@ -58,20 +85,27 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
5885

5986
if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
6087
if let_stmt.colon_token().is_none() {
61-
let type_pos = let_stmt.pat()?.syntax().last_token()?.text_range().end();
62-
let semi_pos = let_stmt.syntax().last_token()?.text_range().end();
88+
if let_stmt.pat().is_none() {
89+
return None;
90+
}
6391

6492
acc.add(
6593
AssistId("add_type_ascription", AssistKind::RefactorRewrite),
6694
"Add `: _` before assignment operator",
6795
ident.text_range(),
68-
|builder| {
96+
|edit| {
97+
let let_stmt = edit.make_mut(let_stmt);
98+
6999
if let_stmt.semicolon_token().is_none() {
70-
builder.insert(semi_pos, ";");
100+
ted::append_child(let_stmt.syntax(), make::tokens::semicolon());
71101
}
72-
match ctx.config.snippet_cap {
73-
Some(cap) => builder.insert_snippet(cap, type_pos, ": ${0:_}"),
74-
None => builder.insert(type_pos, ": _"),
102+
103+
let placeholder_ty = make::ty_placeholder().clone_for_update();
104+
105+
let_stmt.set_ty(Some(placeholder_ty.clone()));
106+
107+
if let Some(cap) = ctx.config.snippet_cap {
108+
edit.add_placeholder_snippet(cap, placeholder_ty);
75109
}
76110
},
77111
)?
@@ -91,38 +125,46 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
91125
AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
92126
"Add `::<>`",
93127
ident.text_range(),
94-
|builder| {
95-
builder.trigger_signature_help();
96-
match ctx.config.snippet_cap {
97-
Some(cap) => {
98-
let fish_head = get_snippet_fish_head(number_of_arguments);
99-
let snip = format!("::<{fish_head}>");
100-
builder.insert_snippet(cap, ident.text_range().end(), snip)
128+
|edit| {
129+
edit.trigger_signature_help();
130+
131+
let new_arg_list = match turbofish_target {
132+
Either::Left(path_segment) => {
133+
edit.make_mut(path_segment).get_or_create_generic_arg_list()
134+
}
135+
Either::Right(method_call) => {
136+
edit.make_mut(method_call).get_or_create_generic_arg_list()
101137
}
102-
None => {
103-
let fish_head = std::iter::repeat("_").take(number_of_arguments).format(", ");
104-
let snip = format!("::<{fish_head}>");
105-
builder.insert(ident.text_range().end(), snip);
138+
};
139+
140+
let fish_head = get_fish_head(number_of_arguments).clone_for_update();
141+
142+
// Note: we need to replace the `new_arg_list` instead of being able to use something like
143+
// `GenericArgList::add_generic_arg` as `PathSegment::get_or_create_generic_arg_list`
144+
// always creates a non-turbofish form generic arg list.
145+
ted::replace(new_arg_list.syntax(), fish_head.syntax());
146+
147+
if let Some(cap) = ctx.config.snippet_cap {
148+
for arg in fish_head.generic_args() {
149+
edit.add_placeholder_snippet(cap, arg)
106150
}
107151
}
108152
},
109153
)
110154
}
111155

112-
/// This will create a snippet string with tabstops marked
113-
fn get_snippet_fish_head(number_of_arguments: usize) -> String {
114-
let mut fish_head = (1..number_of_arguments)
115-
.format_with("", |i, f| f(&format_args!("${{{i}:_}}, ")))
116-
.to_string();
117-
118-
// tabstop 0 is a special case and always the last one
119-
fish_head.push_str("${0:_}");
120-
fish_head
156+
/// This will create a turbofish generic arg list corresponding to the number of arguments
157+
fn get_fish_head(number_of_arguments: usize) -> ast::GenericArgList {
158+
let args = (0..number_of_arguments).map(|_| make::type_arg(make::ty_placeholder()).into());
159+
make::turbofish_generic_arg_list(args)
121160
}
122161

123162
#[cfg(test)]
124163
mod tests {
125-
use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable};
164+
use crate::tests::{
165+
check_assist, check_assist_by_label, check_assist_not_applicable,
166+
check_assist_not_applicable_by_label,
167+
};
126168

127169
use super::*;
128170

@@ -363,6 +405,20 @@ fn main() {
363405
);
364406
}
365407

408+
#[test]
409+
fn add_type_ascription_missing_pattern() {
410+
check_assist_not_applicable_by_label(
411+
add_turbo_fish,
412+
r#"
413+
fn make<T>() -> T {}
414+
fn main() {
415+
let = make$0()
416+
}
417+
"#,
418+
"Add `: _` before assignment operator",
419+
);
420+
}
421+
366422
#[test]
367423
fn add_turbo_fish_function_lifetime_parameter() {
368424
check_assist(

0 commit comments

Comments
 (0)