Skip to main content

ide_assists/handlers/
add_explicit_type.rs

1use either::Either;
2use hir::HirDisplay;
3use ide_db::syntax_helpers::node_ext::walk_ty;
4use syntax::ast::{self, AstNode, LetStmt, Param};
5
6use crate::{AssistContext, AssistId, Assists};
7
8// Assist: add_explicit_type
9//
10// Specify type for a let binding.
11//
12// ```
13// fn main() {
14//     let x$0 = 92;
15// }
16// ```
17// ->
18// ```
19// fn main() {
20//     let x: i32 = 92;
21// }
22// ```
23pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
24    let syntax_node = ctx.find_node_at_offset::<Either<LetStmt, Param>>()?;
25    let (ascribed_ty, expr, pat) = if let Either::Left(let_stmt) = syntax_node {
26        let cursor_in_range = {
27            let eq_range = let_stmt.eq_token()?.text_range();
28            ctx.offset() < eq_range.start()
29        };
30        if !cursor_in_range {
31            cov_mark::hit!(add_explicit_type_not_applicable_if_cursor_after_equals);
32            return None;
33        }
34
35        (let_stmt.ty(), let_stmt.initializer(), let_stmt.pat()?)
36    } else if let Either::Right(param) = syntax_node {
37        if param.syntax().ancestors().nth(2).and_then(ast::ClosureExpr::cast).is_none() {
38            cov_mark::hit!(add_explicit_type_not_applicable_in_fn_param);
39            return None;
40        }
41        (param.ty(), None, param.pat()?)
42    } else {
43        return None;
44    };
45
46    let module = ctx.sema.scope(pat.syntax())?.module();
47    let pat_range = pat.syntax().text_range();
48
49    // Don't enable the assist if there is a type ascription without any placeholders
50    if let Some(ty) = &ascribed_ty {
51        let mut contains_infer_ty = false;
52        walk_ty(ty, &mut |ty| {
53            contains_infer_ty |= matches!(ty, ast::Type::InferType(_));
54            false
55        });
56        if !contains_infer_ty {
57            cov_mark::hit!(add_explicit_type_not_applicable_if_ty_already_specified);
58            return None;
59        }
60    }
61
62    let ty = match (pat, expr) {
63        (ast::Pat::IdentPat(_), Some(expr)) => ctx.sema.type_of_expr(&expr)?,
64        (pat, _) => ctx.sema.type_of_pat(&pat)?,
65    }
66    .adjusted();
67
68    // Fully unresolved or unnameable types can't be annotated
69    if (ty.contains_unknown() && ty.type_arguments().count() == 0) || ty.is_closure() {
70        cov_mark::hit!(add_explicit_type_not_applicable_if_ty_not_inferred);
71        return None;
72    }
73
74    let inferred_type = ty.display_source_code(ctx.db(), module.into(), false).ok()?;
75    acc.add(
76        AssistId::refactor_rewrite("add_explicit_type"),
77        format!("Insert explicit type `{inferred_type}`"),
78        pat_range,
79        |builder| match ascribed_ty {
80            Some(ascribed_ty) => {
81                builder.replace(ascribed_ty.syntax().text_range(), inferred_type);
82            }
83            None => {
84                builder.insert(pat_range.end(), format!(": {inferred_type}"));
85            }
86        },
87    )
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
95
96    #[test]
97    fn add_explicit_type_target() {
98        check_assist_target(add_explicit_type, r#"fn f() { let a$0 = 1; }"#, "a");
99    }
100
101    #[test]
102    fn add_explicit_type_simple() {
103        check_assist(
104            add_explicit_type,
105            r#"fn f() { let a$0 = 1; }"#,
106            r#"fn f() { let a: i32 = 1; }"#,
107        );
108    }
109
110    #[test]
111    fn add_explicit_type_simple_on_infer_ty() {
112        check_assist(
113            add_explicit_type,
114            r#"fn f() { let a$0: _ = 1; }"#,
115            r#"fn f() { let a: i32 = 1; }"#,
116        );
117    }
118
119    #[test]
120    fn add_explicit_type_simple_nested_infer_ty() {
121        check_assist(
122            add_explicit_type,
123            r#"
124//- minicore: option
125fn f() {
126    let a$0: Option<_> = Option::Some(1);
127}
128"#,
129            r#"
130fn f() {
131    let a: Option<i32> = Option::Some(1);
132}
133"#,
134        );
135    }
136
137    #[test]
138    fn add_explicit_type_macro_call_expr() {
139        check_assist(
140            add_explicit_type,
141            r"macro_rules! v { () => {0u64} } fn f() { let a$0 = v!(); }",
142            r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }",
143        );
144    }
145
146    #[test]
147    fn add_explicit_type_not_applicable_for_fully_unresolved() {
148        cov_mark::check!(add_explicit_type_not_applicable_if_ty_not_inferred);
149        check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0 = None; }"#);
150    }
151
152    #[test]
153    fn add_explicit_type_applicable_for_partially_unresolved() {
154        check_assist(
155            add_explicit_type,
156            r#"
157        struct Vec<T, V> { t: T, v: V }
158        impl<T> Vec<T, Vec<ZZZ, i32>> {
159            fn new() -> Self {
160                panic!()
161            }
162        }
163        fn f() { let a$0 = Vec::new(); }"#,
164            r#"
165        struct Vec<T, V> { t: T, v: V }
166        impl<T> Vec<T, Vec<ZZZ, i32>> {
167            fn new() -> Self {
168                panic!()
169            }
170        }
171        fn f() { let a: Vec<_, Vec<_, i32>> = Vec::new(); }"#,
172        );
173    }
174
175    #[test]
176    fn add_explicit_type_not_applicable_closure_expr() {
177        check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0 = || {}; }"#);
178    }
179
180    #[test]
181    fn add_explicit_type_not_applicable_ty_already_specified() {
182        cov_mark::check!(add_explicit_type_not_applicable_if_ty_already_specified);
183        check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0: i32 = 1; }"#);
184    }
185
186    #[test]
187    fn add_explicit_type_not_applicable_cursor_after_equals_of_let() {
188        cov_mark::check!(add_explicit_type_not_applicable_if_cursor_after_equals);
189        check_assist_not_applicable(
190            add_explicit_type,
191            r#"fn f() {let a =$0 match 1 {2 => 3, 3 => 5};}"#,
192        )
193    }
194
195    /// https://github.com/rust-lang/rust-analyzer/issues/2922
196    #[test]
197    fn regression_issue_2922() {
198        check_assist(
199            add_explicit_type,
200            r#"
201fn main() {
202    let $0v = [0.0; 2];
203}
204"#,
205            r#"
206fn main() {
207    let v: [f64; 2] = [0.0; 2];
208}
209"#,
210        );
211        check_assist_not_applicable(
212            add_explicit_type,
213            r#"
214fn main() {
215    let $0l = [0.0; unresolved_function(5)];
216}
217"#,
218        );
219    }
220
221    #[test]
222    fn default_generics_should_not_be_added() {
223        check_assist(
224            add_explicit_type,
225            r#"
226struct Test<K, T = u8> { k: K, t: T }
227
228fn main() {
229    let test$0 = Test { t: 23u8, k: 33 };
230}
231"#,
232            r#"
233struct Test<K, T = u8> { k: K, t: T }
234
235fn main() {
236    let test: Test<i32> = Test { t: 23u8, k: 33 };
237}
238"#,
239        );
240    }
241
242    #[test]
243    fn type_should_be_added_after_pattern() {
244        // LetStmt = Attr* 'let' Pat (':' Type)? '=' initializer:Expr ';'
245        check_assist(
246            add_explicit_type,
247            r#"
248fn main() {
249    let $0test @ () = ();
250}
251"#,
252            r#"
253fn main() {
254    let test @ (): () = ();
255}
256"#,
257        );
258    }
259
260    #[test]
261    fn add_explicit_type_inserts_coercions() {
262        check_assist(
263            add_explicit_type,
264            r#"
265//- minicore: coerce_unsized
266fn f() {
267    let $0x: *const [_] = &[3];
268}
269"#,
270            r#"
271fn f() {
272    let x: *const [i32] = &[3];
273}
274"#,
275        );
276    }
277
278    #[test]
279    fn add_explicit_type_not_applicable_fn_param() {
280        cov_mark::check!(add_explicit_type_not_applicable_in_fn_param);
281        check_assist_not_applicable(add_explicit_type, r#"fn f(x$0: ()) {}"#);
282    }
283
284    #[test]
285    fn add_explicit_type_ascribes_closure_param() {
286        check_assist(
287            add_explicit_type,
288            r#"
289fn f() {
290    |y$0| {
291        let x: i32 = y;
292    };
293}
294"#,
295            r#"
296fn f() {
297    |y: i32| {
298        let x: i32 = y;
299    };
300}
301"#,
302        );
303
304        check_assist(
305            add_explicit_type,
306            r#"
307fn f() {
308    let f: fn(i32) = |y$0| {};
309}
310"#,
311            r#"
312fn f() {
313    let f: fn(i32) = |y: i32| {};
314}
315"#,
316        );
317    }
318
319    #[test]
320    fn add_explicit_type_ascribes_closure_param_already_ascribed() {
321        check_assist(
322            add_explicit_type,
323            r#"
324//- minicore: option
325fn f() {
326    |mut y$0: Option<_>| {
327        y = Some(3);
328    };
329}
330"#,
331            r#"
332fn f() {
333    |mut y: Option<i32>| {
334        y = Some(3);
335    };
336}
337"#,
338        );
339    }
340}