Skip to content

Commit 7057129

Browse files
committed
feat: add type ascription assist
1 parent f815217 commit 7057129

File tree

4 files changed

+220
-0
lines changed

4 files changed

+220
-0
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use ide_db::defs::{Definition, NameRefClass};
2+
use syntax::{ast, AstNode, SyntaxKind, T};
3+
use test_utils::mark;
4+
5+
use crate::{
6+
assist_context::{AssistContext, Assists},
7+
AssistId, AssistKind,
8+
};
9+
10+
// Assist: add_type_ascription
11+
//
12+
// Adds `: _` before the assignment operator to prompt the user for a type
13+
//
14+
// ```
15+
// fn make<T>() -> T { todo!() }
16+
// fn main() {
17+
// let x = make$0();
18+
// }
19+
// ```
20+
// ->
21+
// ```
22+
// fn make<T>() -> T { todo!() }
23+
// fn main() {
24+
// let x: ${0:_} = make();
25+
// }
26+
// ```
27+
pub(crate) fn add_type_ascription(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28+
let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
29+
if let_stmt.colon_token().is_some() {
30+
mark::hit!(add_type_ascription_already_typed);
31+
return None
32+
}
33+
34+
let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
35+
let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
36+
if arg_list.args().count() > 0 {
37+
return None;
38+
}
39+
mark::hit!(add_type_ascription_after_call);
40+
arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
41+
})?;
42+
let next_token = ident.next_token()?;
43+
if next_token.kind() == T![::] {
44+
mark::hit!(add_type_ascription_turbofished);
45+
return None;
46+
}
47+
let name_ref = ast::NameRef::cast(ident.parent())?;
48+
let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
49+
NameRefClass::Definition(def) => def,
50+
NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None,
51+
};
52+
let fun = match def {
53+
Definition::ModuleDef(hir::ModuleDef::Function(it)) => it,
54+
_ => return None,
55+
};
56+
let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
57+
if generics.is_empty() {
58+
mark::hit!(add_type_ascription_non_generic);
59+
return None;
60+
}
61+
let pat = let_stmt.pat()?.syntax().last_token()?.text_range().end();
62+
acc.add(
63+
AssistId("add_type_ascription", AssistKind::RefactorRewrite),
64+
"Add `: _` before assignment operator",
65+
ident.text_range(),
66+
|builder| match ctx.config.snippet_cap {
67+
Some(cap) => builder.insert_snippet(cap, pat, ": ${0:_}"),
68+
None => builder.insert(pat, ": _"),
69+
},
70+
)
71+
}
72+
73+
#[cfg(test)]
74+
mod tests {
75+
use crate::tests::{check_assist, check_assist_not_applicable};
76+
77+
use super::*;
78+
use test_utils::mark;
79+
80+
#[test]
81+
fn add_type_ascription_function() {
82+
check_assist(
83+
add_type_ascription,
84+
r#"
85+
fn make<T>() -> T {}
86+
fn main() {
87+
let x = make$0();
88+
}
89+
"#,
90+
r#"
91+
fn make<T>() -> T {}
92+
fn main() {
93+
let x: ${0:_} = make();
94+
}
95+
"#,
96+
);
97+
}
98+
99+
#[test]
100+
fn add_type_ascription_after_call() {
101+
mark::check!(add_type_ascription_after_call);
102+
check_assist(
103+
add_type_ascription,
104+
r#"
105+
fn make<T>() -> T {}
106+
fn main() {
107+
let x = make()$0;
108+
}
109+
"#,
110+
r#"
111+
fn make<T>() -> T {}
112+
fn main() {
113+
let x: ${0:_} = make();
114+
}
115+
"#,
116+
);
117+
}
118+
119+
#[test]
120+
fn add_type_ascription_method() {
121+
check_assist(
122+
add_type_ascription,
123+
r#"
124+
struct S;
125+
impl S {
126+
fn make<T>(&self) -> T {}
127+
}
128+
fn main() {
129+
let x = S.make$0();
130+
}
131+
"#,
132+
r#"
133+
struct S;
134+
impl S {
135+
fn make<T>(&self) -> T {}
136+
}
137+
fn main() {
138+
let x: ${0:_} = S.make();
139+
}
140+
"#,
141+
);
142+
}
143+
144+
#[test]
145+
fn add_type_ascription_turbofished() {
146+
mark::check!(add_type_ascription_turbofished);
147+
check_assist_not_applicable(
148+
add_type_ascription,
149+
r#"
150+
fn make<T>() -> T {}
151+
fn main() {
152+
let x = make$0::<()>();
153+
}
154+
"#,
155+
);
156+
}
157+
158+
#[test]
159+
fn add_type_ascription_already_typed() {
160+
mark::check!(add_type_ascription_already_typed);
161+
check_assist_not_applicable(
162+
add_type_ascription,
163+
r#"
164+
fn make<T>() -> T {}
165+
fn main() {
166+
let x: () = make$0();
167+
}
168+
"#,
169+
);
170+
}
171+
172+
#[test]
173+
fn add_type_ascription_non_generic() {
174+
mark::check!(add_type_ascription_non_generic);
175+
check_assist_not_applicable(
176+
add_type_ascription,
177+
r#"
178+
fn make() -> () {}
179+
fn main() {
180+
let x = make$0();
181+
}
182+
"#,
183+
);
184+
}
185+
186+
#[test]
187+
fn add_type_ascription_no_let() {
188+
check_assist_not_applicable(
189+
add_type_ascription,
190+
r#"
191+
fn make<T>() -> T {}
192+
fn main() {
193+
make$0();
194+
}
195+
"#,
196+
);
197+
}
198+
}

crates/ide_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ mod handlers {
111111
mod add_lifetime_to_type;
112112
mod add_missing_impl_members;
113113
mod add_turbo_fish;
114+
mod add_type_ascription;
114115
mod apply_demorgan;
115116
mod auto_import;
116117
mod change_visibility;
@@ -175,6 +176,7 @@ mod handlers {
175176
add_explicit_type::add_explicit_type,
176177
add_lifetime_to_type::add_lifetime_to_type,
177178
add_turbo_fish::add_turbo_fish,
179+
add_type_ascription::add_type_ascription,
178180
apply_demorgan::apply_demorgan,
179181
auto_import::auto_import,
180182
change_visibility::change_visibility,

crates/ide_assists/src/tests/generated.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,25 @@ fn main() {
141141
)
142142
}
143143

144+
#[test]
145+
fn doctest_add_type_ascription() {
146+
check_doc_test(
147+
"add_type_ascription",
148+
r#####"
149+
fn make<T>() -> T { todo!() }
150+
fn main() {
151+
let x = make$0();
152+
}
153+
"#####,
154+
r#####"
155+
fn make<T>() -> T { todo!() }
156+
fn main() {
157+
let x: ${0:_} = make();
158+
}
159+
"#####,
160+
)
161+
}
162+
144163
#[test]
145164
fn doctest_apply_demorgan() {
146165
check_doc_test(

xtask/src/tidy.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ fn check_todo(path: &Path, text: &str) {
277277
"tests/tidy.rs",
278278
// Some of our assists generate `todo!()`.
279279
"handlers/add_turbo_fish.rs",
280+
"handlers/add_type_ascription.rs",
280281
"handlers/generate_function.rs",
281282
// To support generating `todo!()` in assists, we have `expr_todo()` in
282283
// `ast::make`.

0 commit comments

Comments
 (0)