aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src')
-rw-r--r--crates/assists/src/handlers/add_lifetime_to_type.rs228
-rw-r--r--crates/assists/src/handlers/fill_match_arms.rs21
-rw-r--r--crates/assists/src/handlers/replace_if_let_with_match.rs73
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs19
-rw-r--r--crates/assists/src/utils.rs19
6 files changed, 340 insertions, 22 deletions
diff --git a/crates/assists/src/handlers/add_lifetime_to_type.rs b/crates/assists/src/handlers/add_lifetime_to_type.rs
new file mode 100644
index 000000000..c1603e972
--- /dev/null
+++ b/crates/assists/src/handlers/add_lifetime_to_type.rs
@@ -0,0 +1,228 @@
1use ast::FieldList;
2use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner, RefType, Type};
3
4use crate::{AssistContext, AssistId, AssistKind, Assists};
5
6// Assist: add_lifetime_to_type
7//
8// Adds a new lifetime to a struct, enum or union.
9//
10// ```
11// struct Point {
12// x: &$0u32,
13// y: u32,
14// }
15// ```
16// ->
17// ```
18// struct Point<'a> {
19// x: &'a u32,
20// y: u32,
21// }
22// ```
23pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let ref_type_focused = ctx.find_node_at_offset::<ast::RefType>()?;
25 if ref_type_focused.lifetime().is_some() {
26 return None;
27 }
28
29 let node = ctx.find_node_at_offset::<ast::AdtDef>()?;
30 let has_lifetime = node
31 .generic_param_list()
32 .map(|gen_list| gen_list.lifetime_params().count() > 0)
33 .unwrap_or_default();
34
35 if has_lifetime {
36 return None;
37 }
38
39 let ref_types = fetch_borrowed_types(&node)?;
40 let target = node.syntax().text_range();
41
42 acc.add(
43 AssistId("add_lifetime_to_type", AssistKind::Generate),
44 "Add lifetime`",
45 target,
46 |builder| {
47 match node.generic_param_list() {
48 Some(gen_param) => {
49 if let Some(left_angle) = gen_param.l_angle_token() {
50 builder.insert(left_angle.text_range().end(), "'a, ");
51 }
52 }
53 None => {
54 if let Some(name) = node.name() {
55 builder.insert(name.syntax().text_range().end(), "<'a>");
56 }
57 }
58 }
59
60 for ref_type in ref_types {
61 if let Some(amp_token) = ref_type.amp_token() {
62 builder.insert(amp_token.text_range().end(), "'a ");
63 }
64 }
65 },
66 )
67}
68
69fn fetch_borrowed_types(node: &ast::AdtDef) -> Option<Vec<RefType>> {
70 let ref_types: Vec<RefType> = match node {
71 ast::AdtDef::Enum(enum_) => {
72 let variant_list = enum_.variant_list()?;
73 variant_list
74 .variants()
75 .filter_map(|variant| {
76 let field_list = variant.field_list()?;
77
78 find_ref_types_from_field_list(&field_list)
79 })
80 .flatten()
81 .collect()
82 }
83 ast::AdtDef::Struct(strukt) => {
84 let field_list = strukt.field_list()?;
85 find_ref_types_from_field_list(&field_list)?
86 }
87 ast::AdtDef::Union(un) => {
88 let record_field_list = un.record_field_list()?;
89 record_field_list
90 .fields()
91 .filter_map(|r_field| {
92 if let Type::RefType(ref_type) = r_field.ty()? {
93 if ref_type.lifetime().is_none() {
94 return Some(ref_type);
95 }
96 }
97
98 None
99 })
100 .collect()
101 }
102 };
103
104 if ref_types.is_empty() {
105 None
106 } else {
107 Some(ref_types)
108 }
109}
110
111fn find_ref_types_from_field_list(field_list: &FieldList) -> Option<Vec<RefType>> {
112 let ref_types: Vec<RefType> = match field_list {
113 ast::FieldList::RecordFieldList(record_list) => record_list
114 .fields()
115 .filter_map(|f| {
116 if let Type::RefType(ref_type) = f.ty()? {
117 if ref_type.lifetime().is_none() {
118 return Some(ref_type);
119 }
120 }
121
122 None
123 })
124 .collect(),
125 ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list
126 .fields()
127 .filter_map(|f| {
128 if let Type::RefType(ref_type) = f.ty()? {
129 if ref_type.lifetime().is_none() {
130 return Some(ref_type);
131 }
132 }
133
134 None
135 })
136 .collect(),
137 };
138
139 if ref_types.is_empty() {
140 None
141 } else {
142 Some(ref_types)
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use crate::tests::{check_assist, check_assist_not_applicable};
149
150 use super::*;
151
152 #[test]
153 fn add_lifetime_to_struct() {
154 check_assist(
155 add_lifetime_to_type,
156 "struct Foo { a: &$0i32 }",
157 "struct Foo<'a> { a: &'a i32 }",
158 );
159
160 check_assist(
161 add_lifetime_to_type,
162 "struct Foo { a: &$0i32, b: &usize }",
163 "struct Foo<'a> { a: &'a i32, b: &'a usize }",
164 );
165
166 check_assist(
167 add_lifetime_to_type,
168 "struct Foo { a: &$0i32, b: usize }",
169 "struct Foo<'a> { a: &'a i32, b: usize }",
170 );
171
172 check_assist(
173 add_lifetime_to_type,
174 "struct Foo<T> { a: &$0T, b: usize }",
175 "struct Foo<'a, T> { a: &'a T, b: usize }",
176 );
177
178 check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a> { a: &$0'a i32 }");
179 check_assist_not_applicable(add_lifetime_to_type, "struct Foo { a: &'a$0 i32 }");
180 }
181
182 #[test]
183 fn add_lifetime_to_enum() {
184 check_assist(
185 add_lifetime_to_type,
186 "enum Foo { Bar { a: i32 }, Other, Tuple(u32, &$0u32)}",
187 "enum Foo<'a> { Bar { a: i32 }, Other, Tuple(u32, &'a u32)}",
188 );
189
190 check_assist(
191 add_lifetime_to_type,
192 "enum Foo { Bar { a: &$0i32 }}",
193 "enum Foo<'a> { Bar { a: &'a i32 }}",
194 );
195
196 check_assist(
197 add_lifetime_to_type,
198 "enum Foo<T> { Bar { a: &$0i32, b: &T }}",
199 "enum Foo<'a, T> { Bar { a: &'a i32, b: &'a T }}",
200 );
201
202 check_assist_not_applicable(add_lifetime_to_type, "enum Foo<'a> { Bar { a: &$0'a i32 }}");
203 check_assist_not_applicable(add_lifetime_to_type, "enum Foo { Bar, $0Misc }");
204 }
205
206 #[test]
207 fn add_lifetime_to_union() {
208 check_assist(
209 add_lifetime_to_type,
210 "union Foo { a: &$0i32 }",
211 "union Foo<'a> { a: &'a i32 }",
212 );
213
214 check_assist(
215 add_lifetime_to_type,
216 "union Foo { a: &$0i32, b: &usize }",
217 "union Foo<'a> { a: &'a i32, b: &'a usize }",
218 );
219
220 check_assist(
221 add_lifetime_to_type,
222 "union Foo<T> { a: &$0T, b: usize }",
223 "union Foo<'a, T> { a: &'a T, b: usize }",
224 );
225
226 check_assist_not_applicable(add_lifetime_to_type, "struct Foo<'a> { a: &'a $0i32 }");
227 }
228}
diff --git a/crates/assists/src/handlers/fill_match_arms.rs b/crates/assists/src/handlers/fill_match_arms.rs
index da47187e4..7663d211d 100644
--- a/crates/assists/src/handlers/fill_match_arms.rs
+++ b/crates/assists/src/handlers/fill_match_arms.rs
@@ -8,7 +8,7 @@ use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
8use test_utils::mark; 8use test_utils::mark;
9 9
10use crate::{ 10use crate::{
11 utils::{render_snippet, Cursor}, 11 utils::{does_pat_match_variant, render_snippet, Cursor},
12 AssistContext, AssistId, AssistKind, Assists, 12 AssistContext, AssistId, AssistKind, Assists,
13}; 13};
14 14
@@ -147,25 +147,6 @@ fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
147 }) 147 })
148} 148}
149 149
150fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
151 let first_node_text = |pat: &Pat| pat.syntax().first_child().map(|node| node.text());
152
153 let pat_head = match pat {
154 Pat::IdentPat(bind_pat) => {
155 if let Some(p) = bind_pat.pat() {
156 first_node_text(&p)
157 } else {
158 return false;
159 }
160 }
161 pat => first_node_text(pat),
162 };
163
164 let var_head = first_node_text(var);
165
166 pat_head == var_head
167}
168
169fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { 150fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
170 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() { 151 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
171 Some(Adt::Enum(e)) => Some(e), 152 Some(Adt::Enum(e)) => Some(e),
diff --git a/crates/assists/src/handlers/replace_if_let_with_match.rs b/crates/assists/src/handlers/replace_if_let_with_match.rs
index aee3397ab..aee880625 100644
--- a/crates/assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/assists/src/handlers/replace_if_let_with_match.rs
@@ -10,7 +10,10 @@ use syntax::{
10 AstNode, 10 AstNode,
11}; 11};
12 12
13use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists}; 13use crate::{
14 utils::{does_pat_match_variant, unwrap_trivial_block},
15 AssistContext, AssistId, AssistKind, Assists,
16};
14 17
15// Assist: replace_if_let_with_match 18// Assist: replace_if_let_with_match
16// 19//
@@ -66,7 +69,13 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
66 .sema 69 .sema
67 .type_of_pat(&pat) 70 .type_of_pat(&pat)
68 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) 71 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
69 .map(|it| it.sad_pattern()) 72 .map(|it| {
73 if does_pat_match_variant(&pat, &it.sad_pattern()) {
74 it.happy_pattern()
75 } else {
76 it.sad_pattern()
77 }
78 })
70 .unwrap_or_else(|| make::wildcard_pat().into()); 79 .unwrap_or_else(|| make::wildcard_pat().into());
71 let else_expr = unwrap_trivial_block(else_block); 80 let else_expr = unwrap_trivial_block(else_block);
72 make::match_arm(vec![pattern], else_expr) 81 make::match_arm(vec![pattern], else_expr)
@@ -279,6 +288,36 @@ fn foo(x: Option<i32>) {
279 } 288 }
280 289
281 #[test] 290 #[test]
291 fn special_case_inverted_option() {
292 check_assist(
293 replace_if_let_with_match,
294 r#"
295enum Option<T> { Some(T), None }
296use Option::*;
297
298fn foo(x: Option<i32>) {
299 $0if let None = x {
300 println!("none")
301 } else {
302 println!("some")
303 }
304}
305 "#,
306 r#"
307enum Option<T> { Some(T), None }
308use Option::*;
309
310fn foo(x: Option<i32>) {
311 match x {
312 None => println!("none"),
313 Some(_) => println!("some"),
314 }
315}
316 "#,
317 );
318 }
319
320 #[test]
282 fn special_case_result() { 321 fn special_case_result() {
283 check_assist( 322 check_assist(
284 replace_if_let_with_match, 323 replace_if_let_with_match,
@@ -309,6 +348,36 @@ fn foo(x: Result<i32, ()>) {
309 } 348 }
310 349
311 #[test] 350 #[test]
351 fn special_case_inverted_result() {
352 check_assist(
353 replace_if_let_with_match,
354 r#"
355enum Result<T, E> { Ok(T), Err(E) }
356use Result::*;
357
358fn foo(x: Result<i32, ()>) {
359 $0if let Err(x) = x {
360 println!("{}", x)
361 } else {
362 println!("ok")
363 }
364}
365 "#,
366 r#"
367enum Result<T, E> { Ok(T), Err(E) }
368use Result::*;
369
370fn foo(x: Result<i32, ()>) {
371 match x {
372 Err(x) => println!("{}", x),
373 Ok(_) => println!("ok"),
374 }
375}
376 "#,
377 );
378 }
379
380 #[test]
312 fn nested_indent() { 381 fn nested_indent() {
313 check_assist( 382 check_assist(
314 replace_if_let_with_match, 383 replace_if_let_with_match,
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 14178a651..559b9651e 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -108,6 +108,7 @@ mod handlers {
108 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; 108 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
109 109
110 mod add_explicit_type; 110 mod add_explicit_type;
111 mod add_lifetime_to_type;
111 mod add_missing_impl_members; 112 mod add_missing_impl_members;
112 mod add_turbo_fish; 113 mod add_turbo_fish;
113 mod apply_demorgan; 114 mod apply_demorgan;
@@ -164,6 +165,7 @@ mod handlers {
164 &[ 165 &[
165 // These are alphabetic for the foolish consistency 166 // These are alphabetic for the foolish consistency
166 add_explicit_type::add_explicit_type, 167 add_explicit_type::add_explicit_type,
168 add_lifetime_to_type::add_lifetime_to_type,
167 add_turbo_fish::add_turbo_fish, 169 add_turbo_fish::add_turbo_fish,
168 apply_demorgan::apply_demorgan, 170 apply_demorgan::apply_demorgan,
169 auto_import::auto_import, 171 auto_import::auto_import,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index d48d063b4..9aa807f10 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -104,6 +104,25 @@ impl Trait<u32> for () {
104} 104}
105 105
106#[test] 106#[test]
107fn doctest_add_lifetime_to_type() {
108 check_doc_test(
109 "add_lifetime_to_type",
110 r#####"
111struct Point {
112 x: &$0u32,
113 y: u32,
114}
115"#####,
116 r#####"
117struct Point<'a> {
118 x: &'a u32,
119 y: u32,
120}
121"#####,
122 )
123}
124
125#[test]
107fn doctest_add_turbo_fish() { 126fn doctest_add_turbo_fish() {
108 check_doc_test( 127 check_doc_test(
109 "add_turbo_fish", 128 "add_turbo_fish",
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 44c35bafa..4e762e18b 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -248,3 +248,22 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
248pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { 248pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
249 [Direction::Next, Direction::Prev].iter().copied() 249 [Direction::Next, Direction::Prev].iter().copied()
250} 250}
251
252pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool {
253 let first_node_text = |pat: &ast::Pat| pat.syntax().first_child().map(|node| node.text());
254
255 let pat_head = match pat {
256 ast::Pat::IdentPat(bind_pat) => {
257 if let Some(p) = bind_pat.pat() {
258 first_node_text(&p)
259 } else {
260 return pat.syntax().text() == var.syntax().text();
261 }
262 }
263 pat => first_node_text(pat),
264 };
265
266 let var_head = first_node_text(var);
267
268 pat_head == var_head
269}