Repository: rust-analyzer/rust-analyzer
Pull request #11629 (2022-03-05 21:04:32)

fix: Fix macro-calls expanding to items in if/while conditions
Fixes https://github.com/rust-analyzer/rust-analyzer/issues/11617


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 9cc5a73c96..64654c9961 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1668,7 +1668,6 @@ checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a"
 name = "text_edit"
 version = "0.0.0"
 dependencies = [
- "itertools",
  "text-size",
 ]
 
@@ -1833,9 +1832,9 @@ checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
 
 [[package]]
 name = "ungrammar"
-version = "1.16.0"
+version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a62374cbbda72c1459ce5e7bfcdf1bd284c812a4faf2324aa083e5d9ea87880f"
+checksum = "ed01567101450f7d600508e7680df6005ae4fe97119d79b0364cc5910ff39732"
 
 [[package]]
 name = "unicase"
diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs
index b7ea42236e..d411b9d71b 100644
--- a/crates/hir/src/display.rs
+++ b/crates/hir/src/display.rs
@@ -86,11 +86,6 @@ impl HirDisplay for Function {
             // The former will ignore lifetime arguments currently.
             type_ref.hir_fmt(f)?;
         }
-
-        if data.is_varargs() {
-            write!(f, ", ...")?;
-        }
-
         write!(f, ")")?;
 
         // `FunctionData::ret_type` will be `::core::future::Future<Output = ...>` for async fns.
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 60e3548d49..45a4c6a50e 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2036,11 +2036,6 @@ impl GenericDef {
     }
 }
 
