diff options
Diffstat (limited to 'crates/assists/src/handlers/add_explicit_type.rs')
-rw-r--r-- | crates/assists/src/handlers/add_explicit_type.rs | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/add_explicit_type.rs b/crates/assists/src/handlers/add_explicit_type.rs new file mode 100644 index 000000000..563cbf505 --- /dev/null +++ b/crates/assists/src/handlers/add_explicit_type.rs | |||
@@ -0,0 +1,211 @@ | |||
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<|> = 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 | let stmt_range = let_stmt.syntax().text_range(); | ||
38 | let eq_range = let_stmt.eq_token()?.text_range(); | ||
39 | // Assist should only be applicable if cursor is between 'let' and '=' | ||
40 | let let_range = TextRange::new(stmt_range.start(), eq_range.start()); | ||
41 | let cursor_in_range = let_range.contains_range(ctx.frange.range); | ||
42 | if !cursor_in_range { | ||
43 | return None; | ||
44 | } | ||
45 | // Assist not applicable if the type has already been specified | ||
46 | // and it has no placeholders | ||
47 | let ascribed_ty = let_stmt.ty(); | ||
48 | if let Some(ty) = &ascribed_ty { | ||
49 | if ty.syntax().descendants().find_map(ast::InferType::cast).is_none() { | ||
50 | return None; | ||
51 | } | ||
52 | } | ||
53 | // Infer type | ||
54 | let ty = ctx.sema.type_of_expr(&expr)?; | ||
55 | |||
56 | if ty.contains_unknown() || ty.is_closure() { | ||
57 | return None; | ||
58 | } | ||
59 | |||
60 | let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?; | ||
61 | acc.add( | ||
62 | AssistId("add_explicit_type", AssistKind::RefactorRewrite), | ||
63 | format!("Insert explicit type `{}`", inferred_type), | ||
64 | pat_range, | ||
65 | |builder| match ascribed_ty { | ||
66 | Some(ascribed_ty) => { | ||
67 | builder.replace(ascribed_ty.syntax().text_range(), inferred_type); | ||
68 | } | ||
69 | None => { | ||
70 | builder.insert(name_range.end(), format!(": {}", inferred_type)); | ||
71 | } | ||
72 | }, | ||
73 | ) | ||
74 | } | ||
75 | |||
76 | #[cfg(test)] | ||
77 | mod tests { | ||
78 | use super::*; | ||
79 | |||
80 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
81 | |||
82 | #[test] | ||
83 | fn add_explicit_type_target() { | ||
84 | check_assist_target(add_explicit_type, "fn f() { let a<|> = 1; }", "a"); | ||
85 | } | ||
86 | |||
87 | #[test] | ||
88 | fn add_explicit_type_works_for_simple_expr() { | ||
89 | check_assist(add_explicit_type, "fn f() { let a<|> = 1; }", "fn f() { let a: i32 = 1; }"); | ||
90 | } | ||
91 | |||
92 | #[test] | ||
93 | fn add_explicit_type_works_for_underscore() { | ||
94 | check_assist( | ||
95 | add_explicit_type, | ||
96 | "fn f() { let a<|>: _ = 1; }", | ||
97 | "fn f() { let a: i32 = 1; }", | ||
98 | ); | ||
99 | } | ||
100 | |||
101 | #[test] | ||
102 | fn add_explicit_type_works_for_nested_underscore() { | ||
103 | check_assist( | ||
104 | add_explicit_type, | ||
105 | r#" | ||
106 | enum Option<T> { | ||
107 | Some(T), | ||
108 | None | ||
109 | } | ||
110 | |||
111 | fn f() { | ||
112 | let a<|>: Option<_> = Option::Some(1); | ||
113 | }"#, | ||
114 | r#" | ||
115 | enum Option<T> { | ||
116 | Some(T), | ||
117 | None | ||
118 | } | ||
119 | |||
120 | fn f() { | ||
121 | let a: Option<i32> = Option::Some(1); | ||
122 | }"#, | ||
123 | ); | ||
124 | } | ||
125 | |||
126 | #[test] | ||
127 | fn add_explicit_type_works_for_macro_call() { | ||
128 | check_assist( | ||
129 | add_explicit_type, | ||
130 | r"macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }", | ||
131 | r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }", | ||
132 | ); | ||
133 | } | ||
134 | |||
135 | #[test] | ||
136 | fn add_explicit_type_works_for_macro_call_recursive() { | ||
137 | check_assist( | ||
138 | add_explicit_type, | ||
139 | r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }"#, | ||
140 | r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#, | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn add_explicit_type_not_applicable_if_ty_not_inferred() { | ||
146 | check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }"); | ||
147 | } | ||
148 | |||
149 | #[test] | ||
150 | fn add_explicit_type_not_applicable_if_ty_already_specified() { | ||
151 | check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }"); | ||
152 | } | ||
153 | |||
154 | #[test] | ||
155 | fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() { | ||
156 | check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }"); | ||
157 | } | ||
158 | |||
159 | #[test] | ||
160 | fn add_explicit_type_not_applicable_if_cursor_after_equals() { | ||
161 | check_assist_not_applicable( | ||
162 | add_explicit_type, | ||
163 | "fn f() {let a =<|> match 1 {2 => 3, 3 => 5};}", | ||
164 | ) | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn add_explicit_type_not_applicable_if_cursor_before_let() { | ||
169 | check_assist_not_applicable( | ||
170 | add_explicit_type, | ||
171 | "fn f() <|>{let a = match 1 {2 => 3, 3 => 5};}", | ||
172 | ) | ||
173 | } | ||
174 | |||
175 | #[test] | ||
176 | fn closure_parameters_are_not_added() { | ||
177 | check_assist_not_applicable( | ||
178 | add_explicit_type, | ||
179 | r#" | ||
180 | fn main() { | ||
181 | let multiply_by_two<|> = |i| i * 3; | ||
182 | let six = multiply_by_two(2); | ||
183 | }"#, | ||
184 | ) | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn default_generics_should_not_be_added() { | ||
189 | check_assist( | ||
190 | add_explicit_type, | ||
191 | r#" | ||
192 | struct Test<K, T = u8> { | ||
193 | k: K, | ||
194 | t: T, | ||
195 | } | ||
196 | |||
197 | fn main() { | ||
198 | let test<|> = Test { t: 23u8, k: 33 }; | ||
199 | }"#, | ||
200 | r#" | ||
201 | struct Test<K, T = u8> { | ||
202 | k: K, | ||
203 | t: T, | ||
204 | } | ||
205 | |||
206 | fn main() { | ||
207 | let test: Test<i32> = Test { t: 23u8, k: 33 }; | ||
208 | }"#, | ||
209 | ); | ||
210 | } | ||
211 | } | ||