aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/flip_comma.rs30
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_assists/src/merge_match_arms.rs188
-rw-r--r--crates/ra_hir/src/ty/tests.rs26
-rw-r--r--crates/ra_syntax/Cargo.toml2
-rw-r--r--crates/ra_syntax/src/ast/expr_extensions.rs22
6 files changed, 255 insertions, 15 deletions
diff --git a/crates/ra_assists/src/flip_comma.rs b/crates/ra_assists/src/flip_comma.rs
index 34489329c..5ee7561bc 100644
--- a/crates/ra_assists/src/flip_comma.rs
+++ b/crates/ra_assists/src/flip_comma.rs
@@ -7,6 +7,13 @@ pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
7 let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?; 7 let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?;
8 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; 8 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
9 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; 9 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
10
11 // Don't apply a "flip" in case of a last comma
12 // that typically comes before punctuation
13 if next.kind().is_punct() {
14 return None;
15 }
16
10 ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| { 17 ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| {
11 edit.target(comma.text_range()); 18 edit.target(comma.text_range());
12 edit.replace(prev.text_range(), next.to_string()); 19 edit.replace(prev.text_range(), next.to_string());
@@ -35,4 +42,27 @@ mod tests {
35 fn flip_comma_target() { 42 fn flip_comma_target() {
36 check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",") 43 check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",")
37 } 44 }
45
46 #[test]
47 #[should_panic]
48 fn flip_comma_before_punct() {
49 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619
50 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
51 // declaration body.
52 check_assist_target(
53 flip_comma,
54 "pub enum Test { \
55 A,<|> \
56 }",
57 ",",
58 );
59
60 check_assist_target(
61 flip_comma,
62 "pub struct Test { \
63 foo: usize,<|> \
64 }",
65 ",",
66 );
67 }
38} 68}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 0d848629d..03eec73ad 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -93,6 +93,7 @@ mod flip_comma;
93mod flip_binexpr; 93mod flip_binexpr;
94mod change_visibility; 94mod change_visibility;
95mod fill_match_arms; 95mod fill_match_arms;
96mod merge_match_arms;
96mod introduce_variable; 97mod introduce_variable;
97mod inline_local_variable; 98mod inline_local_variable;
98mod replace_if_let_with_match; 99mod replace_if_let_with_match;
@@ -109,6 +110,7 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis
109 add_impl::add_impl, 110 add_impl::add_impl,
110 change_visibility::change_visibility, 111 change_visibility::change_visibility,
111 fill_match_arms::fill_match_arms, 112 fill_match_arms::fill_match_arms,
113 merge_match_arms::merge_match_arms,
112 flip_comma::flip_comma, 114 flip_comma::flip_comma,
113 flip_binexpr::flip_binexpr, 115 flip_binexpr::flip_binexpr,
114 introduce_variable::introduce_variable, 116 introduce_variable::introduce_variable,
diff --git a/crates/ra_assists/src/merge_match_arms.rs b/crates/ra_assists/src/merge_match_arms.rs
new file mode 100644
index 000000000..bc5f6f17c
--- /dev/null
+++ b/crates/ra_assists/src/merge_match_arms.rs
@@ -0,0 +1,188 @@
1use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit};
2use hir::db::HirDatabase;
3use ra_syntax::ast::{AstNode, MatchArm};
4
5pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
6 let current_arm = ctx.node_at_offset::<MatchArm>()?;
7
8 // We check if the following match arm matches this one. We could, but don't,
9 // compare to the previous match arm as well.
10 let next = current_arm.syntax().next_sibling();
11 let next_arm = MatchArm::cast(next?.clone())?;
12
13 // Don't try to handle arms with guards for now - can add support for this later
14 if current_arm.guard().is_some() || next_arm.guard().is_some() {
15 return None;
16 }
17
18 let current_expr = current_arm.expr()?;
19 let next_expr = next_arm.expr()?;
20
21 // Check for match arm equality by comparing lengths and then string contents
22 if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() {
23 return None;
24 }
25 if current_expr.syntax().text() != next_expr.syntax().text() {
26 return None;
27 }
28
29 let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start();
30
31 ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| {
32 fn contains_placeholder(a: &MatchArm) -> bool {
33 a.pats().any(|x| match x.kind() {
34 ra_syntax::ast::PatKind::PlaceholderPat(..) => true,
35 _ => false,
36 })
37 }
38
39 let pats = if contains_placeholder(&current_arm) || contains_placeholder(&next_arm) {
40 "_".into()
41 } else {
42 let ps: Vec<String> = current_arm
43 .pats()
44 .map(|x| x.syntax().to_string())
45 .chain(next_arm.pats().map(|x| x.syntax().to_string()))
46 .collect();
47 ps.join(" | ")
48 };
49
50 let arm = format!("{} => {}", pats, current_expr.syntax().text());
51 let offset = TextUnit::from_usize(arm.len()) - cursor_to_end;
52
53 let start = current_arm.syntax().text_range().start();
54 let end = next_arm.syntax().text_range().end();
55
56 edit.target(current_arm.syntax().text_range());
57 edit.replace(TextRange::from_to(start, end), arm);
58 edit.set_cursor(start + offset);
59 });
60
61 ctx.build()
62}
63
64#[cfg(test)]
65mod tests {
66 use super::merge_match_arms;
67 use crate::helpers::{check_assist, check_assist_not_applicable};
68
69 #[test]
70 fn merge_match_arms_single_patterns() {
71 check_assist(
72 merge_match_arms,
73 r#"
74 #[derive(Debug)]
75 enum X { A, B, C }
76
77 fn main() {
78 let x = X::A;
79 let y = match x {
80 X::A => { 1i32<|> }
81 X::B => { 1i32 }
82 X::C => { 2i32 }
83 }
84 }
85 "#,
86 r#"
87 #[derive(Debug)]
88 enum X { A, B, C }
89
90 fn main() {
91 let x = X::A;
92 let y = match x {
93 X::A | X::B => { 1i32<|> }
94 X::C => { 2i32 }
95 }
96 }
97 "#,
98 );
99 }
100
101 #[test]
102 fn merge_match_arms_multiple_patterns() {
103 check_assist(
104 merge_match_arms,
105 r#"
106 #[derive(Debug)]
107 enum X { A, B, C, D, E }
108
109 fn main() {
110 let x = X::A;
111 let y = match x {
112 X::A | X::B => {<|> 1i32 },
113 X::C | X::D => { 1i32 },
114 X::E => { 2i32 },
115 }
116 }
117 "#,
118 r#"
119 #[derive(Debug)]
120 enum X { A, B, C, D, E }
121
122 fn main() {
123 let x = X::A;
124 let y = match x {
125 X::A | X::B | X::C | X::D => {<|> 1i32 },
126 X::E => { 2i32 },
127 }
128 }
129 "#,
130 );
131 }
132
133 #[test]
134 fn merge_match_arms_placeholder_pattern() {
135 check_assist(
136 merge_match_arms,
137 r#"
138 #[derive(Debug)]
139 enum X { A, B, C, D, E }
140
141 fn main() {
142 let x = X::A;
143 let y = match x {
144 X::A => { 1i32 },
145 X::B => { 2i<|>32 },
146 _ => { 2i32 }
147 }
148 }
149 "#,
150 r#"
151 #[derive(Debug)]
152 enum X { A, B, C, D, E }
153
154 fn main() {
155 let x = X::A;
156 let y = match x {
157 X::A => { 1i32 },
158 _ => { 2i<|>32 }
159 }
160 }
161 "#,
162 );
163 }
164
165 #[test]
166 fn merge_match_arms_rejects_guards() {
167 check_assist_not_applicable(
168 merge_match_arms,
169 r#"
170 #[derive(Debug)]
171 enum X {
172 A(i32),
173 B,
174 C
175 }
176
177 fn main() {
178 let x = X::A;
179 let y = match x {
180 X::A(a) if a > 5 => { <|>1i32 },
181 X::B => { 1i32 },
182 X::C => { 2i32 }
183 }
184 }
185 "#,
186 );
187 }
188}
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index 676711d0a..36dea17a3 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -334,6 +334,8 @@ fn infer_literals() {
334 infer(r##" 334 infer(r##"
335fn test() { 335fn test() {
336 5i32; 336 5i32;
337 5f32;
338 5f64;
337 "hello"; 339 "hello";
338 b"bytes"; 340 b"bytes";
339 'c'; 341 'c';
@@ -351,18 +353,20 @@ fn test() {
351} 353}
352"##), 354"##),
353 @r###" 355 @r###"
354[11; 201) '{ ...o"#; }': () 356[11; 221) '{ ...o"#; }': ()
355[17; 21) '5i32': i32 357[17; 21) '5i32': i32
356[27; 34) '"hello"': &str 358[27; 31) '5f32': f32
357[40; 48) 'b"bytes"': &[u8] 359[37; 41) '5f64': f64
358[54; 57) ''c'': char 360[47; 54) '"hello"': &str
359[63; 67) 'b'b'': u8 361[60; 68) 'b"bytes"': &[u8]
360[73; 77) '3.14': f64 362[74; 77) ''c'': char
361[83; 87) '5000': i32 363[83; 87) 'b'b'': u8
362[93; 98) 'false': bool 364[93; 97) '3.14': f64
363[104; 108) 'true': bool 365[103; 107) '5000': i32
364[114; 182) 'r#" ... "#': &str 366[113; 118) 'false': bool
365[188; 198) 'br#"yolo"#': &[u8]"### 367[124; 128) 'true': bool
368[134; 202) 'r#" ... "#': &str
369[208; 218) 'br#"yolo"#': &[u8]"###
366 ); 370 );
367} 371}
368 372
diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml
index 40d63ef7a..bc1c88070 100644
--- a/crates/ra_syntax/Cargo.toml
+++ b/crates/ra_syntax/Cargo.toml
@@ -10,7 +10,7 @@ repository = "https://github.com/rust-analyzer/rust-analyzer"
10[dependencies] 10[dependencies]
11unicode-xid = "0.1.0" 11unicode-xid = "0.1.0"
12itertools = "0.8.0" 12itertools = "0.8.0"
13rowan = "0.6.0" 13rowan = "0.6.1"
14ra_rustc_lexer = { version = "0.1.0-pre.2" } 14ra_rustc_lexer = { version = "0.1.0-pre.2" }
15 15
16# ideally, `serde` should be enabled by `ra_lsp_server`, but we enable it here 16# ideally, `serde` should be enabled by `ra_lsp_server`, but we enable it here
diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs
index f9190d877..8284f1b25 100644
--- a/crates/ra_syntax/src/ast/expr_extensions.rs
+++ b/crates/ra_syntax/src/ast/expr_extensions.rs
@@ -239,16 +239,32 @@ impl ast::Literal {
239 pub fn kind(&self) -> LiteralKind { 239 pub fn kind(&self) -> LiteralKind {
240 match self.token().kind() { 240 match self.token().kind() {
241 INT_NUMBER => { 241 INT_NUMBER => {
242 let allowed_suffix_list = [ 242 let int_suffix_list = [
243 "isize", "i128", "i64", "i32", "i16", "i8", "usize", "u128", "u64", "u32", 243 "isize", "i128", "i64", "i32", "i16", "i8", "usize", "u128", "u64", "u32",
244 "u16", "u8", 244 "u16", "u8",
245 ]; 245 ];
246
247 // The lexer treats e.g. `1f64` as an integer literal. See
248 // https://github.com/rust-analyzer/rust-analyzer/issues/1592
249 // and the comments on the linked PR.
250 let float_suffix_list = ["f32", "f64"];
251
246 let text = self.token().text().to_string(); 252 let text = self.token().text().to_string();
247 let suffix = allowed_suffix_list 253
254 let float_suffix = float_suffix_list
248 .iter() 255 .iter()
249 .find(|&s| text.ends_with(s)) 256 .find(|&s| text.ends_with(s))
250 .map(|&suf| SmolStr::new(suf)); 257 .map(|&suf| SmolStr::new(suf));
251 LiteralKind::IntNumber { suffix } 258
259 if float_suffix.is_some() {
260 LiteralKind::FloatNumber { suffix: float_suffix }
261 } else {
262 let suffix = int_suffix_list
263 .iter()
264 .find(|&s| text.ends_with(s))
265 .map(|&suf| SmolStr::new(suf));
266 LiteralKind::IntNumber { suffix }
267 }
252 } 268 }
253 FLOAT_NUMBER => { 269 FLOAT_NUMBER => {
254 let allowed_suffix_list = ["f64", "f32"]; 270 let allowed_suffix_list = ["f64", "f32"];