-/// A single local definition.
-///
-/// If the definition of this is part of a "MultiLocal", that is a local that has multiple declarations due to or-patterns
-/// then this only references a single one of those.
-/// To retrieve the other locals you should use [`Local::associated_locals`]
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub struct Local {
     pub(crate) parent: DefWithBodyId,
@@ -2112,28 +2107,12 @@ impl Local {
         Type::new(db, krate, def, ty)
     }
 
-    pub fn associated_locals(self, db: &dyn HirDatabase) -> Box<[Local]> {
-        let body = db.body(self.parent);
-        body.ident_patterns_for(&self.pat_id)
-            .iter()
-            .map(|&pat_id| Local { parent: self.parent, pat_id })
-            .collect()
-    }
-
-    /// If this local is part of a multi-local, retrieve the representative local.
-    /// That is the local that references are being resolved to.
-    pub fn representative(self, db: &dyn HirDatabase) -> Local {
-        let body = db.body(self.parent);
-        Local { pat_id: body.pattern_representative(self.pat_id), ..self }
-    }
-
     pub fn source(self, db: &dyn HirDatabase) -> InFile<Either<ast::IdentPat, ast::SelfParam>> {
         let (_body, source_map) = db.body_with_source_map(self.parent);
         let src = source_map.pat_syntax(self.pat_id).unwrap(); // Hmm...
         let root = src.file_syntax(db.upcast());
-        src.map(|ast| match ast {
-            Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
-            Either::Right(it) => Either::Right(it.to_node(&root)),
+        src.map(|ast| {
+            ast.map_left(|it| it.cast().unwrap().to_node(&root)).map_right(|it| it.to_node(&root))
         })
     }
 }
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index 6d3c2c2c46..a2f64eda06 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -241,7 +241,6 @@ pub struct Mark {
 pub struct Body {
     pub exprs: Arena<Expr>,
     pub pats: Arena<Pat>,
-    pub or_pats: FxHashMap<PatId, Arc<[PatId]>>,
     pub labels: Arena<Label>,
     /// The patterns for the function's parameters. While the parameter types are
     /// part of the function signature, the patterns are not (they don't change
@@ -353,19 +352,7 @@ impl Body {
     ) -> impl Iterator<Item = (BlockId, Arc<DefMap>)> + '_ {
         self.block_scopes
             .iter()
-            .map(move |&block| (block, db.block_def_map(block).expect("block ID without DefMap")))
-    }
-
-    pub fn pattern_representative(&self, pat: PatId) -> PatId {
-        self.or_pats.get(&pat).and_then(|pats| pats.first().copied()).unwrap_or(pat)
-    }
-
-    /// Retrieves all ident patterns this pattern shares the ident with.
-    pub fn ident_patterns_for<'slf>(&'slf self, pat: &'slf PatId) -> &'slf [PatId] {
-        match self.or_pats.get(pat) {
-            Some(pats) => &**pats,
-            None => std::slice::from_ref(pat),
-        }
+            .map(move |block| (*block, db.block_def_map(*block).expect("block ID without DefMap")))
     }
 
     fn new(
@@ -378,9 +365,8 @@ impl Body {
     }
 
     fn shrink_to_fit(&mut self) {
-        let Self { _c: _, body_expr: _, block_scopes, or_pats, exprs, labels, params, pats } = self;
+        let Self { _c: _, body_expr: _, block_scopes, exprs, labels, params, pats } = self;
         block_scopes.shrink_to_fit();
-        or_pats.shrink_to_fit();
         exprs.shrink_to_fit();
         labels.shrink_to_fit();
         params.shrink_to_fit();
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index f86f4f3c85..195f21cedc 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -12,7 +12,6 @@ use hir_expand::{
 };
 use la_arena::Arena;
 use profile::Count;
-use rustc_hash::FxHashMap;
 use syntax::{
     ast::{
         self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind,
@@ -93,12 +92,9 @@ pub(super) fn lower(
             body_expr: dummy_expr_id(),
             block_scopes: Vec::new(),
             _c: Count::new(),
-            or_pats: Default::default(),
         },
         expander,
         statements_in_scope: Vec::new(),
-        name_to_pat_grouping: Default::default(),
-        is_lowering_inside_or_pat: false,
     }
     .collect(params, body)
 }
@@ -109,9 +105,6 @@ struct ExprCollector<'a> {
     body: Body,
     source_map: BodySourceMap,
     statements_in_scope: Vec<Statement>,
-    // a poor-mans union-find?
-    name_to_pat_grouping: FxHashMap<Name, Vec<PatId>>,
-    is_lowering_inside_or_pat: bool,
 }
 
 impl ExprCollector<'_> {
@@ -507,14 +500,14 @@ impl ExprCollector<'_> {
             }
             ast::Expr::MacroCall(e) => {
                 let macro_ptr = AstPtr::new(&e);
-                let mut ids = vec![];
+                let mut ids = None;
                 self.collect_macro_call(e, macro_ptr, true, |this, expansion| {
-                    ids.push(match expansion {
+                    ids.get_or_insert(match expansion {
                         Some(it) => this.collect_expr(it),
                         None => this.alloc_expr(Expr::Missing, syntax_ptr.clone()),
-                    })
+                    });
                 });
-                ids[0]
+                ids.unwrap_or_else(|| self.alloc_expr(Expr::Missing, syntax_ptr.clone()))
             }
             ast::Expr::MacroStmts(e) => {
                 e.statements().for_each(|s| self.collect_stmt(s));
@@ -525,13 +518,12 @@ impl ExprCollector<'_> {
 
                 self.alloc_expr(Expr::MacroStmts { tail }, syntax_ptr)
             }
-            ast::Expr::UnderscoreExpr(_) => return None,
         })
     }
 
     fn collect_macro_call<F: FnMut(&mut Self, Option<T>), T: ast::AstNode>(
         &mut self,
-        e: ast::MacroCall,
+        mcall: ast::MacroCall,
         syntax_ptr: AstPtr<ast::MacroCall>,
         record_diagnostics: bool,
         mut collector: F,
@@ -539,8 +531,8 @@ impl ExprCollector<'_> {
         // File containing the macro call. Expansion errors will be attached here.
         let outer_file = self.expander.current_file_id;
 
-        let macro_call = self.expander.to_source(AstPtr::new(&e));
-        let res = self.expander.enter_expand(self.db, e);
+        let macro_call_ptr = self.expander.to_source(AstPtr::new(&mcall));
+        let res = self.expander.enter_expand(self.db, mcall);
 
         let res = match res {
             Ok(res) => res,
@@ -575,7 +567,7 @@ impl ExprCollector<'_> {
 
         match res.value {
             Some((mark, expansion)) => {
-                self.source_map.expansions.insert(macro_call, self.expander.current_file_id);
+                self.source_map.expansions.insert(macro_call_ptr, self.expander.current_file_id);
 
                 let id = collector(self, Some(expansion));
                 self.expander.exit(self.db, mark);
@@ -712,32 +704,13 @@ impl ExprCollector<'_> {
     }
 
     fn collect_pat(&mut self, pat: ast::Pat) -> PatId {
-        let pat_id = self.collect_pat_(pat);
-        for (_, pats) in self.name_to_pat_grouping.drain() {
-            let pats = Arc::<[_]>::from(pats);
-            self.body.or_pats.extend(pats.iter().map(|&pat| (pat, pats.clone())));
-        }
-        self.is_lowering_inside_or_pat = false;
-        pat_id
-    }
-
-    fn collect_pat_opt(&mut self, pat: Option<ast::Pat>) -> PatId {
-        match pat {
-            Some(pat) => self.collect_pat(pat),
-            None => self.missing_pat(),
-        }
-    }
-
-    fn collect_pat_(&mut self, pat: ast::Pat) -> PatId {
         let pattern = match &pat {
             ast::Pat::IdentPat(bp) => {
                 let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
-
-                let key = self.is_lowering_inside_or_pat.then(|| name.clone());
                 let annotation =
                     BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some());
-                let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat));
-                let pattern = if annotation == BindingAnnotation::Unannotated && subpat.is_none() {
+                let subpat = bp.pat().map(|subpat| self.collect_pat(subpat));
+                if annotation == BindingAnnotation::Unannotated && subpat.is_none() {
                     // This could also be a single-segment path pattern. To
                     // decide that, we need to try resolving the name.
                     let (resolved, _) = self.expander.def_map.resolve_path(
@@ -767,14 +740,7 @@ impl ExprCollector<'_> {
                     }
                 } else {
                     Pat::Bind { name, mode: annotation, subpat }
-                };
-
-                let ptr = AstPtr::new(&pat);
-                let pat = self.alloc_pat(pattern, Either::Left(ptr));
-                if let Some(key) = key {
-                    self.name_to_pat_grouping.entry(key).or_default().push(pat);
                 }
-                return pat;
             }
             ast::Pat::TupleStructPat(p) => {
                 let path =
@@ -793,11 +759,10 @@ impl ExprCollector<'_> {
                 path.map(Pat::Path).unwrap_or(Pat::Missing)
             }
             ast::Pat::OrPat(p) => {
-                self.is_lowering_inside_or_pat = true;
-                let pats = p.pats().map(|p| self.collect_pat_(p)).collect();
+                let pats = p.pats().map(|p| self.collect_pat(p)).collect();
                 Pat::Or(pats)
             }
-            ast::Pat::ParenPat(p) => return self.collect_pat_opt_(p.pat()),
+            ast::Pat::ParenPat(p) => return self.collect_pat_opt(p.pat()),
             ast::Pat::TuplePat(p) => {
                 let (args, ellipsis) = self.collect_tuple_pat(p.fields());
                 Pat::Tuple { args, ellipsis }
@@ -812,7 +777,7 @@ impl ExprCollector<'_> {
                     .fields()
                     .filter_map(|f| {
                         let ast_pat = f.pat()?;
-                        let pat = self.collect_pat_(ast_pat);
+                        let pat = self.collect_pat(ast_pat);
                         let name = f.field_name()?.as_name();
                         Some(RecordFieldPat { name, pat })
                     })
@@ -831,9 +796,9 @@ impl ExprCollector<'_> {
 
                 // FIXME properly handle `RestPat`
                 Pat::Slice {
-                    prefix: prefix.into_iter().map(|p| self.collect_pat_(p)).collect(),
-                    slice: slice.map(|p| self.collect_pat_(p)),
-                    suffix: suffix.into_iter().map(|p| self.collect_pat_(p)).collect(),
+                    prefix: prefix.into_iter().map(|p| self.collect_pat(p)).collect(),
+                    slice: slice.map(|p| self.collect_pat(p)),
+                    suffix: suffix.into_iter().map(|p| self.collect_pat(p)).collect(),
                 }
             }
             ast::Pat::LiteralPat(lit) => {
@@ -856,7 +821,7 @@ impl ExprCollector<'_> {
                 Pat::Missing
             }
             ast::Pat::BoxPat(boxpat) => {
-                let inner = self.collect_pat_opt_(boxpat.pat());
+                let inner = self.collect_pat_opt(boxpat.pat());
                 Pat::Box { inner }
             }
             ast::Pat::ConstBlockPat(const_block_pat) => {
@@ -872,7 +837,7 @@ impl ExprCollector<'_> {
                     let macro_ptr = AstPtr::new(&call);
                     let mut pat = None;
                     self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| {
-                        pat = Some(this.collect_pat_opt_(expanded_pat));
+                        pat = Some(this.collect_pat_opt(expanded_pat));
                     });
 
                     match pat {
@@ -889,9 +854,9 @@ impl ExprCollector<'_> {
         self.alloc_pat(pattern, Either::Left(ptr))
     }
 
-    fn collect_pat_opt_(&mut self, pat: Option<ast::Pat>) -> PatId {
+    fn collect_pat_opt(&mut self, pat: Option<ast::Pat>) -> PatId {
         match pat {
-            Some(pat) => self.collect_pat_(pat),
+            Some(pat) => self.collect_pat(pat),
             None => self.missing_pat(),
         }
     }
@@ -903,7 +868,7 @@ impl ExprCollector<'_> {
         // We want to skip the `..` pattern here, since we account for it above.
         let args = args
             .filter(|p| !matches!(p, ast::Pat::RestPat(_)))
-            .map(|p| self.collect_pat_(p))
+            .map(|p| self.collect_pat(p))
             .collect();
 
         (args, ellipsis)
@@ -958,10 +923,7 @@ impl From<ast::LiteralKind> for Literal {
                 let text = bs.value().map(Box::from).unwrap_or_else(Default::default);
                 Literal::ByteString(text)
             }
-            LiteralKind::String(s) => {
-                let text = s.value().map(Box::from).unwrap_or_else(Default::default);
-                Literal::String(text)
-            }
+            LiteralKind::String(_) => Literal::String(Default::default()),
             LiteralKind::Byte => Literal::Uint(Default::default(), Some(BuiltinUint::U8)),
             LiteralKind::Bool(val) => Literal::Bool(val),
             LiteralKind::Char => Literal::Char(Default::default()),
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index ba0f101512..bdb01ca170 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -890,19 +890,27 @@ impl ExpandTo {
             MACRO_PAT => ExpandTo::Pattern,
             MACRO_TYPE => ExpandTo::Type,
 
-            ARG_LIST | TRY_EXPR | TUPLE_EXPR | PAREN_EXPR | ARRAY_EXPR | FOR_EXPR | PATH_EXPR
-            | CLOSURE_EXPR | BREAK_EXPR | RETURN_EXPR | MATCH_EXPR | MATCH_ARM | MATCH_GUARD
-            | RECORD_EXPR_FIELD | CALL_EXPR | INDEX_EXPR | METHOD_CALL_EXPR | FIELD_EXPR
-            | AWAIT_EXPR | CAST_EXPR | REF_EXPR | PREFIX_EXPR | RANGE_EXPR | BIN_EXPR
-            | LET_EXPR => ExpandTo::Expr,
-            LET_STMT => {
-                // FIXME: Handle LHS Pattern
-                ExpandTo::Expr
-            }
-
+            ARG_LIST | ARRAY_EXPR | AWAIT_EXPR | BIN_EXPR | BREAK_EXPR | CALL_EXPR | CAST_EXPR
+            | CLOSURE_EXPR | FIELD_EXPR | FOR_EXPR | IF_EXPR | INDEX_EXPR | LET_EXPR
+            | MATCH_ARM | MATCH_EXPR | MATCH_GUARD | METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR
+            | PREFIX_EXPR | RANGE_EXPR | RECORD_EXPR_FIELD | REF_EXPR | RETURN_EXPR | TRY_EXPR
+            | TUPLE_EXPR | WHILE_EXPR => ExpandTo::Expr,
             _ => {
-                // Unknown , Just guess it is `Items`
-                ExpandTo::Items
+                match ast::LetStmt::cast(parent) {
+                    Some(let_stmt) => {
+                        if let Some(true) = let_stmt.initializer().map(|it| it.syntax() == syn) {
+                            ExpandTo::Expr
+                        } else if let Some(true) = let_stmt.ty().map(|it| it.syntax() == syn) {
+                            ExpandTo::Type
+                        } else {
+                            ExpandTo::Pattern
+                        }
+                    }
+                    None => {
+                        // Unknown , Just guess it is `Items`
+                        ExpandTo::Items
+                    }
+                }
             }
         }
     }
diff --git a/crates/hir_ty/src/infer/unify.rs b/crates/hir_ty/src/infer/unify.rs
index 8a6c34b493..21b48b9d80 100644
--- a/crates/hir_ty/src/infer/unify.rs
+++ b/crates/hir_ty/src/infer/unify.rs
@@ -140,8 +140,7 @@ pub(crate) struct InferenceTable<'a> {
 
 pub(crate) struct InferenceTableSnapshot {
     var_table_snapshot: chalk_solve::infer::InferenceSnapshot<Interner>,
-    // FIXME: snapshot pending_obligations?
-    type_variable_table_snapshot: Vec<TypeVariableData>,
+    // FIXME: snapshot type_variable_table, pending_obligations?
 }
 
 impl<'a> InferenceTable<'a> {
@@ -357,14 +356,12 @@ impl<'a> InferenceTable<'a> {
     }
 
     pub(crate) fn snapshot(&mut self) -> InferenceTableSnapshot {
-        let var_table_snapshot = self.var_unification_table.snapshot();
-        let type_variable_table_snapshot = self.type_variable_table.clone();
-        InferenceTableSnapshot { var_table_snapshot, type_variable_table_snapshot }
+        let snapshot = self.var_unification_table.snapshot();
+        InferenceTableSnapshot { var_table_snapshot: snapshot }
     }
 
     pub(crate) fn rollback_to(&mut self, snapshot: InferenceTableSnapshot) {
         self.var_unification_table.rollback_to(snapshot.var_table_snapshot);
-        self.type_variable_table = snapshot.type_variable_table_snapshot;
     }
 
     /// Checks an obligation without registering it. Useful mostly to check
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index 5724090e63..f886ff7837 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -98,37 +98,24 @@ fn highlight_references(
             range,
             category: access,
         });
-    let mut res = FxHashSet::default();
 
-    let mut def_to_hl_range = |def| {
-        let hl_range = match def {
-            Definition::Module(module) => {
+    let declarations = defs.iter().flat_map(|def| {
+        match def {
+            &Definition::Module(module) => {
                 Some(NavigationTarget::from_module_to_decl(sema.db, module))
             }
             def => def.try_to_nav(sema.db),
         }
         .filter(|decl| decl.file_id == file_id)
-        .and_then(|decl| decl.focus_range)
-        .map(|range| {
+        .and_then(|decl| {
+            let range = decl.focus_range?;
             let category =
                 references::decl_mutability(&def, node, range).then(|| ReferenceCategory::Write);
-            HighlightedRange { range, category }
-        });
-        if let Some(hl_range) = hl_range {
-            res.insert(hl_range);
-        }
-    };
-    for &def in &defs {
-        match def {
-            Definition::Local(local) => local
-                .associated_locals(sema.db)
-                .iter()
-                .for_each(|&local| def_to_hl_range(Definition::Local(local))),
-            def => def_to_hl_range(def),
-        }
-    }
+            Some(HighlightedRange { range, category })
+        })
+    });
 
-    res.extend(usages);
+    let res: FxHashSet<_> = declarations.chain(usages).collect();
     if res.is_empty() {
         None
     } else {
@@ -345,7 +332,6 @@ mod tests {
 
     use super::*;
 
-    #[track_caller]
     fn check(ra_fixture: &str) {
         let config = HighlightRelatedConfig {
             break_points: true,
@@ -357,7 +343,6 @@ mod tests {
         check_with_config(ra_fixture, config);
     }
 
-    #[track_caller]
     fn check_with_config(ra_fixture: &str, config: HighlightRelatedConfig) {
         let (analysis, pos, annotations) = fixture::annotations(ra_fixture);
 
@@ -1068,15 +1053,13 @@ fn function(field: u32) {
             yield_points: true,
         };
 
-        check_with_config(
-            r#"
+        let ra_fixture = r#"
 fn foo() {
     let x$0 = 5;
     let y = x * 2;
-}
-"#,
-            config,
-        );
+}"#;
+
+        check_with_config(ra_fixture, config);
     }
 
     #[test]
@@ -1088,8 +1071,7 @@ fn foo() {
             yield_points: true,
         };
 
-        check_with_config(
-            r#"
+        let ra_fixture = r#"
 fn foo() {
     let x$0 = 5;
     let y = x * 2;
@@ -1097,13 +1079,11 @@ fn foo() {
     loop {
         break;
     }
-}
-"#,
-            config.clone(),
-        );
+}"#;
 
-        check_with_config(
-            r#"
+        check_with_config(ra_fixture, config.clone());
+
+        let ra_fixture = r#"
 fn foo() {
     let x = 5;
     let y = x * 2;
@@ -1113,10 +1093,9 @@ fn foo() {
         break;
 //      ^^^^^
     }
-}
-"#,
-            config,
-        );
+}"#;
+
+        check_with_config(ra_fixture, config);
     }
 
     #[test]
@@ -1128,20 +1107,17 @@ fn foo() {
             yield_points: true,
         };
 
-        check_with_config(
-            r#"
+        let ra_fixture = r#"
 async fn foo() {
     let x$0 = 5;
     let y = x * 2;
 
     0.await;
-}
-"#,
-            config.clone(),
-        );
+}"#;
 
-        check_with_config(
-            r#"
+        check_with_config(ra_fixture, config.clone());
+
+        let ra_fixture = r#"
     async fn foo() {
 //  ^^^^^
         let x = 5;
@@ -1149,10 +1125,9 @@ async fn foo() {
 
         0.await$0;
 //        ^^^^^
-}
-"#,
-            config,
-        );
+}"#;
+
+        check_with_config(ra_fixture, config);
     }
 
     #[test]
@@ -1164,8 +1139,7 @@ async fn foo() {
             yield_points: true,
         };
 
-        check_with_config(
-            r#"
+        let ra_fixture = r#"
 fn foo() -> i32 {
     let x$0 = 5;
     let y = x * 2;
@@ -1175,13 +1149,11 @@ fn foo() -> i32 {
     }
 
     0?
-}
-"#,
-            config.clone(),
-        );
+}"#;
 
-        check_with_config(
-            r#"
+        check_with_config(ra_fixture, config.clone());
+
+        let ra_fixture = r#"
 fn foo() ->$0 i32 {
     let x = 5;
     let y = x * 2;
@@ -1193,9 +1165,9 @@ fn foo() ->$0 i32 {
 
     0?
 //   ^
-"#,
-            config,
-        );
+"#;
+
+        check_with_config(ra_fixture, config);
     }
 
     #[test]
@@ -1207,16 +1179,14 @@ fn foo() ->$0 i32 {
             yield_points: true,
         };
 
-        check_with_config(
-            r#"
+        let ra_fixture = r#"
 fn foo() {
     loop {
         break$0;
     }
-}
-"#,
-            config,
-        );
+}"#;
+
+        check_with_config(ra_fixture, config);
     }
 
     #[test]
@@ -1228,14 +1198,12 @@ fn foo() {
             yield_points: false,
         };
 
-        check_with_config(
-            r#"
+        let ra_fixture = r#"
 async$0 fn foo() {
     0.await;
-}
-"#,
-            config,
-        );
+}"#;
+
+        check_with_config(ra_fixture, config);
     }
 
     #[test]
