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        // note: this may break later if we add more consteval. it just needs to be something that our
212        // consteval engine doesn't understand
213        check_assist_not_applicable(
214            add_explicit_type,
215            r#"
216fn main() {
217    let $0l = [0.0; unresolved_function(5)];
218}
219"#,
220        );
221    }
222
223    #[test]
224    fn default_generics_should_not_be_added() {
225        check_assist(
226            add_explicit_type,
227            r#"
228struct Test<K, T = u8> { k: K, t: T }
229
230fn main() {
231    let test$0 = Test { t: 23u8, k: 33 };
232}
233"#,
234            r#"
235struct Test<K, T = u8> { k: K, t: T }
236
237fn main() {
238    let test: Test<i32> = Test { t: 23u8, k: 33 };
239}
240"#,
241        );
242    }
243
244    #[test]
245    fn type_should_be_added_after_pattern() {
246        // LetStmt = Attr* 'let' Pat (':' Type)? '=' initializer:Expr ';'
247        check_assist(
248            add_explicit_type,
249            r#"
250fn main() {
251    let $0test @ () = ();
252}
253"#,
254            r#"
255fn main() {
256    let test @ (): () = ();
257}
258"#,
259        );
260    }
261
262    #[test]
263    fn add_explicit_type_inserts_coercions() {
264        check_assist(
265            add_explicit_type,
266            r#"
267//- minicore: coerce_unsized
268fn f() {
269    let $0x: *const [_] = &[3];
270}
271"#,
272            r#"
273fn f() {
274    let x: *const [i32] = &[3];
275}
276"#,
277        );
278    }
279
280    #[test]
281    fn add_explicit_type_not_applicable_fn_param() {
282        cov_mark::check!(add_explicit_type_not_applicable_in_fn_param);
283        check_assist_not_applicable(add_explicit_type, r#"fn f(x$0: ()) {}"#);
284    }
285
286    #[test]
287    fn add_explicit_type_ascribes_closure_param() {
288        check_assist(
289            add_explicit_type,
290            r#"
291fn f() {
292    |y$0| {
293        let x: i32 = y;
294    };
295}
296"#,
297            r#"
298fn f() {
299    |y: i32| {
300        let x: i32 = y;
301    };
302}
303"#,
304        );
305
306        check_assist(
307            add_explicit_type,
308            r#"
309fn f() {
310    let f: fn(i32) = |y$0| {};
311}
312"#,
313            r#"
314fn f() {
315    let f: fn(i32) = |y: i32| {};
316}
317"#,
318        );
319    }
320
321    #[test]
322    fn add_explicit_type_ascribes_closure_param_already_ascribed() {
323        check_assist(
324            add_explicit_type,
325            r#"
326//- minicore: option
327fn f() {
328    |mut y$0: Option<_>| {
329        y = Some(3);
330    };
331}
332"#,
333            r#"
334fn f() {
335    |mut y: Option<i32>| {
336        y = Some(3);
337    };
338}
339"#,
340        );
341    }
342}