]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/add_explicit_type.rs
36589203d5a97b2f894aa7f77261dfbd52ed8ffe
[rust.git] / crates / ide_assists / src / handlers / add_explicit_type.rs
1 use hir::HirDisplay;
2 use syntax::{
3     ast::{self, AstNode, LetStmt, NameOwner},
4     TextRange,
5 };
6
7 use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9 // Assist: add_explicit_type
10 //
11 // Specify type for a let binding.
12 //
13 // ```
14 // fn main() {
15 //     let x$0 = 92;
16 // }
17 // ```
18 // ->
19 // ```
20 // fn main() {
21 //     let x: i32 = 92;
22 // }
23 // ```
24 pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25     let let_stmt = ctx.find_node_at_offset::<LetStmt>()?;
26     let module = ctx.sema.scope(let_stmt.syntax()).module()?;
27     let expr = let_stmt.initializer()?;
28     // Must be a binding
29     let pat = match let_stmt.pat()? {
30         ast::Pat::IdentPat(bind_pat) => bind_pat,
31         _ => return None,
32     };
33     let pat_range = pat.syntax().text_range();
34     // The binding must have a name
35     let name = pat.name()?;
36     let name_range = name.syntax().text_range();
37
38     // Assist should only be applicable if cursor is between 'let' and '='
39     let cursor_in_range = {
40         let stmt_range = let_stmt.syntax().text_range();
41         let eq_range = let_stmt.eq_token()?.text_range();
42         let let_range = TextRange::new(stmt_range.start(), eq_range.start());
43         let_range.contains_range(ctx.frange.range)
44     };
45     if !cursor_in_range {
46         cov_mark::hit!(add_explicit_type_not_applicable_if_cursor_after_equals);
47         return None;
48     }
49
50     // Assist not applicable if the type has already been specified
51     // and it has no placeholders
52     let ascribed_ty = let_stmt.ty();
53     if let Some(ty) = &ascribed_ty {
54         if ty.syntax().descendants().find_map(ast::InferType::cast).is_none() {
55             cov_mark::hit!(add_explicit_type_not_applicable_if_ty_already_specified);
56             return None;
57         }
58     }
59
60     // Infer type
61     let ty = ctx.sema.type_of_expr(&expr)?;
62     if ty.contains_unknown() || ty.is_closure() {
63         cov_mark::hit!(add_explicit_type_not_applicable_if_ty_not_inferred);
64         return None;
65     }
66
67     let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
68     acc.add(
69         AssistId("add_explicit_type", AssistKind::RefactorRewrite),
70         format!("Insert explicit type `{}`", inferred_type),
71         pat_range,
72         |builder| match ascribed_ty {
73             Some(ascribed_ty) => {
74                 builder.replace(ascribed_ty.syntax().text_range(), inferred_type);
75             }
76             None => {
77                 builder.insert(name_range.end(), format!(": {}", inferred_type));
78             }
79         },
80     )
81 }
82
83 #[cfg(test)]
84 mod tests {
85     use super::*;
86
87     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
88
89     #[test]
90     fn add_explicit_type_target() {
91         check_assist_target(add_explicit_type, r#"fn f() { let a$0 = 1; }"#, "a");
92     }
93
94     #[test]
95     fn add_explicit_type_works_for_simple_expr() {
96         check_assist(
97             add_explicit_type,
98             r#"fn f() { let a$0 = 1; }"#,
99             r#"fn f() { let a: i32 = 1; }"#,
100         );
101     }
102
103     #[test]
104     fn add_explicit_type_works_for_underscore() {
105         check_assist(
106             add_explicit_type,
107             r#"fn f() { let a$0: _ = 1; }"#,
108             r#"fn f() { let a: i32 = 1; }"#,
109         );
110     }
111
112     #[test]
113     fn add_explicit_type_works_for_nested_underscore() {
114         check_assist(
115             add_explicit_type,
116             r#"
117 enum Option<T> { Some(T), None }
118
119 fn f() {
120     let a$0: Option<_> = Option::Some(1);
121 }
122 "#,
123             r#"
124 enum Option<T> { Some(T), None }
125
126 fn f() {
127     let a: Option<i32> = Option::Some(1);
128 }
129 "#,
130         );
131     }
132
133     #[test]
134     fn add_explicit_type_works_for_macro_call() {
135         check_assist(
136             add_explicit_type,
137             r"macro_rules! v { () => {0u64} } fn f() { let a$0 = v!(); }",
138             r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }",
139         );
140     }
141
142     #[test]
143     fn add_explicit_type_works_for_macro_call_recursive() {
144         check_assist(
145             add_explicit_type,
146             r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a$0 = v!(); }"#,
147             r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#,
148         );
149     }
150
151     #[test]
152     fn add_explicit_type_not_applicable_if_ty_not_inferred() {
153         cov_mark::check!(add_explicit_type_not_applicable_if_ty_not_inferred);
154         check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0 = None; }"#);
155     }
156
157     #[test]
158     fn add_explicit_type_not_applicable_if_ty_already_specified() {
159         cov_mark::check!(add_explicit_type_not_applicable_if_ty_already_specified);
160         check_assist_not_applicable(add_explicit_type, r#"fn f() { let a$0: i32 = 1; }"#);
161     }
162
163     #[test]
164     fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() {
165         check_assist_not_applicable(
166             add_explicit_type,
167             r#"fn f() { let a$0: (i32, i32) = (3, 4); }"#,
168         );
169     }
170
171     #[test]
172     fn add_explicit_type_not_applicable_if_cursor_after_equals() {
173         cov_mark::check!(add_explicit_type_not_applicable_if_cursor_after_equals);
174         check_assist_not_applicable(
175             add_explicit_type,
176             r#"fn f() {let a =$0 match 1 {2 => 3, 3 => 5};}"#,
177         )
178     }
179
180     #[test]
181     fn add_explicit_type_not_applicable_if_cursor_before_let() {
182         check_assist_not_applicable(
183             add_explicit_type,
184             r#"fn f() $0{let a = match 1 {2 => 3, 3 => 5};}"#,
185         )
186     }
187
188     #[test]
189     fn closure_parameters_are_not_added() {
190         check_assist_not_applicable(
191             add_explicit_type,
192             r#"
193 fn main() {
194     let multiply_by_two$0 = |i| i * 3;
195     let six = multiply_by_two(2);
196 }
197 "#,
198         )
199     }
200
201     /// https://github.com/rust-analyzer/rust-analyzer/issues/2922
202     #[test]
203     fn regression_issue_2922() {
204         check_assist(
205             add_explicit_type,
206             r#"
207 fn main() {
208     let $0v = [0.0; 2];
209 }
210 "#,
211             r#"
212 fn main() {
213     let v: [f64; 2] = [0.0; 2];
214 }
215 "#,
216         );
217         // note: this may break later if we add more consteval. it just needs to be something that our
218         // consteval engine doesn't understand
219         check_assist_not_applicable(
220             add_explicit_type,
221             r#"
222 fn main() {
223     let $0l = [0.0; 2+2];
224 }
225 "#,
226         );
227     }
228
229     #[test]
230     fn default_generics_should_not_be_added() {
231         check_assist(
232             add_explicit_type,
233             r#"
234 struct Test<K, T = u8> { k: K, t: T }
235
236 fn main() {
237     let test$0 = Test { t: 23u8, k: 33 };
238 }
239 "#,
240             r#"
241 struct Test<K, T = u8> { k: K, t: T }
242
243 fn main() {
244     let test: Test<i32> = Test { t: 23u8, k: 33 };
245 }
246 "#,
247         );
248     }
249 }