@@ -1247,68 +1215,15 @@ async$0 fn foo() {
             yield_points: true,
         };
 
-        check_with_config(
-            r#"
+        let ra_fixture = r#"
 fn foo() ->$0 i32 {
     if true {
         return -1;
     }
 
     42
-}"#,
-            config,
-        );
-    }
+}"#;
 
-    #[test]
-    fn test_hl_multi_local() {
-        check(
-            r#"
-fn foo((
-    foo$0
-  //^^^
-    | foo
-    //^^^
-    | foo
-    //^^^
-): ()) {
-    foo;
-  //^^^read
-    let foo;
-}
-"#,
-        );
-        check(
-            r#"
-fn foo((
-    foo
-  //^^^
-    | foo$0
-    //^^^
-    | foo
-    //^^^
-): ()) {
-    foo;
-  //^^^read
-    let foo;
-}
-"#,
-        );
-        check(
-            r#"
-fn foo((
-    foo
-  //^^^
-    | foo
-    //^^^
-    | foo
-    //^^^
-): ()) {
-    foo$0;
-  //^^^read
-    let foo;
-}
-"#,
-        );
+        check_with_config(ra_fixture, config);
     }
 }
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index 83bc299adc..daba317e42 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -2084,53 +2084,4 @@ fn foo() {
 "#,
         )
     }
-
-    #[test]
-    fn rename_multi_local() {
-        check(
-            "bar",
-            r#"
-fn foo((foo$0 | foo | foo): ()) {
-    foo;
-    let foo;
-}
-"#,
-            r#"
-fn foo((bar | bar | bar): ()) {
-    bar;
-    let foo;
-}
-"#,
-        );
-        check(
-            "bar",
-            r#"
-fn foo((foo | foo$0 | foo): ()) {
-    foo;
-    let foo;
-}
-"#,
-            r#"
-fn foo((bar | bar | bar): ()) {
-    bar;
-    let foo;
-}
-"#,
-        );
-        check(
-            "bar",
-            r#"
-fn foo((foo | foo | foo): ()) {
-    foo$0;
-    let foo;
-}
-"#,
-            r#"
-fn foo((bar | bar | bar): ()) {
-    bar;
-    let foo;
-}
-"#,
-        );
-    }
 }
