aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assists/merge_match_arms.rs127
-rw-r--r--crates/ra_assists/src/assists/replace_if_let_with_match.rs49
-rw-r--r--crates/ra_db/src/fixture.rs6
-rw-r--r--crates/ra_db/src/input.rs52
-rw-r--r--crates/ra_db/src/lib.rs4
-rw-r--r--crates/ra_fmt/src/lib.rs10
-rw-r--r--crates/ra_ide/src/mock_analysis.rs6
-rw-r--r--crates/ra_project_model/src/lib.rs43
-rw-r--r--crates/ra_syntax/src/ast/expr_extensions.rs15
-rw-r--r--crates/ra_syntax/src/ast/make.rs13
-rw-r--r--docs/user/README.md2
11 files changed, 231 insertions, 96 deletions
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs
index aca391155..64c9379da 100644
--- a/crates/ra_assists/src/assists/merge_match_arms.rs
+++ b/crates/ra_assists/src/assists/merge_match_arms.rs
@@ -1,6 +1,12 @@
1use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; 1use std::iter::successors;
2
2use hir::db::HirDatabase; 3use hir::db::HirDatabase;
3use ra_syntax::ast::{AstNode, MatchArm}; 4use ra_syntax::{
5 ast::{self, AstNode},
6 Direction, TextUnit,
7};
8
9use crate::{Assist, AssistCtx, AssistId, TextRange};
4 10
5// Assist: merge_match_arms 11// Assist: merge_match_arms
6// 12//
@@ -27,62 +33,80 @@ use ra_syntax::ast::{AstNode, MatchArm};
27// } 33// }
28// ``` 34// ```
29pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 35pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
30 let current_arm = ctx.find_node_at_offset::<MatchArm>()?; 36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
31
32 // We check if the following match arm matches this one. We could, but don't,
33 // compare to the previous match arm as well.
34 let next = current_arm.syntax().next_sibling();
35 let next_arm = MatchArm::cast(next?)?;
36
37 // Don't try to handle arms with guards for now - can add support for this later 37 // Don't try to handle arms with guards for now - can add support for this later
38 if current_arm.guard().is_some() || next_arm.guard().is_some() { 38 if current_arm.guard().is_some() {
39 return None; 39 return None;
40 } 40 }
41
42 let current_expr = current_arm.expr()?; 41 let current_expr = current_arm.expr()?;
43 let next_expr = next_arm.expr()?; 42 let current_text_range = current_arm.syntax().text_range();
44 43
45 // Check for match arm equality by comparing lengths and then string contents 44 enum CursorPos {
46 if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() { 45 InExpr(TextUnit),
47 return None; 46 InPat(TextUnit),
48 } 47 }
49 if current_expr.syntax().text() != next_expr.syntax().text() { 48 let cursor_pos = ctx.frange.range.start();
49 let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) {
50 CursorPos::InExpr(current_text_range.end() - cursor_pos)
51 } else {
52 CursorPos::InPat(cursor_pos)
53 };
54
55 // We check if the following match arms match this one. We could, but don't,
56 // compare to the previous match arm as well.
57 let arms_to_merge = successors(Some(current_arm), next_arm)
58 .take_while(|arm| {
59 if arm.guard().is_some() {
60 return false;
61 }
62 match arm.expr() {
63 Some(expr) => expr.syntax().text() == current_expr.syntax().text(),
64 None => false,
65 }
66 })
67 .collect::<Vec<_>>();
68
69 if arms_to_merge.len() <= 1 {
50 return None; 70 return None;
51 } 71 }
52 72
53 let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start();
54
55 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| { 73 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| {
56 fn contains_placeholder(a: &MatchArm) -> bool { 74 let pats = if arms_to_merge.iter().any(contains_placeholder) {
57 a.pats().any(|x| match x {
58 ra_syntax::ast::Pat::PlaceholderPat(..) => true,
59 _ => false,
60 })
61 }
62
63 let pats = if contains_placeholder(&current_arm) || contains_placeholder(&next_arm) {
64 "_".into() 75 "_".into()
65 } else { 76 } else {
66 let ps: Vec<String> = current_arm 77 arms_to_merge
67 .pats() 78 .iter()
79 .flat_map(ast::MatchArm::pats)
68 .map(|x| x.syntax().to_string()) 80 .map(|x| x.syntax().to_string())
69 .chain(next_arm.pats().map(|x| x.syntax().to_string())) 81 .collect::<Vec<String>>()
70 .collect(); 82 .join(" | ")
71 ps.join(" | ")
72 }; 83 };
73 84
74 let arm = format!("{} => {}", pats, current_expr.syntax().text()); 85 let arm = format!("{} => {}", pats, current_expr.syntax().text());
75 let offset = TextUnit::from_usize(arm.len()) - cursor_to_end;
76 86
77 let start = current_arm.syntax().text_range().start(); 87 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
78 let end = next_arm.syntax().text_range().end(); 88 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
79 89
80 edit.target(current_arm.syntax().text_range()); 90 edit.target(current_text_range);
91 edit.set_cursor(match cursor_pos {
92 CursorPos::InExpr(back_offset) => start + TextUnit::from_usize(arm.len()) - back_offset,
93 CursorPos::InPat(offset) => offset,
94 });
81 edit.replace(TextRange::from_to(start, end), arm); 95 edit.replace(TextRange::from_to(start, end), arm);
82 edit.set_cursor(start + offset);
83 }) 96 })
84} 97}
85 98
99fn contains_placeholder(a: &ast::MatchArm) -> bool {
100 a.pats().any(|x| match x {
101 ra_syntax::ast::Pat::PlaceholderPat(..) => true,
102 _ => false,
103 })
104}
105
106fn next_arm(arm: &ast::MatchArm) -> Option<ast::MatchArm> {
107 arm.syntax().siblings(Direction::Next).skip(1).find_map(ast::MatchArm::cast)
108}
109
86#[cfg(test)] 110#[cfg(test)]
87mod tests { 111mod tests {
88 use super::merge_match_arms; 112 use super::merge_match_arms;
@@ -185,6 +209,37 @@ mod tests {
185 } 209 }
186 210
187 #[test] 211 #[test]
212 fn merges_all_subsequent_arms() {
213 check_assist(
214 merge_match_arms,
215 r#"
216 enum X { A, B, C, D, E }
217
218 fn main() {
219 match X::A {
220 X::A<|> => 92,
221 X::B => 92,
222 X::C => 92,
223 X::D => 62,
224 _ => panic!(),
225 }
226 }
227 "#,
228 r#"
229 enum X { A, B, C, D, E }
230
231 fn main() {
232 match X::A {
233 X::A<|> | X::B | X::C => 92,
234 X::D => 62,
235 _ => panic!(),
236 }
237 }
238 "#,
239 )
240 }
241
242 #[test]
188 fn merge_match_arms_rejects_guards() { 243 fn merge_match_arms_rejects_guards() {
189 check_assist_not_applicable( 244 check_assist_not_applicable(
190 merge_match_arms, 245 merge_match_arms,
diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/assists/replace_if_let_with_match.rs
index c9b62e5ff..c8b13b7b3 100644
--- a/crates/ra_assists/src/assists/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/assists/replace_if_let_with_match.rs
@@ -1,9 +1,12 @@
1use format_buf::format;
2use hir::db::HirDatabase; 1use hir::db::HirDatabase;
3use ra_fmt::extract_trivial_expression; 2use ra_fmt::unwrap_trivial_block;
4use ra_syntax::{ast, AstNode}; 3use ra_syntax::{
4 ast::{self, make},
5 AstNode,
6};
5 7
6use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
9use ast::edit::IndentLevel;
7 10
8// Assist: replace_if_let_with_match 11// Assist: replace_if_let_with_match
9// 12//
@@ -43,32 +46,24 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx<impl HirDatabase>) -> Opt
43 }; 46 };
44 47
45 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| { 48 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| {
46 let match_expr = build_match_expr(expr, pat, then_block, else_block); 49 let match_expr = {
47 edit.target(if_expr.syntax().text_range()); 50 let then_arm = {
48 edit.replace_node_and_indent(if_expr.syntax(), match_expr); 51 let then_expr = unwrap_trivial_block(then_block);
49 edit.set_cursor(if_expr.syntax().text_range().start()) 52 make::match_arm(vec![pat], then_expr)
50 }) 53 };
51} 54 let else_arm = {
55 let else_expr = unwrap_trivial_block(else_block);
56 make::match_arm(vec![make::placeholder_pat().into()], else_expr)
57 };
58 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
59 };
52 60
53fn build_match_expr( 61 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
54 expr: ast::Expr,
55 pat1: ast::Pat,
56 arm1: ast::BlockExpr,
57 arm2: ast::BlockExpr,
58) -> String {
59 let mut buf = String::new();
60 format!(buf, "match {} {{\n", expr.syntax().text());
61 format!(buf, " {} => {}\n", pat1.syntax().text(), format_arm(&arm1));
62 format!(buf, " _ => {}\n", format_arm(&arm2));
63 buf.push_str("}");
64 buf
65}
66 62
67fn format_arm(block: &ast::BlockExpr) -> String { 63 edit.target(if_expr.syntax().text_range());
68 match extract_trivial_expression(block) { 64 edit.set_cursor(if_expr.syntax().text_range().start());
69 Some(e) if !e.syntax().text().contains_char('\n') => format!("{},", e.syntax().text()), 65 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr.into());
70 _ => block.syntax().text().to_string(), 66 })
71 }
72} 67}
73 68
74#[cfg(test)] 69#[cfg(test)]
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs
index 30b598e9a..17cd138c2 100644
--- a/crates/ra_db/src/fixture.rs
+++ b/crates/ra_db/src/fixture.rs
@@ -8,8 +8,8 @@ use rustc_hash::FxHashMap;
8use test_utils::{extract_offset, parse_fixture, CURSOR_MARKER}; 8use test_utils::{extract_offset, parse_fixture, CURSOR_MARKER};
9 9
10use crate::{ 10use crate::{
11 CrateGraph, CrateId, Edition, Env, FileId, FilePosition, RelativePathBuf, SourceDatabaseExt, 11 input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, RelativePathBuf,
12 SourceRoot, SourceRootId, 12 SourceDatabaseExt, SourceRoot, SourceRootId,
13}; 13};
14 14
15pub const WORKSPACE: SourceRootId = SourceRootId(0); 15pub const WORKSPACE: SourceRootId = SourceRootId(0);
@@ -139,7 +139,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
139 for (from, to) in crate_deps { 139 for (from, to) in crate_deps {
140 let from_id = crates[&from]; 140 let from_id = crates[&from];
141 let to_id = crates[&to]; 141 let to_id = crates[&to];
142 crate_graph.add_dep(from_id, to.into(), to_id).unwrap(); 142 crate_graph.add_dep(from_id, CrateName::new(&to).unwrap(), to_id).unwrap();
143 } 143 }
144 } 144 }
145 145
diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs
index 07269237a..1f1dcea42 100644
--- a/crates/ra_db/src/input.rs
+++ b/crates/ra_db/src/input.rs
@@ -83,6 +83,26 @@ pub struct CrateGraph {
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
84pub struct CrateId(pub u32); 84pub struct CrateId(pub u32);
85 85
86pub struct CrateName(SmolStr);
87
88impl CrateName {
89 /// Crates a crate name, checking for dashes in the string provided.
90 /// Dashes are not allowed in the crate names,
91 /// hence the input string is returned as `Err` for those cases.
92 pub fn new(name: &str) -> Result<CrateName, &str> {
93 if name.contains('-') {
94 Err(name)
95 } else {
96 Ok(Self(SmolStr::new(name)))
97 }
98 }
99
100 /// Crates a crate name, unconditionally replacing the dashes with underscores.
101 pub fn normalize_dashes(name: &str) -> CrateName {
102 Self(SmolStr::new(name.replace('-', "_")))
103 }
104}
105
86#[derive(Debug, Clone, PartialEq, Eq)] 106#[derive(Debug, Clone, PartialEq, Eq)]
87struct CrateData { 107struct CrateData {
88 file_id: FileId, 108 file_id: FileId,
@@ -131,13 +151,13 @@ impl CrateGraph {
131 pub fn add_dep( 151 pub fn add_dep(
132 &mut self, 152 &mut self,
133 from: CrateId, 153 from: CrateId,
134 name: SmolStr, 154 name: CrateName,
135 to: CrateId, 155 to: CrateId,
136 ) -> Result<(), CyclicDependenciesError> { 156 ) -> Result<(), CyclicDependenciesError> {
137 if self.dfs_find(from, to, &mut FxHashSet::default()) { 157 if self.dfs_find(from, to, &mut FxHashSet::default()) {
138 return Err(CyclicDependenciesError); 158 return Err(CyclicDependenciesError);
139 } 159 }
140 self.arena.get_mut(&from).unwrap().add_dep(name, to); 160 self.arena.get_mut(&from).unwrap().add_dep(name.0, to);
141 Ok(()) 161 Ok(())
142 } 162 }
143 163
@@ -268,7 +288,7 @@ pub struct CyclicDependenciesError;
268 288
269#[cfg(test)] 289#[cfg(test)]
270mod tests { 290mod tests {
271 use super::{CfgOptions, CrateGraph, Edition::Edition2018, Env, FileId, SmolStr}; 291 use super::{CfgOptions, CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId};
272 292
273 #[test] 293 #[test]
274 fn it_should_panic_because_of_cycle_dependencies() { 294 fn it_should_panic_because_of_cycle_dependencies() {
@@ -279,9 +299,9 @@ mod tests {
279 graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default(), Env::default()); 299 graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default(), Env::default());
280 let crate3 = 300 let crate3 =
281 graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default(), Env::default()); 301 graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default(), Env::default());
282 assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok()); 302 assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
283 assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok()); 303 assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
284 assert!(graph.add_dep(crate3, SmolStr::new("crate1"), crate1).is_err()); 304 assert!(graph.add_dep(crate3, CrateName::new("crate1").unwrap(), crate1).is_err());
285 } 305 }
286 306
287 #[test] 307 #[test]
@@ -293,7 +313,23 @@ mod tests {
293 graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default(), Env::default()); 313 graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default(), Env::default());
294 let crate3 = 314 let crate3 =
295 graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default(), Env::default()); 315 graph.add_crate_root(FileId(3u32), Edition2018, CfgOptions::default(), Env::default());
296 assert!(graph.add_dep(crate1, SmolStr::new("crate2"), crate2).is_ok()); 316 assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
297 assert!(graph.add_dep(crate2, SmolStr::new("crate3"), crate3).is_ok()); 317 assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
318 }
319
320 #[test]
321 fn dashes_are_normalized() {
322 let mut graph = CrateGraph::default();
323 let crate1 =
324 graph.add_crate_root(FileId(1u32), Edition2018, CfgOptions::default(), Env::default());
325 let crate2 =
326 graph.add_crate_root(FileId(2u32), Edition2018, CfgOptions::default(), Env::default());
327 assert!(graph
328 .add_dep(crate1, CrateName::normalize_dashes("crate-name-with-dashes"), crate2)
329 .is_ok());
330 assert_eq!(
331 graph.dependencies(crate1).collect::<Vec<_>>(),
332 vec![&Dependency { crate_id: crate2, name: "crate_name_with_dashes".into() }]
333 );
298 } 334 }
299} 335}
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index 21341b769..fb002d717 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -10,7 +10,9 @@ use ra_syntax::{ast, Parse, SourceFile, TextRange, TextUnit};
10 10
11pub use crate::{ 11pub use crate::{
12 cancellation::Canceled, 12 cancellation::Canceled,
13 input::{CrateGraph, CrateId, Dependency, Edition, Env, FileId, SourceRoot, SourceRootId}, 13 input::{
14 CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, SourceRoot, SourceRootId,
15 },
14}; 16};
15pub use relative_path::{RelativePath, RelativePathBuf}; 17pub use relative_path::{RelativePath, RelativePathBuf};
16pub use salsa; 18pub use salsa;
diff --git a/crates/ra_fmt/src/lib.rs b/crates/ra_fmt/src/lib.rs
index 10f592257..4bca27b5c 100644
--- a/crates/ra_fmt/src/lib.rs
+++ b/crates/ra_fmt/src/lib.rs
@@ -35,8 +35,14 @@ fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
35 successors(token.prev_token(), |token| token.prev_token()) 35 successors(token.prev_token(), |token| token.prev_token())
36} 36}
37 37
38pub fn extract_trivial_expression(expr: &ast::BlockExpr) -> Option<ast::Expr> { 38pub fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
39 let block = expr.block()?; 39 extract_trivial_expression(&block)
40 .filter(|expr| !expr.syntax().text().contains_char('\n'))
41 .unwrap_or_else(|| block.into())
42}
43
44pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
45 let block = block.block()?;
40 let expr = block.expr()?; 46 let expr = block.expr()?;
41 let non_trivial_children = block.syntax().children().filter(|it| match it.kind() { 47 let non_trivial_children = block.syntax().children().filter(|it| match it.kind() {
42 WHITESPACE | T!['{'] | T!['}'] => false, 48 WHITESPACE | T!['{'] | T!['}'] => false,
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index bf8a54932..081aaee8c 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -3,7 +3,7 @@
3use std::sync::Arc; 3use std::sync::Arc;
4 4
5use ra_cfg::CfgOptions; 5use ra_cfg::CfgOptions;
6use ra_db::{Env, RelativePathBuf}; 6use ra_db::{CrateName, Env, RelativePathBuf};
7use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; 7use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER};
8 8
9use crate::{ 9use crate::{
@@ -107,7 +107,9 @@ impl MockAnalysis {
107 crate_graph.add_crate_root(file_id, Edition2018, cfg_options, Env::default()); 107 crate_graph.add_crate_root(file_id, Edition2018, cfg_options, Env::default());
108 let crate_name = path.parent().unwrap().file_name().unwrap(); 108 let crate_name = path.parent().unwrap().file_name().unwrap();
109 if let Some(root_crate) = root_crate { 109 if let Some(root_crate) = root_crate {
110 crate_graph.add_dep(root_crate, crate_name.into(), other_crate).unwrap(); 110 crate_graph
111 .add_dep(root_crate, CrateName::new(crate_name).unwrap(), other_crate)
112 .unwrap();
111 } 113 }
112 } 114 }
113 change.add_file(source_root, file_id, path, Arc::new(contents)); 115 change.add_file(source_root, file_id, path, Arc::new(contents));
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 6a104e6f2..bc1d15406 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -13,7 +13,7 @@ use std::{
13}; 13};
14 14
15use ra_cfg::CfgOptions; 15use ra_cfg::CfgOptions;
16use ra_db::{CrateGraph, CrateId, Edition, Env, FileId}; 16use ra_db::{CrateGraph, CrateId, CrateName, Edition, Env, FileId};
17use rustc_hash::FxHashMap; 17use rustc_hash::FxHashMap;
18use serde_json::from_reader; 18use serde_json::from_reader;
19 19
@@ -177,7 +177,9 @@ impl ProjectWorkspace {
177 if let (Some(&from), Some(&to)) = 177 if let (Some(&from), Some(&to)) =
178 (crates.get(&from_crate_id), crates.get(&to_crate_id)) 178 (crates.get(&from_crate_id), crates.get(&to_crate_id))
179 { 179 {
180 if let Err(_) = crate_graph.add_dep(from, dep.name.clone().into(), to) { 180 if let Err(_) =
181 crate_graph.add_dep(from, CrateName::new(&dep.name).unwrap(), to)
182 {
181 log::error!( 183 log::error!(
182 "cyclic dependency {:?} -> {:?}", 184 "cyclic dependency {:?} -> {:?}",
183 from_crate_id, 185 from_crate_id,
@@ -215,7 +217,9 @@ impl ProjectWorkspace {
215 if let (Some(&from), Some(&to)) = 217 if let (Some(&from), Some(&to)) =
216 (sysroot_crates.get(&from), sysroot_crates.get(&to)) 218 (sysroot_crates.get(&from), sysroot_crates.get(&to))
217 { 219 {
218 if let Err(_) = crate_graph.add_dep(from, name.into(), to) { 220 if let Err(_) =
221 crate_graph.add_dep(from, CrateName::new(name).unwrap(), to)
222 {
219 log::error!("cyclic dependency between sysroot crates") 223 log::error!("cyclic dependency between sysroot crates")
220 } 224 }
221 } 225 }
@@ -257,7 +261,7 @@ impl ProjectWorkspace {
257 if let Some(proc_macro) = libproc_macro { 261 if let Some(proc_macro) = libproc_macro {
258 if let Err(_) = crate_graph.add_dep( 262 if let Err(_) = crate_graph.add_dep(
259 crate_id, 263 crate_id,
260 "proc_macro".into(), 264 CrateName::new("proc_macro").unwrap(),
261 proc_macro, 265 proc_macro,
262 ) { 266 ) {
263 log::error!( 267 log::error!(
@@ -276,9 +280,14 @@ impl ProjectWorkspace {
276 for &from in pkg_crates.get(&pkg).into_iter().flatten() { 280 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
277 if let Some(to) = lib_tgt { 281 if let Some(to) = lib_tgt {
278 if to != from { 282 if to != from {
279 if let Err(_) = 283 if let Err(_) = crate_graph.add_dep(
280 crate_graph.add_dep(from, pkg.name(&cargo).into(), to) 284 from,
281 { 285 // For root projects with dashes in their name,
286 // cargo metadata does not do any normalization,
287 // so we do it ourselves currently
288 CrateName::normalize_dashes(pkg.name(&cargo)),
289 to,
290 ) {
282 log::error!( 291 log::error!(
283 "cyclic dependency between targets of {}", 292 "cyclic dependency between targets of {}",
284 pkg.name(&cargo) 293 pkg.name(&cargo)
@@ -289,17 +298,23 @@ impl ProjectWorkspace {
289 // core is added as a dependency before std in order to 298 // core is added as a dependency before std in order to
290 // mimic rustcs dependency order 299 // mimic rustcs dependency order
291 if let Some(core) = libcore { 300 if let Some(core) = libcore {
292 if let Err(_) = crate_graph.add_dep(from, "core".into(), core) { 301 if let Err(_) =
302 crate_graph.add_dep(from, CrateName::new("core").unwrap(), core)
303 {
293 log::error!("cyclic dependency on core for {}", pkg.name(&cargo)) 304 log::error!("cyclic dependency on core for {}", pkg.name(&cargo))
294 } 305 }
295 } 306 }
296 if let Some(alloc) = liballoc { 307 if let Some(alloc) = liballoc {
297 if let Err(_) = crate_graph.add_dep(from, "alloc".into(), alloc) { 308 if let Err(_) =
309 crate_graph.add_dep(from, CrateName::new("alloc").unwrap(), alloc)
310 {
298 log::error!("cyclic dependency on alloc for {}", pkg.name(&cargo)) 311 log::error!("cyclic dependency on alloc for {}", pkg.name(&cargo))
299 } 312 }
300 } 313 }
301 if let Some(std) = libstd { 314 if let Some(std) = libstd {
302 if let Err(_) = crate_graph.add_dep(from, "std".into(), std) { 315 if let Err(_) =
316 crate_graph.add_dep(from, CrateName::new("std").unwrap(), std)
317 {
303 log::error!("cyclic dependency on std for {}", pkg.name(&cargo)) 318 log::error!("cyclic dependency on std for {}", pkg.name(&cargo))
304 } 319 }
305 } 320 }
@@ -312,9 +327,11 @@ impl ProjectWorkspace {
312 for dep in pkg.dependencies(&cargo) { 327 for dep in pkg.dependencies(&cargo) {
313 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) { 328 if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
314 for &from in pkg_crates.get(&pkg).into_iter().flatten() { 329 for &from in pkg_crates.get(&pkg).into_iter().flatten() {
315 if let Err(_) = 330 if let Err(_) = crate_graph.add_dep(
316 crate_graph.add_dep(from, dep.name.clone().into(), to) 331 from,
317 { 332 CrateName::new(&dep.name).unwrap(),
333 to,
334 ) {
318 log::error!( 335 log::error!(
319 "cyclic dependency {} -> {}", 336 "cyclic dependency {} -> {}",
320 pkg.name(&cargo), 337 pkg.name(&cargo),
diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs
index 539759450..2e50a095c 100644
--- a/crates/ra_syntax/src/ast/expr_extensions.rs
+++ b/crates/ra_syntax/src/ast/expr_extensions.rs
@@ -7,6 +7,21 @@ use crate::{
7 SyntaxToken, T, 7 SyntaxToken, T,
8}; 8};
9 9
10impl ast::Expr {
11 pub fn is_block_like(&self) -> bool {
12 match self {
13 ast::Expr::IfExpr(_)
14 | ast::Expr::LoopExpr(_)
15 | ast::Expr::ForExpr(_)
16 | ast::Expr::WhileExpr(_)
17 | ast::Expr::BlockExpr(_)
18 | ast::Expr::MatchExpr(_)
19 | ast::Expr::TryBlockExpr(_) => true,
20 _ => false,
21 }
22 }
23}
24
10#[derive(Debug, Clone, PartialEq, Eq)] 25#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum ElseBranch { 26pub enum ElseBranch {
12 Block(ast::BlockExpr), 27 Block(ast::BlockExpr),
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 38c0e9a66..629503dc5 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -122,11 +122,18 @@ pub fn match_arm(pats: impl IntoIterator<Item = ast::Pat>, expr: ast::Expr) -> a
122} 122}
123 123
124pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList { 124pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
125 let arms_str = arms.into_iter().map(|arm| format!("\n {}", arm.syntax())).join(","); 125 let arms_str = arms
126 return from_text(&format!("{},\n", arms_str)); 126 .into_iter()
127 .map(|arm| {
128 let needs_comma = arm.expr().map_or(true, |it| !it.is_block_like());
129 let comma = if needs_comma { "," } else { "" };
130 format!(" {}{}\n", arm.syntax(), comma)
131 })
132 .collect::<String>();
133 return from_text(&format!("{}", arms_str));
127 134
128 fn from_text(text: &str) -> ast::MatchArmList { 135 fn from_text(text: &str) -> ast::MatchArmList {
129 ast_from_text(&format!("fn f() {{ match () {{{}}} }}", text)) 136 ast_from_text(&format!("fn f() {{ match () {{\n{}}} }}", text))
130 } 137 }
131} 138}
132 139
diff --git a/docs/user/README.md b/docs/user/README.md
index 2948f6ff3..18867cd11 100644
--- a/docs/user/README.md
+++ b/docs/user/README.md
@@ -94,7 +94,7 @@ host.
94* `rust-analyzer.highlightingOn`: enables experimental syntax highlighting. 94* `rust-analyzer.highlightingOn`: enables experimental syntax highlighting.
95 Colors can be configured via `editor.tokenColorCustomizations`. 95 Colors can be configured via `editor.tokenColorCustomizations`.
96 As an example, [Pale Fire](https://github.com/matklad/pale-fire/) color scheme tweaks rust colors. 96 As an example, [Pale Fire](https://github.com/matklad/pale-fire/) color scheme tweaks rust colors.
97* `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts. 97* `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts the
98 `Enter` key to make it easier to continue comments. Note that it may conflict with VIM emulation plugin. 98 `Enter` key to make it easier to continue comments. Note that it may conflict with VIM emulation plugin.
99* `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable 99* `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable
100* `rust-analyzer.enableCargoWatchOnStartup`: prompt to install & enable `cargo 100* `rust-analyzer.enableCargoWatchOnStartup`: prompt to install & enable `cargo