Repository: rust-analyzer/rust-analyzer
Pull request #3732 (2020-03-26 13:48:10)

Assist: replace unwrap with match
attempt on #3669


Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 29e388e7c1..58c8f083a5 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -7,18 +7,14 @@ on:
     - staging
     - trying
 
-env:
-  CARGO_INCREMENTAL: 0
-  CARGO_NET_RETRY: 10
-  CC: deny_c
-  RUN_SLOW_TESTS: 1
-  RUSTFLAGS: -D warnings
-  RUSTUP_MAX_RETRIES: 10
-
 jobs:
   rust-audit:
     name: Audit Rust vulnerabilities
     runs-on: ubuntu-latest
+    env:
+      RUSTUP_MAX_RETRIES: 10
+      CARGO_NET_RETRY: 10
+
     steps:
     - name: Install Rust toolchain
       uses: actions-rs/toolchain@v1
@@ -50,6 +46,14 @@ jobs:
       matrix:
         os: [ubuntu-latest, windows-latest, macos-latest]
 
+    env:
+      RUSTFLAGS: -D warnings
+      CC: deny_c
+      CARGO_INCREMENTAL: 0
+      RUN_SLOW_TESTS: 1
+      RUSTUP_MAX_RETRIES: 10
+      CARGO_NET_RETRY: 10
+
     steps:
     - name: Checkout repository
       uses: actions/checkout@v2
@@ -107,6 +111,9 @@ jobs:
   typescript:
     name: TypeScript
     runs-on: ubuntu-latest
+    env:
+      CXX: g++-4.9
+      CC: gcc-4.9
     steps:
     - name: Checkout repository
       uses: actions/checkout@v2
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index fd184e8f10..c055d113da 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -8,12 +8,6 @@ on:
     - release
     - nightly
 
-env:
-  CARGO_INCREMENTAL: 0
-  CARGO_NET_RETRY: 10
-  RUSTFLAGS: -D warnings
-  RUSTUP_MAX_RETRIES: 10
-
 jobs:
   dist:
     name: dist
@@ -22,6 +16,12 @@ jobs:
       matrix:
         os: [ubuntu-latest, windows-latest, macos-latest]
 
+    env:
+      RUSTFLAGS: -D warnings
+      CARGO_INCREMENTAL: 0
+      RUSTUP_MAX_RETRIES: 10
+      CARGO_NET_RETRY: 10
+
     steps:
     - name: Checkout repository
       uses: actions/checkout@v2
diff --git a/.github/workflows/rustdoc.yaml b/.github/workflows/rustdoc.yaml
index cf4bca8403..c84ce5d489 100644
--- a/.github/workflows/rustdoc.yaml
+++ b/.github/workflows/rustdoc.yaml
@@ -4,15 +4,12 @@ on:
    branches:
    - master
 
-env:
-  CARGO_INCREMENTAL: 0
-  CARGO_NET_RETRY: 10
-  RUSTFLAGS: -D warnings
-  RUSTUP_MAX_RETRIES: 10
-
 jobs:
   rustdoc:
     runs-on: ubuntu-latest
+    env:
+      RUSTFLAGS: -D warnings
+      CARGO_INCREMENTAL: 0
 
     steps:
     - name: Checkout repository
diff --git a/Cargo.lock b/Cargo.lock
index 10ea555505..908319f875 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -46,9 +46,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
 
 [[package]]
 name = "backtrace"