diff --git a/crates/ide_assists/src/handlers/extract_module.rs b/crates/ide_assists/src/handlers/extract_module.rs
index 880617dd38..64875adfae 100644
--- a/crates/ide_assists/src/handlers/extract_module.rs
+++ b/crates/ide_assists/src/handlers/extract_module.rs
@@ -179,7 +179,7 @@ impl Module {
         //Here impl is not included as each item inside impl will be tied to the parent of
         //implementing block(a struct, enum, etc), if the parent is in selected module, it will
         //get updated by ADT section given below or if it is not, then we dont need to do any operation
-        self.body_items.iter().cloned().for_each(|item| {
+        self.body_items.clone().into_iter().for_each(|item| {
             match_ast! {
                 match (item.syntax()) {
                     ast::Adt(it) => {
@@ -240,11 +240,6 @@ impl Module {
                             self.expand_and_group_usages_file_wise(ctx, node_def, &mut refs);
                         }
                     },
-                    ast::Macro(it) => {
-                        if let Some(nod) = ctx.sema.to_def(&it) {
-                            self.expand_and_group_usages_file_wise(ctx, Definition::Macro(nod), &mut refs);
-                        }
-                    },
                     _ => (),
                 }
             }
@@ -786,6 +781,7 @@ fn get_replacements_for_visibilty_change(
             ast::Item::Fn(it) => replacements.push((it.visibility(), it.syntax().clone())),
             //Associated item's visibility should not be changed
             ast::Item::Impl(it) if it.for_token().is_none() => impls.push(it),
+            ast::Item::MacroRules(it) => replacements.push((it.visibility(), it.syntax().clone())),
             ast::Item::MacroDef(it) => replacements.push((it.visibility(), it.syntax().clone())),
             ast::Item::Module(it) => replacements.push((it.visibility(), it.syntax().clone())),
             ast::Item::Static(it) => replacements.push((it.visibility(), it.syntax().clone())),
@@ -1380,27 +1376,6 @@ mod modname {
         )
     }
 
-    #[test]
-    fn test_extract_module_macro_rules() {
-        check_assist(
-            extract_module,
-            r"
-$0macro_rules! m {
-    () => {};
-}$0
-m! {}
-            ",
-            r"
-mod modname {
-    macro_rules! m {
-        () => {};
-    }
-}
-modname::m! {}
-            ",
-        );
-    }
-
     #[test]
     fn test_do_not_apply_visibility_modifier_to_trait_impl_items() {
         check_assist(
diff --git a/crates/ide_assists/src/handlers/inline_local_variable.rs b/crates/ide_assists/src/handlers/inline_local_variable.rs
index 0b4f46caf4..1fce654df2 100644
--- a/crates/ide_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ide_assists/src/handlers/inline_local_variable.rs
@@ -206,7 +206,6 @@ fn inline_usage(
         return None;
     }
 
-    // FIXME: Handle multiple local definitions
     let bind_pat = match local.source(sema.db).value {
         Either::Left(ident) => ident,
         _ => return None,
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index 4a14a7f849..fcad172984 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -187,7 +187,6 @@ pub fn for_each_tail_expr(expr: &ast::Expr, cb: &mut dyn FnMut(&ast::Expr)) {
         | ast::Expr::TupleExpr(_)
         | ast::Expr::WhileExpr(_)
         | ast::Expr::LetExpr(_)
-        | ast::Expr::UnderscoreExpr(_)
         | ast::Expr::YieldExpr(_) => cb(expr),
     }
 }
diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs
index a44fe04e74..970ca2b6d7 100644
--- a/crates/ide_db/src/rename.rs
+++ b/crates/ide_db/src/rename.rs
@@ -293,18 +293,8 @@ fn rename_reference(
         (file_id, source_edit_from_references(references, def, new_name))
     }));
 
-    let mut insert_def_edit = |def| {
-        let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
-        source_change.insert_source_edit(file_id, edit);
-        Ok(())
-    };
-    match def {
-        Definition::Local(l) => l
-            .associated_locals(sema.db)
-            .iter()
-            .try_for_each(|&local| insert_def_edit(Definition::Local(local))),
-        def => insert_def_edit(def),
-    }?;
+    let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
+    source_change.insert_source_edit(file_id, edit);
     Ok(source_change)
 }
 
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index 3ff48520f4..7ea2bc63f2 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -310,10 +310,6 @@ impl Definition {
 
     pub fn usages<'a>(self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> {
         FindUsages {
-            local_repr: match self {
-                Definition::Local(local) => Some(local.representative(sema.db)),
-                _ => None,
-            },
             def: self,
             sema,
             scope: None,
@@ -329,7 +325,6 @@ pub struct FindUsages<'a> {
     sema: &'a Semantics<'a, RootDatabase>,
     scope: Option<SearchScope>,
     include_self_kw_refs: Option<hir::Type>,
-    local_repr: Option<hir::Local>,
     search_self_mod: bool,
 }
 
@@ -598,19 +593,6 @@ impl<'a> FindUsages<'a> {
         sink: &mut dyn FnMut(FileId, FileReference) -> bool,
     ) -> bool {
         match NameRefClass::classify(self.sema, name_ref) {
-            Some(NameRefClass::Definition(def @ Definition::Local(local)))
-                if matches!(
-                    self.local_repr, Some(repr) if repr == local.representative(self.sema.db)
-                ) =>
-            {
-                let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
-                let reference = FileReference {
-                    range,
-                    name: ast::NameLike::NameRef(name_ref.clone()),
-                    category: ReferenceCategory::new(&def, name_ref),
-                };
-                sink(file_id, reference)
-            }
             Some(NameRefClass::Definition(def)) if def == self.def => {
                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
                 let reference = FileReference {
@@ -640,7 +622,7 @@ impl<'a> FindUsages<'a> {
                     Definition::Field(_) if field == self.def => {
                         ReferenceCategory::new(&field, name_ref)
                     }
-                    Definition::Local(_) if matches!(self.local_repr, Some(repr) if repr == local.representative(self.sema.db)) => {
+                    Definition::Local(l) if local == l => {
                         ReferenceCategory::new(&Definition::Local(local), name_ref)
                     }
                     _ => return false,
@@ -685,21 +667,6 @@ impl<'a> FindUsages<'a> {
                 };
                 sink(file_id, reference)
             }
-            Some(NameClass::Definition(def @ Definition::Local(local))) if def != self.def => {
-                if matches!(
-                    self.local_repr,
-                    Some(repr) if local.representative(self.sema.db) == repr
-                ) {
-                    let FileRange { file_id, range } = self.sema.original_range(name.syntax());
-                    let reference = FileReference {
-                        range,
-                        name: ast::NameLike::Name(name.clone()),
-                        category: None,
-                    };
-                    return sink(file_id, reference);
-                }
-                false
-            }
             // Resolve trait impl function definitions to the trait definition's version if self.def is the trait definition's
             Some(NameClass::Definition(def)) if def != self.def => {
                 /* poor man's try block */
diff --git a/crates/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs
index aefb3d059f..944d3ef87d 100644
--- a/crates/mbe/src/expander/matcher.rs
+++ b/crates/mbe/src/expander/matcher.rs
@@ -690,19 +690,9 @@ fn match_meta_var(kind: &str, input: &mut TtIter) -> ExpandResult<Option<Fragmen
         "item" => parser::PrefixEntryPoint::Item,
         "vis" => parser::PrefixEntryPoint::Vis,
         "expr" => {
-            // `expr` should not match underscores.
-            // HACK: Macro expansion should not be done using "rollback and try another alternative".
-            // rustc [explicitly checks the next token][0].
-            // [0]: https://github.com/rust-lang/rust/blob/f0c4da499/compiler/rustc_expand/src/mbe/macro_parser.rs#L576
-            match input.peek_n(0) {
-                Some(tt::TokenTree::Leaf(tt::Leaf::Ident(it))) if it.text == "_" => {
-                    return ExpandResult::only_err(ExpandError::NoMatchingRule)
-                }
-                _ => {}
-            };
             return input
                 .expect_fragment(parser::PrefixEntryPoint::Expr)
-                .map(|tt| tt.map(Fragment::Expr));
+                .map(|tt| tt.map(Fragment::Expr))
         }
         _ => {
             let tt_result = match kind {
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index 5d652fb8dc..a40db15049 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -593,16 +593,7 @@ pub(crate) fn record_expr_field_list(p: &mut Parser) {
             T![.] if p.at(T![..]) => {
                 m.abandon(p);
                 p.bump(T![..]);
-
-                // test destructuring_assignment_struct_rest_pattern
-                // fn foo() {
-                //     S { .. } = S {};
-                // }
-
-                // We permit `.. }` on the left-hand side of a destructuring assignment.
-                if !p.at(T!['}']) {
-                    expr(p);
-                }
+                expr(p);
             }
             T!['{'] => {
                 error_block(p, "expected a field");
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index e5f8e8199e..e2c1b1fec5 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -81,17 +81,6 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar
         T![if] => if_expr(p),
         T![let] => let_expr(p),
 
-        T![_] => {
-            // test destructuring_assignment_wildcard_pat
-            // fn foo() {
-            //     _ = 1;
-            //     Some(_) = None;
-            // }
-            let m = p.start();
-            p.bump(T![_]);
-            m.complete(p, UNDERSCORE_EXPR)
-        }
-
         T![loop] => loop_expr(p, None),
         T![box] => box_expr(p, None),
         T![for] => for_expr(p, None),
diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs
index 4e8a0cfe80..d04b5dbf00 100644
--- a/crates/parser/src/syntax_kind/generated.rs
+++ b/crates/parser/src/syntax_kind/generated.rs
@@ -188,7 +188,6 @@ pub enum SyntaxKind {
     RETURN_EXPR,
     YIELD_EXPR,
     LET_EXPR,
-    UNDERSCORE_EXPR,
     MATCH_EXPR,
     MATCH_ARM_LIST,
     MATCH_ARM,
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 8baea77075..f59cd4f257 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -30,7 +30,7 @@ rayon = "1"
 expect-test = "1.2.0-pre.1"
 proc-macro2 = "1.0.8"
 quote = "1.0.2"
-ungrammar = "=1.16.0"
+ungrammar = "=1.15.0"
 
 test_utils = { path = "../test_utils" }
 sourcegen = { path = "../sourcegen" }
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index fc1ee6f601..11247fbf1c 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -262,7 +262,6 @@ pub struct ExternBlock {
 impl ast::HasAttrs for ExternBlock {}
 impl ast::HasDocComments for ExternBlock {}
 impl ExternBlock {
-    pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
     pub fn abi(&self) -> Option<Abi> { support::child(&self.syntax) }
     pub fn extern_item_list(&self) -> Option<ExternItemList> { support::child(&self.syntax) }
 }
@@ -1063,15 +1062,6 @@ impl LetExpr {
     pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct UnderscoreExpr {
-    pub(crate) syntax: SyntaxNode,
-}
-impl ast::HasAttrs for UnderscoreExpr {}
-impl UnderscoreExpr {
-    pub fn underscore_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![_]) }
-}
-
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct StmtList {
     pub(crate) syntax: SyntaxNode,
@@ -1532,7 +1522,6 @@ pub enum Expr {
     WhileExpr(WhileExpr),
     YieldExpr(YieldExpr),
     LetExpr(LetExpr),
-    UnderscoreExpr(UnderscoreExpr),
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -2684,17 +2673,6 @@ impl AstNode for LetExpr {
     }
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
-impl AstNode for UnderscoreExpr {
-    fn can_cast(kind: SyntaxKind) -> bool { kind == UNDERSCORE_EXPR }
-    fn cast(syntax: SyntaxNode) -> Option<Self> {
-        if Self::can_cast(syntax.kind()) {
-            Some(Self { syntax })
-        } else {
-            None
-        }
-    }
-    fn syntax(&self) -> &SyntaxNode { &self.syntax }
-}
 impl AstNode for StmtList {
     fn can_cast(kind: SyntaxKind) -> bool { kind == STMT_LIST }
     fn cast(syntax: SyntaxNode) -> Option<Self> {
@@ -3359,9 +3337,6 @@ impl From<YieldExpr> for Expr {
 impl From<LetExpr> for Expr {
     fn from(node: LetExpr) -> Expr { Expr::LetExpr(node) }
 }
-impl From<UnderscoreExpr> for Expr {
-    fn from(node: UnderscoreExpr) -> Expr { Expr::UnderscoreExpr(node) }
-}
 impl AstNode for Expr {
     fn can_cast(kind: SyntaxKind) -> bool {
         match kind {
@@ -3370,7 +3345,7 @@ impl AstNode for Expr {
             | INDEX_EXPR | LITERAL | LOOP_EXPR | MACRO_CALL | MACRO_STMTS | MATCH_EXPR
             | METHOD_CALL_EXPR | PAREN_EXPR | PATH_EXPR | PREFIX_EXPR | RANGE_EXPR
             | RECORD_EXPR | REF_EXPR | RETURN_EXPR | TRY_EXPR | TUPLE_EXPR | WHILE_EXPR
-            | YIELD_EXPR | LET_EXPR | UNDERSCORE_EXPR => true,
+            | YIELD_EXPR | LET_EXPR => true,
             _ => false,
         }
     }
@@ -3408,7 +3383,6 @@ impl AstNode for Expr {
             WHILE_EXPR => Expr::WhileExpr(WhileExpr { syntax }),
             YIELD_EXPR => Expr::YieldExpr(YieldExpr { syntax }),
             LET_EXPR => Expr::LetExpr(LetExpr { syntax }),
-            UNDERSCORE_EXPR => Expr::UnderscoreExpr(UnderscoreExpr { syntax }),
             _ => return None,
         };
         Some(res)
@@ -3447,7 +3421,6 @@ impl AstNode for Expr {
             Expr::WhileExpr(it) => &it.syntax,
             Expr::YieldExpr(it) => &it.syntax,
             Expr::LetExpr(it) => &it.syntax,
-            Expr::UnderscoreExpr(it) => &it.syntax,
         }
     }
 }
@@ -3914,7 +3887,6 @@ impl AstNode for AnyHasAttrs {
             | WHILE_EXPR
             | YIELD_EXPR
             | LET_EXPR
-            | UNDERSCORE_EXPR
             | STMT_LIST
             | RECORD_EXPR_FIELD_LIST
             | RECORD_EXPR_FIELD
@@ -4574,11 +4546,6 @@ impl std::fmt::Display for LetExpr {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
-impl std::fmt::Display for UnderscoreExpr {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        std::fmt::Display::fmt(self.syntax(), f)
-    }
-}
 impl std::fmt::Display for StmtList {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)
diff --git a/crates/text_edit/Cargo.toml b/crates/text_edit/Cargo.toml
index dae69e265d..e9c6bb3348 100644
--- a/crates/text_edit/Cargo.toml
+++ b/crates/text_edit/Cargo.toml
@@ -10,5 +10,4 @@ rust-version = "1.57"
 doctest = false
 
 [dependencies]
-itertools = "0.10.0"
 text-size = "1.0.0"
diff --git a/crates/text_edit/src/lib.rs b/crates/text_edit/src/lib.rs
index 1a6add0578..19c96d34c5 100644
--- a/crates/text_edit/src/lib.rs
+++ b/crates/text_edit/src/lib.rs
@@ -4,8 +4,6 @@
 //! so `TextEdit` is the ultimate representation of the work done by
 //! rust-analyzer.
 
-use itertools::Itertools;
-use std::cmp::max;
 pub use text_size::{TextRange, TextSize};
 
 /// `InsertDelete` -- a single "atomic" change to text
@@ -93,15 +91,13 @@ impl TextEdit {
         }
 
         let text_size = TextSize::of(&*text);
-        let mut total_len = text_size;
-        let mut max_total_len = text_size.clone();
+        let mut total_len = text_size.clone();
         for indel in &self.indels {
             total_len += TextSize::of(&indel.insert);
             total_len -= indel.delete.len();
-            max_total_len = max(max_total_len, total_len);
         }
 
-        if let Some(additional) = max_total_len.checked_sub(text_size.into()) {
+        if let Some(additional) = total_len.checked_sub(text_size.into()) {
             text.reserve(additional.into());
         }
 
@@ -113,14 +109,16 @@ impl TextEdit {
     }
 
     pub fn union(&mut self, other: TextEdit) -> Result<(), TextEdit> {
-        let iter_merge =
-            self.iter().merge_by(other.iter(), |l, r| l.delete.start() <= r.delete.start());
-        if !check_disjoint(&mut iter_merge.clone()) {
+        // FIXME: can be done without allocating intermediate vector
+        let mut all = self.iter().chain(other.iter()).collect::<Vec<_>>();
+        if !check_disjoint_and_sort(&mut all) {
             return Err(other);
         }
 
+        self.indels.extend(other.indels);
+        check_disjoint_and_sort(&mut self.indels);
         // Only dedup deletions and replacements, keep all insertions
-        self.indels = iter_merge.dedup_by(|a, b| a == b && !a.delete.is_empty()).cloned().collect();
+        self.indels.dedup_by(|a, b| a == b && !a.delete.is_empty());
         Ok(())
     }
 
@@ -190,17 +188,14 @@ impl TextEditBuilder {
 fn assert_disjoint_or_equal(indels: &mut [Indel]) {
     assert!(check_disjoint_and_sort(indels));
 }
-
-fn check_disjoint_and_sort(indels: &mut [Indel]) -> bool {
-    indels.sort_by_key(|indel| (indel.delete.start(), indel.delete.end()));
-    check_disjoint(&mut indels.iter())
-}
-
-fn check_disjoint<'a, I>(indels: &mut I) -> bool
-where
-    I: std::iter::Iterator<Item = &'a Indel> + Clone,
-{
-    indels.clone().zip(indels.skip(1)).all(|(l, r)| l.delete.end() <= r.delete.start() || l == r)
+// FIXME: Remove the impl Bound here, it shouldn't be needed
+fn check_disjoint_and_sort(indels: &mut [impl std::borrow::Borrow<Indel>]) -> bool {
+    indels.sort_by_key(|indel| (indel.borrow().delete.start(), indel.borrow().delete.end()));
+    indels.iter().zip(indels.iter().skip(1)).all(|(l, r)| {
+        let l = l.borrow();
+        let r = r.borrow();
+        l.delete.end() <= r.delete.start() || l == r
+    })
 }
 
 #[cfg(test)]
@@ -237,22 +232,6 @@ mod tests {
         assert_eq!(edit1.indels.len(), 3);
     }
 
-    #[test]
-    fn test_union_with_duplicates() {
-        let mut builder1 = TextEditBuilder::default();
-        builder1.delete(range(7, 11));
-        builder1.delete(range(13, 17));
-
-        let mut builder2 = TextEditBuilder::default();
-        builder2.delete(range(1, 5));
-        builder2.delete(range(13, 17));
-
-        let mut edit1 = builder1.finish();
-        let edit2 = builder2.finish();
-        assert!(edit1.union(edit2).is_ok());
-        assert_eq!(edit1.indels.len(), 3);
-    }
-
     #[test]
     fn test_union_panics() {
         let mut edit1 = TextEdit::delete(range(7, 11));


Test patch:
diff --git a/crates/hir_ty/src/tests/macros.rs b/crates/hir_ty/src/tests/macros.rs
index 344e7293c5..a61175f273 100644
--- a/crates/hir_ty/src/tests/macros.rs
+++ b/crates/hir_ty/src/tests/macros.rs
@@ -190,6 +190,7 @@ fn expr_macro_def_expanded_in_various_places() {
             !0..6 '1isize': isize
             !0..6 '1isize': isize
             !0..6 '1isize': isize
+            !0..6 '1isize': isize
             39..442 '{     ...!(); }': ()
             73..94 'spam!(...am!())': {unknown}
             100..119 'for _ ...!() {}': ()
@@ -197,7 +198,6 @@ fn expr_macro_def_expanded_in_various_places() {
             117..119 '{}': ()
             124..134 '|| spam!()': || -> isize
             140..156 'while ...!() {}': ()
-            146..153 'spam!()': bool
             154..156 '{}': ()
             161..174 'break spam!()': !
             180..194 'return spam!()': !
@@ -271,6 +271,7 @@ fn expr_macro_rules_expanded_in_various_places() {
             !0..6 '1isize': isize
             !0..6 '1isize': isize
             !0..6 '1isize': isize
+            !0..6 '1isize': isize
             53..456 '{     ...!(); }': ()
             87..108 'spam!(...am!())': {unknown}
             114..133 'for _ ...!() {}': ()
@@ -278,7 +279,6 @@ fn expr_macro_rules_expanded_in_various_places() {
             131..133 '{}': ()
             138..148 '|| spam!()': || -> isize
             154..170 'while ...!() {}': ()
-            160..167 'spam!()': bool
             168..170 '{}': ()
             175..188 'break spam!()': !
             194..208 'return spam!()': !
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index e7fad48080..ab10bc6ef5 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -1757,30 +1757,6 @@ fn foo() { let bar = Bar; bar.fo$0o(); }
     );
 }
 
-#[test]
-fn test_hover_variadic_function() {
-    check(
-        r#"
-extern "C" {
-    pub fn foo(bar: i32, ...) -> i32;
-}
-
-fn main() { let foo_test = unsafe { fo$0o(1, 2, 3); } }
-"#,
-        expect![[r#"
-            *foo*
-
-            ```rust
-            test
-            ```
-
-            ```rust
-            pub unsafe fn foo(bar: i32, ...) -> i32
-            ```
-        "#]],
-    );
-}
-
 #[test]
 fn test_hover_trait_has_impl_action() {
     check_actions(
@@ -3532,27 +3508,6 @@ const FOO$0: usize = 1 << 100;
 
             ---
 
-            This is a doc
-        "#]],
-    );
-    check(
-        r#"
-/// This is a doc
-const FOO$0: &str = "bar";
-"#,
-        expect![[r#"
-            *FOO*
-
-            ```rust
-            test
-            ```
-
-            ```rust
-            const FOO: &str = "bar"
-            ```
-
-            ---
-
             This is a doc
         "#]],
     );
diff --git a/crates/parser/test_data/parser/err/0025_nope.rast b/crates/parser/test_data/parser/err/0025_nope.rast
new file mode 100644
index 0000000000..b48b4aed8d
--- /dev/null
+++ b/crates/parser/test_data/parser/err/0025_nope.rast
@@ -0,0 +1,204 @@
+SOURCE_FILE@0..575
+  FN@0..574
+    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..574
+      STMT_LIST@10..574
+        L_CURLY@10..11 "{"
+        WHITESPACE@11..16 "\n    "
+        ENUM@16..152
+          ENUM_KW@16..20 "enum"
+          WHITESPACE@20..21 " "
+          NAME@21..25
+            IDENT@21..25 "Test"
+          WHITESPACE@25..26 " "
+          VARIANT_LIST@26..152
+            L_CURLY@26..27 "{"
+            WHITESPACE@27..36 "\n        "
+            VARIANT@36..40
+              NAME@36..40
+                IDENT@36..40 "Var1"
+            COMMA@40..41 ","
+            WHITESPACE@41..50 "\n        "
+            VARIANT@50..62
+              NAME@50..54
+                IDENT@50..54 "Var2"
+              TUPLE_FIELD_LIST@54..62
+                L_PAREN@54..55 "("
+                TUPLE_FIELD@55..61
+                  PATH_TYPE@55..61
+                    PATH@55..61
+                      PATH_SEGMENT@55..61
+                        NAME_REF@55..61
+                          IDENT@55..61 "String"
+                R_PAREN@61..62 ")"
+            COMMA@62..63 ","
+            WHITESPACE@63..72 "\n        "
+            VARIANT@72..145
+              NAME@72..76
+                IDENT@72..76 "Var3"
+              WHITESPACE@76..77 " "
+              RECORD_FIELD_LIST@77..145
+                L_CURLY@77..78 "{"
+                WHITESPACE@78..91 "\n            "
+                RECORD_FIELD@91..95
+                  NAME@91..94
+                    IDENT@91..94 "abc"
+                  COLON@94..95 ":"
+                WHITESPACE@95..96 " "
+                ERROR@96..98
+                  L_CURLY@96..97 "{"
+                  R_CURLY@97..98 "}"
+                ERROR@98..99
+                  COMMA@98..99 ","
+                WHITESPACE@99..100 " "
+                COMMENT@100..135 "//~ ERROR: expected t ..."
+                WHITESPACE@135..144 "\n        "
+                R_CURLY@144..145 "}"
+            COMMA@145..146 ","
+            WHITESPACE@146..151 "\n    "
+            R_CURLY@151..152 "}"
+        WHITESPACE@152..158 "\n\n    "
+        COMMENT@158..171 "// recover..."
+        WHITESPACE@171..176 "\n    "
+        LET_STMT@176..186
+          LET_KW@176..179 "let"
+          WHITESPACE@179..180 " "
+          IDENT_PAT@180..181
+            NAME@180..181
+              IDENT@180..181 "a"
+          WHITESPACE@181..182 " "
+          EQ@182..183 "="
+          WHITESPACE@183..184 " "
+          LITERAL@184..185
+            INT_NUMBER@184..185 "1"
+          SEMICOLON@185..186 ";"
+        WHITESPACE@186..191 "\n    "
+        ENUM@191..223
+          ENUM_KW@191..195 "enum"
+          WHITESPACE@195..196 " "
+          NAME@196..201
+            IDENT@196..201 "Test2"
+          WHITESPACE@201..202 " "
+          VARIANT_LIST@202..223
+            L_CURLY@202..203 "{"
+            WHITESPACE@203..212 "\n        "
+            VARIANT@212..216
+              NAME@212..216
+                IDENT@212..216 "Fine"
+            COMMA@216..217 ","
+            WHITESPACE@217..222 "\n    "
+            R_CURLY@222..223 "}"
+        WHITESPACE@223..229 "\n\n    "
+        ENUM@229..300
+          ENUM_KW@229..233 "enum"
+          WHITESPACE@233..234 " "
+          NAME@234..239
+            IDENT@234..239 "Test3"
+          WHITESPACE@239..240 " "
+          VARIANT_LIST@240..300
+            L_CURLY@240..241 "{"
+            WHITESPACE@241..250 "\n        "
+            VARIANT@250..293
+              NAME@250..259
+                IDENT@250..259 "StillFine"
+              WHITESPACE@259..260 " "
+              RECORD_FIELD_LIST@260..293
+                L_CURLY@260..261 "{"
+                WHITESPACE@261..274 "\n            "
+                RECORD_FIELD@274..282
+                  NAME@274..277
+                    IDENT@274..277 "def"
+                  COLON@277..278 ":"
+                  WHITESPACE@278..279 " "
+                  PATH_TYPE@279..282
+                    PATH@279..282
+                      PATH_SEGMENT@279..282
+                        NAME_REF@279..282
+                          IDENT@279..282 "i32"
+                COMMA@282..283 ","
+                WHITESPACE@283..292 "\n        "
+                R_CURLY@292..293 "}"
+            COMMA@293..294 ","
+            WHITESPACE@294..299 "\n    "
+            R_CURLY@299..300 "}"
+        WHITESPACE@300..306 "\n\n    "
+        EXPR_STMT@306..459
+          BLOCK_EXPR@306..459
+            STMT_LIST@306..459
+              L_CURLY@306..307 "{"
+              WHITESPACE@307..316 "\n        "
+              ENUM@316..453
+                COMMENT@316..329 "// fail again"
+                WHITESPACE@329..338 "\n        "
+                ENUM_KW@338..342 "enum"
+                WHITESPACE@342..343 " "
+                NAME@343..348
+                  IDENT@343..348 "Test4"
+                WHITESPACE@348..349 " "
+                VARIANT_LIST@349..453
+                  L_CURLY@349..350 "{"
+                  WHITESPACE@350..363 "\n            "
+                  VARIANT@363..372
+                    NAME@363..367
+                      IDENT@363..367 "Nope"
+                    TUPLE_FIELD_LIST@367..372
+                      L_PAREN@367..368 "("
+                      TUPLE_FIELD@368..371
+                        PATH_TYPE@368..371
+                          PATH@368..371
+                            PATH_SEGMENT@368..371
+                              NAME_REF@368..371
+                                IDENT@368..371 "i32"
+                      WHITESPACE@371..372 " "
+                      ERROR@372..372
+                  ERROR@372..374
+                    L_CURLY@372..373 "{"
+                    R_CURLY@373..374 "}"
+                  ERROR@374..375
+                    R_PAREN@374..375 ")"
+                  WHITESPACE@375..376 " "
+                  COMMENT@376..396 "//~ ERROR: found `{`"
+                  WHITESPACE@396..422 "\n                     ..."
+                  COMMENT@422..443 "//~^ ERROR: found `{`"
+                  WHITESPACE@443..452 "\n        "
+                  R_CURLY@452..453 "}"
+              WHITESPACE@453..458 "\n    "
+              R_CURLY@458..459 "}"
+        WHITESPACE@459..464 "\n    "
+        COMMENT@464..486 "// still recover later"
+        WHITESPACE@486..491 "\n    "
+        LET_STMT@491..510
+          LET_KW@491..494 "let"
+          WHITESPACE@494..495 " "
+          IDENT_PAT@495..505
+            NAME@495..505
+              IDENT@495..505 "bad_syntax"
+          WHITESPACE@505..506 " "
+          EQ@506..507 "="
+          WHITESPACE@507..508 " "
+          ERROR@508..509
+            UNDERSCORE@508..509 "_"
+          SEMICOLON@509..510 ";"
+        WHITESPACE@510..511 " "
+        COMMENT@511..572 "//~ ERROR: expected e ..."
+        WHITESPACE@572..573 "\n"
+        R_CURLY@573..574 "}"
+  WHITESPACE@574..575 "\n"
+error 95..95: expected type
+error 95..95: expected COMMA
+error 96..96: expected field
+error 98..98: expected field declaration
+error 371..371: expected COMMA
+error 372..372: expected a type
+error 372..372: expected R_PAREN
+error 372..372: expected COMMA
+error 372..372: expected enum variant
+error 374..374: expected enum variant
+error 508..508: expected expression
diff --git a/crates/parser/test_data/parser/err/0025_nope.rs b/crates/parser/test_data/parser/err/0025_nope.rs
index c78abe80ab..28726ed513 100644
--- a/crates/parser/test_data/parser/err/0025_nope.rs
+++ b/crates/parser/test_data/parser/err/0025_nope.rs
@@ -27,6 +27,5 @@ fn main() {
         }
     }
     // still recover later
-    let; //~ ERROR: expected pattern
-    let _ = 0;
+    let bad_syntax = _; //~ ERROR: expected expression, found reserved identifier `_`
 }
diff --git a/crates/parser/test_data/parser/err/0025_nope.txt b/crates/parser/test_data/parser/err/0025_nope.txt
index 6b49724ec9..aca57faf96 100644
--- a/crates/parser/test_data/parser/err/0025_nope.txt
+++ b/crates/parser/test_data/parser/err/0025_nope.txt
@@ -174,24 +174,20 @@ SOURCE_FILE
         WHITESPACE "\n    "
         COMMENT "// still recover later"
         WHITESPACE "\n    "
-        LET_STMT
-          LET_KW "let"
-          ERROR
-            SEMICOLON ";"
-        WHITESPACE " "
-        COMMENT "//~ ERROR: expected pattern"
-        WHITESPACE "\n    "
         LET_STMT
           LET_KW "let"
           WHITESPACE " "
-          WILDCARD_PAT
-            UNDERSCORE "_"
+          IDENT_PAT
+            NAME
+              IDENT "bad_syntax"
           WHITESPACE " "
           EQ "="
           WHITESPACE " "
-          LITERAL
-            INT_NUMBER "0"
+          ERROR
+            UNDERSCORE "_"
           SEMICOLON ";"
+        WHITESPACE " "
+        COMMENT "//~ ERROR: expected expression, found reserved identifier `_`"
         WHITESPACE "\n"
         R_CURLY "}"
   WHITESPACE "\n"
@@ -205,5 +201,4 @@ error 372: expected R_PAREN
 error 372: expected COMMA
 error 372: expected enum variant
 error 374: expected enum variant
-error 494: expected pattern
-error 495: expected SEMICOLON
+error 508: expected expression
diff --git a/crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.rs b/crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.rs
deleted file mode 100644
index 22a5b5f3e3..0000000000
--- a/crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn foo() {
-    S { .. } = S {};
-}
diff --git a/crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.txt b/crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.txt
deleted file mode 100644
index fb8aa5accb..0000000000
--- a/crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-SOURCE_FILE
-  FN
-    FN_KW "fn"
-    WHITESPACE " "
-    NAME
-      IDENT "foo"
-    PARAM_LIST
-      L_PAREN "("
-      R_PAREN ")"
-    WHITESPACE " "
-    BLOCK_EXPR
-      STMT_LIST
-        L_CURLY "{"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            RECORD_EXPR
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "S"
-              WHITESPACE " "
-              RECORD_EXPR_FIELD_LIST
-                L_CURLY "{"
-                WHITESPACE " "
-                DOT2 ".."
-                WHITESPACE " "
-                R_CURLY "}"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            RECORD_EXPR
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "S"
-              WHITESPACE " "
-              RECORD_EXPR_FIELD_LIST
-                L_CURLY "{"
-                R_CURLY "}"
-          SEMICOLON ";"
-        WHITESPACE "\n"
-        R_CURLY "}"
-  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.rs b/crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.rs
deleted file mode 100644
index 91acfb3a0a..0000000000
--- a/crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-fn foo() {
-    _ = 1;
-    Some(_) = None;
-}
diff --git a/crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.txt b/crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.txt
deleted file mode 100644
index 5f53d34510..0000000000
--- a/crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.txt
+++ /dev/null
@@ -1,50 +0,0 @@
-SOURCE_FILE
-  FN
-    FN_KW "fn"
-    WHITESPACE " "
-    NAME
-      IDENT "foo"
-    PARAM_LIST
-      L_PAREN "("
-      R_PAREN ")"
-    WHITESPACE " "
-    BLOCK_EXPR
-      STMT_LIST
-        L_CURLY "{"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            UNDERSCORE_EXPR
-              UNDERSCORE "_"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            LITERAL
-              INT_NUMBER "1"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            CALL_EXPR
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "Some"
-              ARG_LIST
-                L_PAREN "("
-                UNDERSCORE_EXPR
-                  UNDERSCORE "_"
-                R_PAREN ")"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            PATH_EXPR
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "None"
-          SEMICOLON ";"
-        WHITESPACE "\n"
-        R_CURLY "}"
-  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/ok/0072_destructuring_assignment.rs b/crates/parser/test_data/parser/ok/0072_destructuring_assignment.rs
deleted file mode 100644
index 9d3e86603f..0000000000
--- a/crates/parser/test_data/parser/ok/0072_destructuring_assignment.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-fn foo() {
-    let (mut a, mut b) = (0, 1);
-    (b, a, ..) = (a, b);
-    (_) = ..;
-    struct S { a: i32 }
-    S { .. } = S { ..S::default() };
-    Some(..) = Some(0).
-    Ok(_) = 0;
-    let (a, b);
-    [a, .., b] = [1, .., 2];
-    (_, _) = (a, b);
-    (_) = (a, b);
-    _ = (a, b);
-}
diff --git a/crates/parser/test_data/parser/ok/0072_destructuring_assignment.txt b/crates/parser/test_data/parser/ok/0072_destructuring_assignment.txt
deleted file mode 100644
index e8b836dfbd..0000000000
--- a/crates/parser/test_data/parser/ok/0072_destructuring_assignment.txt
+++ /dev/null
@@ -1,352 +0,0 @@
-SOURCE_FILE
-  FN
-    FN_KW "fn"
-    WHITESPACE " "
-    NAME
-      IDENT "foo"
-    PARAM_LIST
-      L_PAREN "("
-      R_PAREN ")"
-    WHITESPACE " "
-    BLOCK_EXPR
-      STMT_LIST
-        L_CURLY "{"
-        WHITESPACE "\n    "
-        LET_STMT
-          LET_KW "let"
-          WHITESPACE " "
-          TUPLE_PAT
-            L_PAREN "("
-            IDENT_PAT
-              MUT_KW "mut"
-              WHITESPACE " "
-              NAME
-                IDENT "a"
-            COMMA ","
-            WHITESPACE " "
-            IDENT_PAT
-              MUT_KW "mut"
-              WHITESPACE " "
-              NAME
-                IDENT "b"
-            R_PAREN ")"
-          WHITESPACE " "
-          EQ "="
-          WHITESPACE " "
-          TUPLE_EXPR
-            L_PAREN "("
-            LITERAL
-              INT_NUMBER "0"
-            COMMA ","
-            WHITESPACE " "
-            LITERAL
-              INT_NUMBER "1"
-            R_PAREN ")"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            TUPLE_EXPR
-              L_PAREN "("
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "b"
-              COMMA ","
-              WHITESPACE " "
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "a"
-              COMMA ","
-              WHITESPACE " "
-              RANGE_EXPR
-                DOT2 ".."
-              R_PAREN ")"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            TUPLE_EXPR
-              L_PAREN "("
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "a"
-              COMMA ","
-              WHITESPACE " "
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "b"
-              R_PAREN ")"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            PAREN_EXPR
-              L_PAREN "("
-              UNDERSCORE_EXPR
-                UNDERSCORE "_"
-              R_PAREN ")"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            RANGE_EXPR
-              DOT2 ".."
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        STRUCT
-          STRUCT_KW "struct"
-          WHITESPACE " "
-          NAME
-            IDENT "S"
-          WHITESPACE " "
-          RECORD_FIELD_LIST
-            L_CURLY "{"
-            WHITESPACE " "
-            RECORD_FIELD
-              NAME
-                IDENT "a"
-              COLON ":"
-              WHITESPACE " "
-              PATH_TYPE
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "i32"
-            WHITESPACE " "
-            R_CURLY "}"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            RECORD_EXPR
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "S"
-              WHITESPACE " "
-              RECORD_EXPR_FIELD_LIST
-                L_CURLY "{"
-                WHITESPACE " "
-                DOT2 ".."
-                WHITESPACE " "
-                R_CURLY "}"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            RECORD_EXPR
-              PATH
-                PATH_SEGMENT
-                  NAME_REF
-                    IDENT "S"
-              WHITESPACE " "
-              RECORD_EXPR_FIELD_LIST
-                L_CURLY "{"
-                WHITESPACE " "
-                DOT2 ".."
-                CALL_EXPR
-                  PATH_EXPR
-                    PATH
-                      PATH
-                        PATH_SEGMENT
-                          NAME_REF
-                            IDENT "S"
-                      COLON2 "::"
-                      PATH_SEGMENT
-                        NAME_REF
-                          IDENT "default"
-                  ARG_LIST
-                    L_PAREN "("
-                    R_PAREN ")"
-                WHITESPACE " "
-                R_CURLY "}"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            BIN_EXPR
-              CALL_EXPR
-                PATH_EXPR
-                  PATH
-                    PATH_SEGMENT
-                      NAME_REF
-                        IDENT "Some"
-                ARG_LIST
-                  L_PAREN "("
-                  RANGE_EXPR
-                    DOT2 ".."
-                  R_PAREN ")"
-              WHITESPACE " "
-              EQ "="
-              WHITESPACE " "
-              METHOD_CALL_EXPR
-                CALL_EXPR
-                  PATH_EXPR
-                    PATH
-                      PATH_SEGMENT
-                        NAME_REF
-                          IDENT "Some"
-                  ARG_LIST
-                    L_PAREN "("
-                    LITERAL
-                      INT_NUMBER "0"
-                    R_PAREN ")"
-                DOT "."
-                WHITESPACE "\n    "
-                NAME_REF
-                  IDENT "Ok"
-                ARG_LIST
-                  L_PAREN "("
-                  UNDERSCORE_EXPR
-                    UNDERSCORE "_"
-                  R_PAREN ")"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            LITERAL
-              INT_NUMBER "0"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        LET_STMT
-          LET_KW "let"
-          WHITESPACE " "
-          TUPLE_PAT
-            L_PAREN "("
-            IDENT_PAT
-              NAME
-                IDENT "a"
-            COMMA ","
-            WHITESPACE " "
-            IDENT_PAT
-              NAME
-                IDENT "b"
-            R_PAREN ")"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            ARRAY_EXPR
-              L_BRACK "["
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "a"
-              COMMA ","
-              WHITESPACE " "
-              RANGE_EXPR
-                DOT2 ".."
-              COMMA ","
-              WHITESPACE " "
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "b"
-              R_BRACK "]"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            ARRAY_EXPR
-              L_BRACK "["
-              LITERAL
-                INT_NUMBER "1"
-              COMMA ","
-              WHITESPACE " "
-              RANGE_EXPR
-                DOT2 ".."
-              COMMA ","
-              WHITESPACE " "
-              LITERAL
-                INT_NUMBER "2"
-              R_BRACK "]"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            TUPLE_EXPR
-              L_PAREN "("
-              UNDERSCORE_EXPR
-                UNDERSCORE "_"
-              COMMA ","
-              WHITESPACE " "
-              UNDERSCORE_EXPR
-                UNDERSCORE "_"
-              R_PAREN ")"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            TUPLE_EXPR
-              L_PAREN "("
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "a"
-              COMMA ","
-              WHITESPACE " "
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "b"
-              R_PAREN ")"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            PAREN_EXPR
-              L_PAREN "("
-              UNDERSCORE_EXPR
-                UNDERSCORE "_"
-              R_PAREN ")"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            TUPLE_EXPR
-              L_PAREN "("
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "a"
-              COMMA ","
-              WHITESPACE " "
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "b"
-              R_PAREN ")"
-          SEMICOLON ";"
-        WHITESPACE "\n    "
-        EXPR_STMT
-          BIN_EXPR
-            UNDERSCORE_EXPR
-              UNDERSCORE "_"
-            WHITESPACE " "
-            EQ "="
-            WHITESPACE " "
-            TUPLE_EXPR
-              L_PAREN "("
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "a"
-              COMMA ","
-              WHITESPACE " "
-              PATH_EXPR
-                PATH
-                  PATH_SEGMENT
-                    NAME_REF
-                      IDENT "b"
-              R_PAREN ")"
-          SEMICOLON ";"
-        WHITESPACE "\n"
-        R_CURLY "}"
-  WHITESPACE "\n"
diff --git a/crates/syntax/src/tests/ast_src.rs b/crates/syntax/src/tests/ast_src.rs
index 3152137fb8..aeff851ce4 100644
--- a/crates/syntax/src/tests/ast_src.rs
+++ b/crates/syntax/src/tests/ast_src.rs
@@ -143,7 +143,6 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
         "RETURN_EXPR",
         "YIELD_EXPR",
         "LET_EXPR",
-        "UNDERSCORE_EXPR",
         "MATCH_EXPR",
         "MATCH_ARM_LIST",
         "MATCH_ARM",