aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/src/handlers/add_from_impl_for_enum.rs70
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs78
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs16
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs52
-rw-r--r--crates/ra_assists/src/marks.rs1
-rw-r--r--crates/ra_assists/src/utils.rs110
6 files changed, 237 insertions, 90 deletions
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
index 03806724a..49deb6701 100644
--- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
+++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
@@ -1,11 +1,12 @@
1use ra_ide_db::RootDatabase;
1use ra_syntax::{ 2use ra_syntax::{
2 ast::{self, AstNode, NameOwner}, 3 ast::{self, AstNode, NameOwner},
3 TextSize, 4 TextSize,
4}; 5};
5use stdx::format_to; 6use stdx::format_to;
6 7
7use crate::{Assist, AssistCtx, AssistId}; 8use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId};
8use ra_ide_db::RootDatabase; 9use test_utils::tested_by;
9 10
10// Assist add_from_impl_for_enum 11// Assist add_from_impl_for_enum
11// 12//
@@ -41,7 +42,8 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option<Assist> {
41 _ => return None, 42 _ => return None,
42 }; 43 };
43 44
44 if already_has_from_impl(ctx.sema, &variant) { 45 if existing_from_impl(ctx.sema, &variant).is_some() {
46 tested_by!(test_add_from_impl_already_exists);
45 return None; 47 return None;
46 } 48 }
47 49
@@ -70,41 +72,33 @@ impl From<{0}> for {1} {{
70 ) 72 )
71} 73}
72 74
73fn already_has_from_impl( 75fn existing_from_impl(
74 sema: &'_ hir::Semantics<'_, RootDatabase>, 76 sema: &'_ hir::Semantics<'_, RootDatabase>,
75 variant: &ast::EnumVariant, 77 variant: &ast::EnumVariant,
76) -> bool { 78) -> Option<()> {
77 let scope = sema.scope(&variant.syntax()); 79 let variant = sema.to_def(variant)?;
80 let enum_ = variant.parent_enum(sema.db);
81 let krate = enum_.module(sema.db).krate();
78 82
79 let from_path = ast::make::path_from_text("From"); 83 let from_trait = FamousDefs(sema, krate).core_convert_From()?;
80 let from_hir_path = match hir::Path::from_ast(from_path) {
81 Some(p) => p,
82 None => return false,
83 };
84 let from_trait = match scope.resolve_hir_path(&from_hir_path) {
85 Some(hir::PathResolution::Def(hir::ModuleDef::Trait(t))) => t,
86 _ => return false,
87 };
88 84
89 let e: hir::Enum = match sema.to_def(&variant.parent_enum()) { 85 let enum_type = enum_.ty(sema.db);
90 Some(e) => e,
91 None => return false,
92 };
93 let e_ty = e.ty(sema.db);
94 86
95 let hir_enum_var: hir::EnumVariant = match sema.to_def(variant) { 87 let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
96 Some(ev) => ev,
97 None => return false,
98 };
99 let var_ty = hir_enum_var.fields(sema.db)[0].signature_ty(sema.db);
100 88
101 e_ty.impls_trait(sema.db, from_trait, &[var_ty]) 89 if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
90 Some(())
91 } else {
92 None
93 }
102} 94}
103 95
104#[cfg(test)] 96#[cfg(test)]
105mod tests { 97mod tests {
106 use super::*; 98 use super::*;
99
107 use crate::helpers::{check_assist, check_assist_not_applicable}; 100 use crate::helpers::{check_assist, check_assist_not_applicable};
101 use test_utils::covers;
108 102
109 #[test] 103 #[test]
110 fn test_add_from_impl_for_enum() { 104 fn test_add_from_impl_for_enum() {
@@ -136,36 +130,40 @@ mod tests {
136 ); 130 );
137 } 131 }
138 132
133 fn check_not_applicable(ra_fixture: &str) {
134 let fixture =
135 format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
136 check_assist_not_applicable(add_from_impl_for_enum, &fixture)
137 }
138
139 #[test] 139 #[test]
140 fn test_add_from_impl_no_element() { 140 fn test_add_from_impl_no_element() {
141 check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One }"); 141 check_not_applicable("enum A { <|>One }");
142 } 142 }
143 143
144 #[test] 144 #[test]
145 fn test_add_from_impl_more_than_one_element_in_tuple() { 145 fn test_add_from_impl_more_than_one_element_in_tuple() {
146 check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One(u32, String) }"); 146 check_not_applicable("enum A { <|>One(u32, String) }");
147 } 147 }
148 148
149 #[test] 149 #[test]
150 fn test_add_from_impl_struct_variant() { 150 fn test_add_from_impl_struct_variant() {
151 check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One { x: u32 } }"); 151 check_not_applicable("enum A { <|>One { x: u32 } }");
152 } 152 }
153 153
154 #[test] 154 #[test]
155 fn test_add_from_impl_already_exists() { 155 fn test_add_from_impl_already_exists() {
156 check_assist_not_applicable( 156 covers!(test_add_from_impl_already_exists);
157 add_from_impl_for_enum, 157 check_not_applicable(
158 r#"enum A { <|>One(u32), } 158 r#"
159enum A { <|>One(u32), }
159 160
160impl From<u32> for A { 161impl From<u32> for A {
161 fn from(v: u32) -> Self { 162 fn from(v: u32) -> Self {
162 A::One(v) 163 A::One(v)
163 } 164 }
164} 165}
165 166"#,
166pub trait From<T> {
167 fn from(T) -> Self;
168}"#,
169 ); 167 );
170 } 168 }
171 169
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
index 0a0a88f3d..9841f6980 100644
--- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
@@ -1,11 +1,10 @@
1use ra_fmt::unwrap_trivial_block; 1use ra_fmt::unwrap_trivial_block;
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{self, make}, 3 ast::{self, edit::IndentLevel, make},
4 AstNode, 4 AstNode,
5}; 5};
6 6
7use crate::{Assist, AssistCtx, AssistId}; 7use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
8use ast::edit::IndentLevel;
9 8
10// Assist: replace_if_let_with_match 9// Assist: replace_if_let_with_match
11// 10//
@@ -44,15 +43,21 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
44 ast::ElseBranch::IfExpr(_) => return None, 43 ast::ElseBranch::IfExpr(_) => return None,
45 }; 44 };
46 45
47 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| { 46 let sema = ctx.sema;
47 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| {
48 let match_expr = { 48 let match_expr = {
49 let then_arm = { 49 let then_arm = {
50 let then_expr = unwrap_trivial_block(then_block); 50 let then_expr = unwrap_trivial_block(then_block);
51 make::match_arm(vec![pat], then_expr) 51 make::match_arm(vec![pat.clone()], then_expr)
52 }; 52 };
53 let else_arm = { 53 let else_arm = {
54 let pattern = sema
55 .type_of_pat(&pat)
56 .and_then(|ty| TryEnum::from_ty(sema, &ty))
57 .map(|it| it.sad_pattern())
58 .unwrap_or_else(|| make::placeholder_pat().into());
54 let else_expr = unwrap_trivial_block(else_block); 59 let else_expr = unwrap_trivial_block(else_block);
55 make::match_arm(vec![make::placeholder_pat().into()], else_expr) 60 make::match_arm(vec![pattern], else_expr)
56 }; 61 };
57 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) 62 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
58 }; 63 };
@@ -68,6 +73,7 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
68#[cfg(test)] 73#[cfg(test)]
69mod tests { 74mod tests {
70 use super::*; 75 use super::*;
76
71 use crate::helpers::{check_assist, check_assist_target}; 77 use crate::helpers::{check_assist, check_assist_target};
72 78
73 #[test] 79 #[test]
@@ -145,4 +151,64 @@ impl VariantData {
145 }", 151 }",
146 ); 152 );
147 } 153 }
154
155 #[test]
156 fn special_case_option() {
157 check_assist(
158 replace_if_let_with_match,
159 r#"
160enum Option<T> { Some(T), None }
161use Option::*;
162
163fn foo(x: Option<i32>) {
164 <|>if let Some(x) = x {
165 println!("{}", x)
166 } else {
167 println!("none")
168 }
169}
170 "#,
171 r#"
172enum Option<T> { Some(T), None }
173use Option::*;
174
175fn foo(x: Option<i32>) {
176 <|>match x {
177 Some(x) => println!("{}", x),
178 None => println!("none"),
179 }
180}
181 "#,
182 );
183 }
184
185 #[test]
186 fn special_case_result() {
187 check_assist(
188 replace_if_let_with_match,
189 r#"
190enum Result<T, E> { Ok(T), Err(E) }
191use Result::*;
192
193fn foo(x: Result<i32, ()>) {
194 <|>if let Ok(x) = x {
195 println!("{}", x)
196 } else {
197 println!("none")
198 }
199}
200 "#,
201 r#"
202enum Result<T, E> { Ok(T), Err(E) }
203use Result::*;
204
205fn foo(x: Result<i32, ()>) {
206 <|>match x {
207 Ok(x) => println!("{}", x),
208 Err(_) => println!("none"),
209 }
210}
211 "#,
212 );
213 }
148} 214}
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
index bdbaae389..0cf23b754 100644
--- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
@@ -1,6 +1,5 @@
1use std::iter::once; 1use std::iter::once;
2 2
3use hir::Adt;
4use ra_syntax::{ 3use ra_syntax::{
5 ast::{ 4 ast::{
6 self, 5 self,
@@ -12,6 +11,7 @@ use ra_syntax::{
12 11
13use crate::{ 12use crate::{
14 assist_ctx::{Assist, AssistCtx}, 13 assist_ctx::{Assist, AssistCtx},
14 utils::TryEnum,
15 AssistId, 15 AssistId,
16}; 16};
17 17
@@ -45,20 +45,10 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
45 let init = let_stmt.initializer()?; 45 let init = let_stmt.initializer()?;
46 let original_pat = let_stmt.pat()?; 46 let original_pat = let_stmt.pat()?;
47 let ty = ctx.sema.type_of_expr(&init)?; 47 let ty = ctx.sema.type_of_expr(&init)?;
48 let enum_ = match ty.as_adt() { 48 let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case());
49 Some(Adt::Enum(it)) => it,
50 _ => return None,
51 };
52 let happy_case =
53 [("Result", "Ok"), ("Option", "Some")].iter().find_map(|(known_type, happy_case)| {
54 if &enum_.name(ctx.db).to_string() == known_type {
55 return Some(happy_case);
56 }
57 None
58 });
59 49
60 ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| { 50 ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| {
61 let with_placeholder: ast::Pat = match happy_case { 51 let with_placeholder: ast::Pat = match happy_variant {
62 None => make::placeholder_pat().into(), 52 None => make::placeholder_pat().into(),
63 Some(var_name) => make::tuple_struct_pat( 53 Some(var_name) => make::tuple_struct_pat(
64 make::path_unqualified(make::path_segment(make::name_ref(var_name))), 54 make::path_unqualified(make::path_segment(make::name_ref(var_name))),
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
index 62cb7a763..62d4ea522 100644
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -1,12 +1,11 @@
1use std::iter; 1use std::iter;
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 ast::{self, make}, 4 ast::{self, edit::IndentLevel, make},
5 AstNode, 5 AstNode,
6}; 6};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
9use ast::edit::IndentLevel;
10 9
11// Assist: replace_unwrap_with_match 10// Assist: replace_unwrap_with_match
12// 11//
@@ -38,42 +37,27 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
38 } 37 }
39 let caller = method_call.expr()?; 38 let caller = method_call.expr()?;
40 let ty = ctx.sema.type_of_expr(&caller)?; 39 let ty = ctx.sema.type_of_expr(&caller)?;
40 let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case();
41 41
42 let type_name = ty.as_adt()?.name(ctx.sema.db).to_string(); 42 ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| {
43 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
44 let it = make::bind_pat(make::name("a")).into();
45 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
43 46
44 for (unwrap_type, variant_name) in [("Result", "Ok"), ("Option", "Some")].iter() { 47 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
45 if &type_name == unwrap_type { 48 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
46 return ctx.add_assist(
47 AssistId("replace_unwrap_with_match"),
48 "Replace unwrap with match",
49 |edit| {
50 let ok_path =
51 make::path_unqualified(make::path_segment(make::name_ref(variant_name)));
52 let it = make::bind_pat(make::name("a")).into();
53 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
54 49
55 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); 50 let unreachable_call = make::unreachable_macro_call().into();
56 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); 51 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
57 52
58 let unreachable_call = make::unreachable_macro_call().into(); 53 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
59 let err_arm = make::match_arm( 54 let match_expr = make::expr_match(caller.clone(), match_arm_list);
60 iter::once(make::placeholder_pat().into()), 55 let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
61 unreachable_call,
62 );
63 56
64 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); 57 edit.target(method_call.syntax().text_range());
65 let match_expr = make::expr_match(caller.clone(), match_arm_list); 58 edit.set_cursor(caller.syntax().text_range().start());
66 let match_expr = 59 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
67 IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); 60 })
68
69 edit.target(method_call.syntax().text_range());
70 edit.set_cursor(caller.syntax().text_range().start());
71 edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
72 },
73 );
74 }
75 }
76 None
77} 61}
78 62
79#[cfg(test)] 63#[cfg(test)]
diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs
index 6c2a2b8b6..8d910205f 100644
--- a/crates/ra_assists/src/marks.rs
+++ b/crates/ra_assists/src/marks.rs
@@ -8,4 +8,5 @@ test_utils::marks![
8 test_not_inline_mut_variable 8 test_not_inline_mut_variable
9 test_not_applicable_if_variable_unused 9 test_not_applicable_if_variable_unused
10 change_visibility_field_false_positive 10 change_visibility_field_false_positive
11 test_add_from_impl_already_exists
11]; 12];
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs
index 3d6c59bda..efd988697 100644
--- a/crates/ra_assists/src/utils.rs
+++ b/crates/ra_assists/src/utils.rs
@@ -1,7 +1,9 @@
1//! Assorted functions shared by several assists. 1//! Assorted functions shared by several assists.
2pub(crate) mod insert_use; 2pub(crate) mod insert_use;
3 3
4use hir::Semantics; 4use std::iter;
5
6use hir::{Adt, Crate, Semantics, Trait, Type};
5use ra_ide_db::RootDatabase; 7use ra_ide_db::RootDatabase;
6use ra_syntax::{ 8use ra_syntax::{
7 ast::{self, make, NameOwner}, 9 ast::{self, make, NameOwner},
@@ -99,3 +101,109 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
99 _ => None, 101 _ => None,
100 } 102 }
101} 103}
104
105#[derive(Clone, Copy)]
106pub(crate) enum TryEnum {
107 Result,
108 Option,
109}
110
111impl TryEnum {
112 const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result];
113
114 pub(crate) fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> {
115 let enum_ = match ty.as_adt() {
116 Some(Adt::Enum(it)) => it,
117 _ => return None,
118 };
119 TryEnum::ALL.iter().find_map(|&var| {
120 if &enum_.name(sema.db).to_string() == var.type_name() {
121 return Some(var);
122 }
123 None
124 })
125 }
126
127 pub(crate) fn happy_case(self) -> &'static str {
128 match self {
129 TryEnum::Result => "Ok",
130 TryEnum::Option => "Some",
131 }
132 }
133
134 pub(crate) fn sad_pattern(self) -> ast::Pat {
135 match self {
136 TryEnum::Result => make::tuple_struct_pat(
137 make::path_unqualified(make::path_segment(make::name_ref("Err"))),
138 iter::once(make::placeholder_pat().into()),
139 )
140 .into(),
141 TryEnum::Option => make::bind_pat(make::name("None")).into(),
142 }
143 }
144
145 fn type_name(self) -> &'static str {
146 match self {
147 TryEnum::Result => "Result",
148 TryEnum::Option => "Option",
149 }
150 }
151}
152
153/// Helps with finding well-know things inside the standard library. This is
154/// somewhat similar to the known paths infra inside hir, but it different; We
155/// want to make sure that IDE specific paths don't become interesting inside
156/// the compiler itself as well.
157pub(crate) struct FamousDefs<'a, 'b>(pub(crate) &'a Semantics<'b, RootDatabase>, pub(crate) Crate);
158
159#[allow(non_snake_case)]
160impl FamousDefs<'_, '_> {
161 #[cfg(test)]
162 pub(crate) const FIXTURE: &'static str = r#"
163//- /libcore.rs crate:core
164pub mod convert{
165 pub trait From<T> {
166 fn from(T) -> Self;
167 }
168}
169
170pub mod prelude { pub use crate::convert::From }
171#[prelude_import]
172pub use prelude::*;
173"#;
174
175 pub(crate) fn core_convert_From(&self) -> Option<Trait> {
176 self.find_trait("core:convert:From")
177 }
178
179 fn find_trait(&self, path: &str) -> Option<Trait> {
180 let db = self.0.db;
181 let mut path = path.split(':');
182 let trait_ = path.next_back()?;
183 let std_crate = path.next()?;
184 let std_crate = self
185 .1
186 .dependencies(db)
187 .into_iter()
188 .find(|dep| &dep.name.to_string() == std_crate)?
189 .krate;
190
191 let mut module = std_crate.root_module(db)?;
192 for segment in path {
193 module = module.children(db).find_map(|child| {
194 let name = child.name(db)?;
195 if &name.to_string() == segment {
196 Some(child)
197 } else {
198 None
199 }
200 })?;
201 }
202 let def =
203 module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1;
204 match def {
205 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
206 _ => None,
207 }
208 }
209}