-version = "0.3.46"
+version = "0.3.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e"
+checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8"
 dependencies = [
  "backtrace-sys",
  "cfg-if",
@@ -58,9 +58,9 @@ dependencies = [
 
 [[package]]
 name = "backtrace-sys"
-version = "0.1.35"
+version = "0.1.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118"
+checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69"
 dependencies = [
  "cc",
  "libc",
@@ -804,9 +804,9 @@ dependencies = [
 
 [[package]]
 name = "paste"
-version = "0.1.8"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8292c1e1e81ddb552c4c90c36af201a0ce7e34995f55f0480f01052f242811c9"
+checksum = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046"
 dependencies = [
  "paste-impl",
  "proc-macro-hack",
@@ -814,9 +814,9 @@ dependencies = [
 
 [[package]]
 name = "paste-impl"
-version = "0.1.8"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e9c43f2645f06ee452544ad032886a75f3d1797b9487dcadcae9100ba58a51c"
+checksum = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb"
 dependencies = [
  "proc-macro-hack",
  "proc-macro2",
@@ -1238,9 +1238,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
 
 [[package]]
 name = "regex"
-version = "1.3.6"
+version = "1.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3"
+checksum = "8900ebc1363efa7ea1c399ccc32daed870b4002651e0bed86e72d501ebbe0048"
 dependencies = [
  "aho-corasick",
  "memchr",
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index cd6d1ee6c3..54e0a6c840 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -2,14 +2,13 @@ use ra_syntax::{
     ast::{self, NameOwner, VisibilityOwner},
     AstNode,
     SyntaxKind::{
-        ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY,
-        WHITESPACE,
+        ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, IDENT, MODULE, STRUCT_DEF, TRAIT_DEF,
+        VISIBILITY, WHITESPACE,
     },
     SyntaxNode, TextUnit, T,
 };
 
 use crate::{Assist, AssistCtx, AssistId};
-use test_utils::tested_by;
 
 // Assist: change_visibility
 //
@@ -48,16 +47,13 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
         }
         (vis_offset(&parent), keyword.text_range())
     } else {
-        let field_name: ast::Name = ctx.find_node_at_offset()?;
-        let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?;
-        if field.name()? != field_name {
-            tested_by!(change_visibility_field_false_positive);
+        let ident = ctx.token_at_offset().find(|leaf| leaf.kind() == IDENT)?;
+        let field = ident.parent().ancestors().find_map(ast::RecordFieldDef::cast)?;
+        if field.name()?.syntax().text_range() != ident.text_range() && field.visibility().is_some()
+        {
             return None;
         }
-        if field.visibility().is_some() {
-            return None;
-        }
-        (vis_offset(field.syntax()), field_name.syntax().text_range())
+        (vis_offset(field.syntax()), ident.text_range())
     };
 
     ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| {
@@ -102,11 +98,8 @@ fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
 
 #[cfg(test)]
 mod tests {
-    use test_utils::covers;
-
-    use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
-
     use super::*;
+    use crate::helpers::{check_assist, check_assist_target};
 
     #[test]
     fn change_visibility_adds_pub_crate_to_items() {
@@ -127,17 +120,8 @@ mod tests {
     fn change_visibility_works_with_struct_fields() {
         check_assist(
             change_visibility,
-            r"struct S { <|>field: u32 }",
-            r"struct S { <|>pub(crate) field: u32 }",
-        )
-    }
-
-    #[test]
-    fn change_visibility_field_false_positive() {
-        covers!(change_visibility_field_false_positive);
-        check_assist_not_applicable(
-            change_visibility,
-            r"struct S { field: [(); { let <|>x = ();}] }",
+            "struct S { <|>field: u32 }",
+            "struct S { <|>pub(crate) field: u32 }",
         )
     }
 
@@ -160,7 +144,7 @@ mod tests {
     fn change_visibility_handles_comment_attrs() {
         check_assist(
             change_visibility,
-            r"
+            "
             /// docs
 
             // comments
@@ -168,7 +152,7 @@ mod tests {
             #[derive(Debug)]
             <|>struct Foo;
             ",
-            r"
+            "
             /// docs
 
             // comments
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
new file mode 100644
index 0000000000..62cb7a7631
--- /dev/null
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -0,0 +1,177 @@
+use std::iter;
+
+use ra_syntax::{
+    ast::{self, make},
+    AstNode,
+};
+
+use crate::{Assist, AssistCtx, AssistId};
+use ast::edit::IndentLevel;
+
+// Assist: replace_unwrap_with_match
+//
+// Replaces `unwrap` a `match` expression. Works for Result and Option.
+//
+// ```
+// enum Result<T, E> { Ok(T), Err(E) }
+// fn main() {
+//     let x: Result<i32, i32> = Result::Ok(92);
+//     let y = x.<|>unwrap();
+// }
+// ```
+// ->
+// ```
+// enum Result<T, E> { Ok(T), Err(E) }
+// fn main() {
+//     let x: Result<i32, i32> = Result::Ok(92);
+//     let y = match x {
+//         Ok(a) => a,
+//         _ => unreachable!(),
+//     };
+// }
+// ```
+pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
+    let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
+    let name = method_call.name_ref()?;
+    if name.text() != "unwrap" {
+        return None;
+    }
+    let caller = method_call.expr()?;
+    let ty = ctx.sema.type_of_expr(&caller)?;
+
+    let type_name = ty.as_adt()?.name(ctx.sema.db).to_string();
+
+    for (unwrap_type, variant_name) in [("Result", "Ok"), ("Option", "Some")].iter() {
+        if &type_name == unwrap_type {
+            return ctx.add_assist(
+                AssistId("replace_unwrap_with_match"),
+                "Replace unwrap with match",
+                |edit| {
+                    let ok_path =
+                        make::path_unqualified(make::path_segment(make::name_ref(variant_name)));
+                    let it = make::bind_pat(make::name("a")).into();
+                    let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
+
+                    let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
+                    let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
+
+                    let unreachable_call = make::unreachable_macro_call().into();
+                    let err_arm = make::match_arm(
+                        iter::once(make::placeholder_pat().into()),
+                        unreachable_call,
+                    );
+
+                    let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
+                    let match_expr = make::expr_match(caller.clone(), match_arm_list);
+                    let match_expr =
+                        IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
+
+                    edit.target(method_call.syntax().text_range());
+                    edit.set_cursor(caller.syntax().text_range().start());
+                    edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
+                },
+            );
+        }
+    }
+    None
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::helpers::{check_assist, check_assist_target};
+
+    #[test]
+    fn test_replace_result_unwrap_with_match() {
+        check_assist(
+            replace_unwrap_with_match,
+            r"
+enum Result<T, E> { Ok(T), Err(E) }
+fn i<T>(a: T) -> T { a }
+fn main() {
+    let x: Result<i32, i32> = Result::Ok(92);
+    let y = i(x).<|>unwrap();
+}
+            ",
+            r"
+enum Result<T, E> { Ok(T), Err(E) }
+fn i<T>(a: T) -> T { a }
+fn main() {
+    let x: Result<i32, i32> = Result::Ok(92);
+    let y = <|>match i(x) {
+        Ok(a) => a,
+        _ => unreachable!(),
+    };
+}
+            ",
+        )
+    }
+
+    #[test]
+    fn test_replace_option_unwrap_with_match() {
+        check_assist(
+            replace_unwrap_with_match,
+            r"
+enum Option<T> { Some(T), None }
+fn i<T>(a: T) -> T { a }
+fn main() {
+    let x = Option::Some(92);
+    let y = i(x).<|>unwrap();
+}
+            ",
+            r"
+enum Option<T> { Some(T), None }
+fn i<T>(a: T) -> T { a }
+fn main() {
+    let x = Option::Some(92);
+    let y = <|>match i(x) {
+        Some(a) => a,
+        _ => unreachable!(),
+    };
+}
+            ",
+        );
+    }
+
+    #[test]
+    fn test_replace_result_unwrap_with_match_chaining() {
+        check_assist(
+            replace_unwrap_with_match,
+            r"
+enum Result<T, E> { Ok(T), Err(E) }
+fn i<T>(a: T) -> T { a }
+fn main() {
+    let x: Result<i32, i32> = Result::Ok(92);
+    let y = i(x).<|>unwrap().count_zeroes();
+}
+            ",
+            r"
+enum Result<T, E> { Ok(T), Err(E) }
+fn i<T>(a: T) -> T { a }
+fn main() {
+    let x: Result<i32, i32> = Result::Ok(92);
+    let y = <|>match i(x) {
+        Ok(a) => a,
+        _ => unreachable!(),
+    }.count_zeroes();
+}
+            ",
+        )
+    }
+
+    #[test]
+    fn replace_unwrap_with_match_target() {
+        check_assist_target(
+            replace_unwrap_with_match,
+            r"
+enum Option<T> { Some(T), None }
+fn i<T>(a: T) -> T { a }
+fn main() {
+    let x = Option::Some(92);
+    let y = i(x).<|>unwrap();
+}
+            ",
+            r"i(x).unwrap()",
+        );
+    }
+}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index bcc9b3f10c..becd5e99da 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -119,6 +119,7 @@ mod handlers {
     mod remove_mut;
     mod replace_if_let_with_match;
     mod replace_qualified_name_with_use;
+    mod replace_unwrap_with_match;
     mod split_import;
 
     pub(crate) fn all() -> &'static [AssistHandler] {
@@ -154,6 +155,7 @@ mod handlers {
             remove_mut::remove_mut,
             replace_if_let_with_match::replace_if_let_with_match,
             replace_qualified_name_with_use::replace_qualified_name_with_use,
+            replace_unwrap_with_match::replace_unwrap_with_match,
             split_import::split_import,
         ]
     }
diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs
index 6c2a2b8b6e..22404ee80a 100644
--- a/crates/ra_assists/src/marks.rs
+++ b/crates/ra_assists/src/marks.rs
@@ -7,5 +7,4 @@ test_utils::marks![
     not_applicable_outside_of_bind_pat
     test_not_inline_mut_variable
     test_not_applicable_if_variable_unused
-    change_visibility_field_false_positive
 ];
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs
index 5e3e9203cd..3464f43dfb 100644
--- a/crates/ra_db/src/fixture.rs
+++ b/crates/ra_db/src/fixture.rs
@@ -28,9 +28,9 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
         db
     }
 
-    fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
+    fn with_position(fixture: &str) -> (Self, FilePosition) {
         let mut db = Self::default();
-        let pos = with_files(&mut db, ra_fixture);
+        let pos = with_files(&mut db, fixture);
         (db, pos.unwrap())
     }
 
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index cd2a8fc620..3f0ebca0df 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -235,7 +235,7 @@ impl Module {
     }
 
     pub fn visibility_of(self, db: &dyn HirDatabase, def: &ModuleDef) -> Option<Visibility> {
-        db.crate_def_map(self.id.krate)[self.id.local_id].scope.visibility_of(def.clone().into())
+        db.crate_def_map(self.id.krate)[self.id.local_id].scope.visbility_of(def.clone().into())
     }
 
     pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs
index ec931b34fe..fcba950911 100644
--- a/crates/ra_hir/src/db.rs
+++ b/crates/ra_hir/src/db.rs
@@ -10,16 +10,15 @@ pub use hir_def::db::{
     TraitDataQuery, TypeAliasDataQuery, UnionDataQuery,
 };
 pub use hir_expand::db::{
-    AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
-    MacroArgQuery, MacroDefQuery, MacroExpandQuery, ParseMacroQuery,
+    AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternMacroQuery, MacroArgQuery, MacroDefQuery,
+    MacroExpandQuery, ParseMacroQuery,
 };
 pub use hir_ty::db::{
     AssociatedTyDataQuery, AssociatedTyValueQuery, CallableItemSignatureQuery, FieldTypesQuery,
     GenericDefaultsQuery, GenericPredicatesForParamQuery, GenericPredicatesQuery, HirDatabase,
     HirDatabaseStorage, ImplDatumQuery, ImplSelfTyQuery, ImplTraitQuery, ImplsForTraitQuery,
     ImplsInCrateQuery, InferQueryQuery, InternAssocTyValueQuery, InternChalkImplQuery,
-    InternTypeCtorQuery, InternTypeParamIdQuery, StructDatumQuery, TraitDatumQuery,
-    TraitSolveQuery, TyQuery, ValueTyQuery,
+    InternTypeCtorQuery, StructDatumQuery, TraitDatumQuery, TraitSolveQuery, TyQuery, ValueTyQuery,
 };
 
 #[test]
diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs
index 5dc7395f5e..7f8c1ea218 100644
--- a/crates/ra_hir_def/src/db.rs
+++ b/crates/ra_hir_def/src/db.rs
@@ -48,7 +48,6 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
     fn raw_items(&self, file_id: HirFileId) -> Arc<RawItems>;
 
     #[salsa::invoke(crate_def_map_wait)]
-    #[salsa::transparent]
     fn crate_def_map(&self, krate: CrateId) -> Arc<CrateDefMap>;
 
     #[salsa::invoke(CrateDefMap::crate_def_map_query)]
@@ -110,6 +109,12 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
     fn documentation(&self, def: AttrDefId) -> Option<Documentation>;
 }
 
+// impl<T: DefDatabase> Upcast<dyn AstDatabase> for T {
+//     fn upcast(&self) -> &dyn AstDatabase {
+//         &*self
+//     }
+// }
+
 fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> {
     let _p = profile("crate_def_map:wait");
     db.crate_def_map_query(krate)
diff --git a/crates/ra_hir_def/src/item_scope.rs b/crates/ra_hir_def/src/item_scope.rs
index 259b9ff035..ef1aaba6f7 100644
--- a/crates/ra_hir_def/src/item_scope.rs
+++ b/crates/ra_hir_def/src/item_scope.rs
@@ -68,7 +68,7 @@ impl ItemScope {
         self.impls.iter().copied()
     }
 
-    pub fn visibility_of(&self, def: ModuleDefId) -> Option<Visibility> {
+    pub fn visbility_of(&self, def: ModuleDefId) -> Option<Visibility> {
         self.name_of(ItemInNs::Types(def))
             .or_else(|| self.name_of(ItemInNs::Values(def)))
             .map(|(_, v)| v)
diff --git a/crates/ra_hir_ty/src/db.rs b/crates/ra_hir_ty/src/db.rs
index 1462b053f0..11fc2ac3d1 100644
--- a/crates/ra_hir_ty/src/db.rs
+++ b/crates/ra_hir_ty/src/db.rs
@@ -22,7 +22,6 @@ use hir_expand::name::Name;
 #[salsa::requires(salsa::Database)]
 pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
     #[salsa::invoke(infer_wait)]
-    #[salsa::transparent]
     fn infer(&self, def: DefWithBodyId) -> Arc<InferenceResult>;
 
     #[salsa::invoke(crate::infer::infer_query)]
diff --git a/crates/ra_ide_db/src/change.rs b/crates/ra_ide_db/src/change.rs
index 8446ef88e8..628cf6416a 100644
--- a/crates/ra_ide_db/src/change.rs
+++ b/crates/ra_ide_db/src/change.rs
@@ -311,7 +311,6 @@ impl RootDatabase {
             hir::db::MacroDefQuery
             hir::db::ParseMacroQuery
             hir::db::MacroExpandQuery
-            hir::db::InternEagerExpansionQuery
 
             // DefDatabase
             hir::db::RawItemsQuery
@@ -360,21 +359,14 @@ impl RootDatabase {
             hir::db::ImplsInCrateQuery
             hir::db::ImplsForTraitQuery
             hir::db::InternTypeCtorQuery
-            hir::db::InternTypeParamIdQuery
             hir::db::InternChalkImplQuery
             hir::db::InternAssocTyValueQuery
             hir::db::AssociatedTyDataQuery
+            hir::db::AssociatedTyValueQuery
+            hir::db::TraitSolveQuery
             hir::db::TraitDatumQuery
             hir::db::StructDatumQuery
             hir::db::ImplDatumQuery
-            hir::db::AssociatedTyValueQuery
-            hir::db::TraitSolveQuery
-
-            // SymbolsDatabase
-            crate::symbol_index::FileSymbolsQuery
-
-            // LineIndexDatabase
-            crate::LineIndexQuery
         ];
         acc.sort_by_key(|it| std::cmp::Reverse(it.1));
         acc
diff --git a/crates/ra_parser/src/grammar/expressions/atom.rs b/crates/ra_parser/src/grammar/expressions/atom.rs
index 0d277a5866..2335d99b3a 100644
--- a/crates/ra_parser/src/grammar/expressions/atom.rs
+++ b/crates/ra_parser/src/grammar/expressions/atom.rs
@@ -230,20 +230,14 @@ fn lambda_expr(p: &mut Parser) -> CompletedMarker {
     p.eat(T![async]);
     p.eat(T![move]);
     params::param_list_closure(p);
-    if opt_fn_ret_type(p) {
-        if p.at(T!['{']) {
-            // test lambda_ret_block
-            // fn main() { || -> i32 { 92 }(); }
-            block_expr(p, None);
-        } else {
-            p.error("expected `{`");
-        }
+    if opt_fn_ret_type(p) && !p.at(T!['{']) {
+        p.error("expected `{`");
+    }
+
+    if p.at_ts(EXPR_FIRST) {
+        expr(p);
     } else {
-        if p.at_ts(EXPR_FIRST) {
-            expr(p);
-        } else {
-            p.error("expected expression");
-        }
+        p.error("expected expression");
     }
     m.complete(p, LAMBDA_EXPR)
 }
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 1145b69e89..e296004395 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -250,6 +250,10 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken {
         .unwrap_or_else(|| panic!("unhandled token: {:?}", kind))
 }
 
+pub fn unreachable_macro_call() -> ast::MacroCall {
+    ast_from_text(&format!("unreachable!()"))
+}
+
 fn ast_from_text<N: AstNode>(text: &str) -> N {
     let parse = SourceFile::parse(text);
     let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 1033d6de94..1cc2f65712 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -734,29 +734,19 @@ pub fn handle_code_action(
         res.push(fix.action.clone());
     }
 
-    let mut grouped_assists: FxHashMap<String, (usize, Vec<Assist>)> = FxHashMap::default();
+    let mut grouped_assists: FxHashMap<String, Vec<Assist>> = FxHashMap::default();
     for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() {
         match &assist.group_label {
-            Some(label) => grouped_assists
-                .entry(label.to_owned())
-                .or_insert_with(|| {
-                    let idx = res.len();
-                    let dummy = Command::new(String::new(), String::new(), None);
-                    res.push(dummy.into());
-                    (idx, Vec::new())
-                })
-                .1
-                .push(assist),
-            None => {
-                res.push(create_single_code_action(assist, &world)?.into());
-            }
+            Some(label) => grouped_assists.entry(label.to_owned()).or_default().push(assist),
+            None => res.push(create_single_code_action(assist, &world)?.into()),
         }
     }
 
-    for (group_label, (idx, assists)) in grouped_assists {
+    for (group_label, assists) in grouped_assists {
         if assists.len() == 1 {
-            res[idx] =
-                create_single_code_action(assists.into_iter().next().unwrap(), &world)?.into();
+            res.push(
+                create_single_code_action(assists.into_iter().next().unwrap(), &world)?.into(),
+            );
         } else {
             let title = group_label;
 
@@ -770,15 +760,17 @@ pub fn handle_code_action(
                 command: "rust-analyzer.selectAndApplySourceChange".to_string(),
                 arguments: Some(vec![serde_json::Value::Array(arguments)]),
             });
-            res[idx] = CodeAction {
-                title,
-                kind: None,
-                diagnostics: None,
-                edit: None,
-                command,
-                is_preferred: None,
-            }
-            .into();
+            res.push(
+                CodeAction {
+                    title,
+                    kind: None,
+                    diagnostics: None,
+                    edit: None,
+                    command,
+                    is_preferred: None,
+                }
+                .into(),
+            );
         }
     }
 
diff --git a/docs/user/assists.md b/docs/user/assists.md
index f3ce6b0e0f..b2568a954f 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -597,6 +597,29 @@ use std::collections::HashMap;
 fn process(map: HashMap<String, String>) {}
 ```
 
+## `replace_unwrap_with_match`
+
+Replaces `unwrap` a `match` expression. Works for Result and Option.
+
+```rust
+// BEFORE
+enum Result<T, E> { Ok(T), Err(E) }
+fn main() {
+    let x: Result<i32, i32> = Result::Ok(92);
+    let y = x.┃unwrap();
+}
+
+// AFTER
+enum Result<T, E> { Ok(T), Err(E) }
+fn main() {
+    let x: Result<i32, i32> = Result::Ok(92);
+    let y = match x {
+        Ok(a) => a,
+        _ => unreachable!(),
+    };
+}
+```
+
 ## `split_import`
 
 Wraps the tail of import into braces.
diff --git a/editors/code/src/commands/analyzer_status.ts b/editors/code/src/commands/analyzer_status.ts
index 09daa3402c..1c6ea399b1 100644
--- a/editors/code/src/commands/analyzer_status.ts
+++ b/editors/code/src/commands/analyzer_status.ts
@@ -5,7 +5,7 @@ import { Ctx, Cmd } from '../ctx';
 
 // Shows status of rust-analyzer (for debugging)
 export function analyzerStatus(ctx: Ctx): Cmd {
-    let poller: NodeJS.Timer | undefined = undefined;
+    let poller: NodeJS.Timer | null = null;
     const tdcp = new TextDocumentContentProvider(ctx);
 
     ctx.pushCleanup(
@@ -17,32 +17,41 @@ export function analyzerStatus(ctx: Ctx): Cmd {
 
     ctx.pushCleanup({
         dispose() {
-            if (poller !== undefined) {
+            if (poller != null) {
                 clearInterval(poller);
             }
         },
     });
 
-    return async () => {
-        if (poller === undefined) {
+    return async function handle() {
+        if (poller == null) {
             poller = setInterval(() => tdcp.eventEmitter.fire(tdcp.uri), 1000);
         }
         const document = await vscode.workspace.openTextDocument(tdcp.uri);
-        return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
+        return vscode.window.showTextDocument(
+            document,
+            vscode.ViewColumn.Two,
+            true,
+        );
     };
 }
 
-class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
-    readonly uri = vscode.Uri.parse('rust-analyzer-status://status');
-    readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
+class TextDocumentContentProvider
+    implements vscode.TextDocumentContentProvider {
+    uri = vscode.Uri.parse('rust-analyzer-status://status');
+    eventEmitter = new vscode.EventEmitter<vscode.Uri>();
 
     constructor(private readonly ctx: Ctx) {
     }
 
-    provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
-        if (!vscode.window.activeTextEditor) return '';
+    provideTextDocumentContent(
+        _uri: vscode.Uri,
+    ): vscode.ProviderResult<string> {
+        const editor = vscode.window.activeTextEditor;
+        const client = this.ctx.client;
+        if (!editor || !client) return '';
 
-        return this.ctx.client.sendRequest(ra.analyzerStatus, null);
+        return client.sendRequest(ra.analyzerStatus, null);
     }
 
     get onDidChange(): vscode.Event<vscode.Uri> {


Test patch:
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index 62dcb38089..543224232f 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -622,6 +622,30 @@ fn process(map: HashMap<String, String>) {}
     )
 }
 
+#[test]
+fn doctest_replace_unwrap_with_match() {
+    check(
+        "replace_unwrap_with_match",
+        r#####"
+enum Result<T, E> { Ok(T), Err(E) }
+fn main() {
+    let x: Result<i32, i32> = Result::Ok(92);
+    let y = x.<|>unwrap();
+}
+"#####,
+        r#####"
+enum Result<T, E> { Ok(T), Err(E) }
+fn main() {
+    let x: Result<i32, i32> = Result::Ok(92);
+    let y = match x {
+        Ok(a) => a,
+        _ => unreachable!(),
+    };
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_split_import() {
     check(
diff --git a/crates/ra_hir_def/src/nameres/tests/incremental.rs b/crates/ra_hir_def/src/nameres/tests/incremental.rs
index 496fc6b081..83f429c291 100644
--- a/crates/ra_hir_def/src/nameres/tests/incremental.rs
+++ b/crates/ra_hir_def/src/nameres/tests/incremental.rs
@@ -4,8 +4,8 @@ use ra_db::SourceDatabaseExt;
 
 use super::*;
 
-fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change: &str) {
-    let (mut db, pos) = TestDB::with_position(ra_fixture_initial);
+fn check_def_map_is_not_recomputed(initial: &str, file_change: &str) {
+    let (mut db, pos) = TestDB::with_position(initial);
     let krate = db.test_crate();
     {
         let events = db.log_executed(|| {
@@ -13,7 +13,7 @@ fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change:
         });
         assert!(format!("{:?}", events).contains("crate_def_map"), "{:#?}", events)
     }
-    db.set_file_text(pos.file_id, Arc::new(ra_fixture_change.to_string()));
+    db.set_file_text(pos.file_id, Arc::new(file_change.to_string()));
 
     {
         let events = db.log_executed(|| {
@@ -26,7 +26,7 @@ fn check_def_map_is_not_recomputed(ra_fixture_initial: &str, ra_fixture_change:
 #[test]
 fn typing_inside_a_function_should_not_invalidate_def_map() {
     check_def_map_is_not_recomputed(
-        r"
+        "
         //- /lib.rs
         mod foo;<|>
 
@@ -41,7 +41,7 @@ fn typing_inside_a_function_should_not_invalidate_def_map() {
         //- /foo/bar.rs
         pub struct Baz;
         ",
-        r"
+        "
         mod foo;
 
         use crate::foo::bar::Baz;
@@ -54,7 +54,7 @@ fn typing_inside_a_function_should_not_invalidate_def_map() {
 #[test]
 fn adding_inner_items_should_not_invalidate_def_map() {
     check_def_map_is_not_recomputed(
-        r"
+        "
         //- /lib.rs
         struct S { a: i32}
         enum E { A }
@@ -72,7 +72,7 @@ fn adding_inner_items_should_not_invalidate_def_map() {
         //- /foo/bar.rs
         pub struct Baz;
         ",
-        r"
+        "
         struct S { a: i32, b: () }
         enum E { A, B }
         trait T {
@@ -92,7 +92,7 @@ fn adding_inner_items_should_not_invalidate_def_map() {
 #[test]
 fn typing_inside_a_macro_should_not_invalidate_def_map() {
     let (mut db, pos) = TestDB::with_position(
-        r"
+        "
         //- /lib.rs
         macro_rules! m {
             ($ident:ident) => {
diff --git a/crates/ra_syntax/test_data/parser/err/0010_unsafe_lambda_block.txt b/crates/ra_syntax/test_data/parser/err/0010_unsafe_lambda_block.txt
index 0ffbd25aa8..e0edf6a2d8 100644
--- a/crates/ra_syntax/test_data/parser/err/0010_unsafe_lambda_block.txt
+++ b/crates/ra_syntax/test_data/parser/err/0010_unsafe_lambda_block.txt
@@ -12,8 +12,8 @@ SOURCE_FILE@[0; 42)
       BLOCK@[10; 41)
         L_CURLY@[10; 11) "{"
         WHITESPACE@[11; 16) "\n    "
-        EXPR_STMT@[16; 24)
-          LAMBDA_EXPR@[16; 24)
+        EXPR_STMT@[16; 39)
+          LAMBDA_EXPR@[16; 38)
             PARAM_LIST@[16; 18)
               PIPE@[16; 17) "|"
               PIPE@[17; 18) "|"
@@ -24,22 +24,20 @@ SOURCE_FILE@[0; 42)
               TUPLE_TYPE@[22; 24)
                 L_PAREN@[22; 23) "("
                 R_PAREN@[23; 24) ")"
-        WHITESPACE@[24; 25) " "
-        EXPR_STMT@[25; 39)
-          BLOCK_EXPR@[25; 38)
-            UNSAFE_KW@[25; 31) "unsafe"
-            WHITESPACE@[31; 32) " "
-            BLOCK@[32; 38)
-              L_CURLY@[32; 33) "{"
-              WHITESPACE@[33; 34) " "
-              TUPLE_EXPR@[34; 36)
-                L_PAREN@[34; 35) "("
-                R_PAREN@[35; 36) ")"
-              WHITESPACE@[36; 37) " "
-              R_CURLY@[37; 38) "}"
+            WHITESPACE@[24; 25) " "
+            BLOCK_EXPR@[25; 38)
+              UNSAFE_KW@[25; 31) "unsafe"
+              WHITESPACE@[31; 32) " "
+              BLOCK@[32; 38)
+                L_CURLY@[32; 33) "{"
+                WHITESPACE@[33; 34) " "
+                TUPLE_EXPR@[34; 36)
+                  L_PAREN@[34; 35) "("
+                  R_PAREN@[35; 36) ")"
+                WHITESPACE@[36; 37) " "
+                R_CURLY@[37; 38) "}"
           SEMI@[38; 39) ";"
         WHITESPACE@[39; 40) "\n"
         R_CURLY@[40; 41) "}"
   WHITESPACE@[41; 42) "\n"
 error [24; 24): expected `{`
-error [24; 24): expected SEMI
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0158_lambda_ret_block.rs b/crates/ra_syntax/test_data/parser/inline/ok/0158_lambda_ret_block.rs
deleted file mode 100644
index 061118d3aa..0000000000
--- a/crates/ra_syntax/test_data/parser/inline/ok/0158_lambda_ret_block.rs
+++ /dev/null
@@ -1,1 +0,0 @@
-fn main() { || -> i32 { 92 }(); }
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0158_lambda_ret_block.txt b/crates/ra_syntax/test_data/parser/inline/ok/0158_lambda_ret_block.txt
deleted file mode 100644
index ba8779094f..0000000000
--- a/crates/ra_syntax/test_data/parser/inline/ok/0158_lambda_ret_block.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-SOURCE_FILE@[0; 34)
-  FN_DEF@[0; 33)
-    FN_KW@[0; 2) "fn"
-    WHITESPACE@[2; 3) " "
-    NAME@[3; 7)
-      IDENT@[3; 7) "main"
-    PARAM_LIST@[7; 9)
-      L_PAREN@[7; 8) "("
-      R_PAREN@[8; 9) ")"
-    WHITESPACE@[9; 10) " "
-    BLOCK_EXPR@[10; 33)
-      BLOCK@[10; 33)
-        L_CURLY@[10; 11) "{"
-        WHITESPACE@[11; 12) " "
-        EXPR_STMT@[12; 31)
-          CALL_EXPR@[12; 30)
-            LAMBDA_EXPR@[12; 28)
-              PARAM_LIST@[12; 14)
-                PIPE@[12; 13) "|"
-                PIPE@[13; 14) "|"
-              WHITESPACE@[14; 15) " "
-              RET_TYPE@[15; 21)
-                THIN_ARROW@[15; 17) "->"
-                WHITESPACE@[17; 18) " "
-                PATH_TYPE@[18; 21)
-                  PATH@[18; 21)
-                    PATH_SEGMENT@[18; 21)
-                      NAME_REF@[18; 21)
-                        IDENT@[18; 21) "i32"
-              WHITESPACE@[21; 22) " "
-              BLOCK_EXPR@[22; 28)
-                BLOCK@[22; 28)
-                  L_CURLY@[22; 23) "{"
-                  WHITESPACE@[23; 24) " "
-                  LITERAL@[24; 26)
-                    INT_NUMBER@[24; 26) "92"
-                  WHITESPACE@[26; 27) " "
-                  R_CURLY@[27; 28) "}"
-            ARG_LIST@[28; 30)
-              L_PAREN@[28; 29) "("
-              R_PAREN@[29; 30) ")"
-          SEMI@[30; 31) ";"
-        WHITESPACE@[31; 32) " "
-        R_CURLY@[32; 33) "}"
-  WHITESPACE@[33; 34) "\n"