aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/add_explicit_type.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/add_explicit_type.rs')
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs178
1 files changed, 178 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
new file mode 100644
index 000000000..2cb9d2f48
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -0,0 +1,178 @@
1use hir::HirDisplay;
2use ra_syntax::{
3 ast::{self, AstNode, LetStmt, NameOwner, TypeAscriptionOwner},
4 TextRange,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
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// ```
24pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let expr = stmt.initializer()?;
27 let pat = stmt.pat()?;
28 // Must be a binding
29 let pat = match pat {
30 ast::Pat::BindPat(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 = stmt.syntax().text_range();
38 let eq_range = stmt.eq_token()?.text_range();
39 // Assist should only be applicable if cursor is between 'let' and '='
40 let let_range = TextRange::from_to(stmt_range.start(), eq_range.start());
41 let cursor_in_range = ctx.frange.range.is_subrange(&let_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 = stmt.ascribed_type();
48 if let Some(ref ty) = ascribed_ty {
49 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() {
50 return None;
51 }
52 }
53 // Infer type
54 let db = ctx.db;
55 let analyzer = ctx.source_analyzer(stmt.syntax(), None);
56 let ty = analyzer.type_of(db, &expr)?;
57 // Assist not applicable if the type is unknown
58 if ty.contains_unknown() {
59 return None;
60 }
61
62 ctx.add_assist(
63 AssistId("add_explicit_type"),
64 format!("Insert explicit type '{}'", ty.display(db)),
65 |edit| {
66 edit.target(pat_range);
67 if let Some(ascribed_ty) = ascribed_ty {
68 edit.replace(ascribed_ty.syntax().text_range(), format!("{}", ty.display(db)));
69 } else {
70 edit.insert(name_range.end(), format!(": {}", ty.display(db)));
71 }
72 },
73 )
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 use crate::helpers::{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(
90 add_explicit_type,
91 "fn f() { let a<|> = 1; }",
92 "fn f() { let a<|>: i32 = 1; }",
93 );
94 }
95
96 #[test]
97 fn add_explicit_type_works_for_underscore() {
98 check_assist(
99 add_explicit_type,
100 "fn f() { let a<|>: _ = 1; }",
101 "fn f() { let a<|>: i32 = 1; }",
102 );
103 }
104
105 #[test]
106 fn add_explicit_type_works_for_nested_underscore() {
107 check_assist(
108 add_explicit_type,
109 r#"
110 enum Option<T> {
111 Some(T),
112 None
113 }
114
115 fn f() {
116 let a<|>: Option<_> = Option::Some(1);
117 }"#,
118 r#"
119 enum Option<T> {
120 Some(T),
121 None
122 }
123
124 fn f() {
125 let a<|>: Option<i32> = Option::Some(1);
126 }"#,
127 );
128 }
129
130 #[test]
131 fn add_explicit_type_works_for_macro_call() {
132 check_assist(
133 add_explicit_type,
134 "macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }",
135 "macro_rules! v { () => {0u64} } fn f() { let a<|>: u64 = v!(); }",
136 );
137 }
138
139 #[test]
140 fn add_explicit_type_works_for_macro_call_recursive() {
141 check_assist(
142 add_explicit_type,
143 "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }",
144 "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|>: u64 = v!(); }",
145 );
146 }
147
148 #[test]
149 fn add_explicit_type_not_applicable_if_ty_not_inferred() {
150 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }");
151 }
152
153 #[test]
154 fn add_explicit_type_not_applicable_if_ty_already_specified() {
155 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }");
156 }
157
158 #[test]
159 fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() {
160 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }");
161 }
162
163 #[test]
164 fn add_explicit_type_not_applicable_if_cursor_after_equals() {
165 check_assist_not_applicable(
166 add_explicit_type,
167 "fn f() {let a =<|> match 1 {2 => 3, 3 => 5};}",
168 )
169 }
170
171 #[test]
172 fn add_explicit_type_not_applicable_if_cursor_before_let() {
173 check_assist_not_applicable(
174 add_explicit_type,
175 "fn f() <|>{let a = match 1 {2 => 3, 3 => 5};}",
176 )
177 }
178}