about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2022-03-05 11:31:29 +0000
committerGitHub <noreply@github.com>2022-03-05 11:31:29 +0000
commit8f504dc87384b0fa41a55327566f0db9c08eb98c (patch)
tree4a2d8a06d385c45f17b4b40afe217ea125d137f7
parente844b1570a4ddf2ece7ed7c092eaa136ce1d3878 (diff)
parent10d30be331a2975e89bdc5e45ec941bb00eb36ae (diff)
downloadrust-8f504dc87384b0fa41a55327566f0db9c08eb98c.tar.gz
rust-8f504dc87384b0fa41a55327566f0db9c08eb98c.zip
Merge #11598
11598: feat: Parse destructuring assignment r=Veykril a=ChayimFriedman2

Part of #11532.

Lowering is not as easy and may not even be feasible right now as it requires generating identifiers: `(a, b) = (b, a)` is desugared into
```rust
{
    let (<gensym_a>, <gensym_b>) = (b, a);
    a = <gensym_a>;
    b = <gensym_b>;
}
```

rustc uses hygiene to implement that, but we don't support hygiene yet.

However, I think parsing was the main problem as lowering will just affect type inference, and while `{unknown}` is not nice it's much better than a syntax error.

I'm still looking for the best way to do lowering, though.

Fixes #11454.

Co-authored-by: Chayim Refael Friedman <chayimfr@gmail.com>
-rw-r--r--Cargo.lock4
-rw-r--r--crates/hir_def/src/body/lower.rs1
-rw-r--r--crates/ide_assists/src/handlers/extract_module.rs29
-rw-r--r--crates/ide_db/src/helpers.rs1
-rw-r--r--crates/mbe/src/expander/matcher.rs12
-rw-r--r--crates/parser/src/grammar/expressions.rs11
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs11
-rw-r--r--crates/parser/src/syntax_kind/generated.rs1
-rw-r--r--crates/parser/test_data/parser/err/0025_nope.rast204
-rw-r--r--crates/parser/test_data/parser/err/0025_nope.rs3
-rw-r--r--crates/parser/test_data/parser/err/0025_nope.txt21
-rw-r--r--crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.rs3
-rw-r--r--crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.txt44
-rw-r--r--crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.rs4
-rw-r--r--crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.txt50
-rw-r--r--crates/parser/test_data/parser/ok/0072_destructuring_assignment.rs14
-rw-r--r--crates/parser/test_data/parser/ok/0072_destructuring_assignment.txt352
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/ast/generated/nodes.rs35
-rw-r--r--crates/syntax/src/tests/ast_src.rs1
20 files changed, 582 insertions, 221 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3f2ee2c5727..9cc5a73c966 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1833,9 +1833,9 @@ checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
 
 [[package]]
 name = "ungrammar"
-version = "1.15.0"
+version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed01567101450f7d600508e7680df6005ae4fe97119d79b0364cc5910ff39732"
+checksum = "a62374cbbda72c1459ce5e7bfcdf1bd284c812a4faf2324aa083e5d9ea87880f"
 
 [[package]]
 name = "unicase"
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index a2b57cb5bdf..f86f4f3c85b 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -525,6 +525,7 @@ impl ExprCollector<'_> {
 
                 self.alloc_expr(Expr::MacroStmts { tail }, syntax_ptr)
             }
+            ast::Expr::UnderscoreExpr(_) => return None,
         })
     }
 
diff --git a/crates/ide_assists/src/handlers/extract_module.rs b/crates/ide_assists/src/handlers/extract_module.rs
index 64875adfae2..880617dd38d 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.clone().into_iter().for_each(|item| {
+        self.body_items.iter().cloned().for_each(|item| {
             match_ast! {
                 match (item.syntax()) {
                     ast::Adt(it) => {
@@ -240,6 +240,11 @@ 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);
+                        }
+                    },
                     _ => (),
                 }
             }
@@ -781,7 +786,6 @@ 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())),
@@ -1377,6 +1381,27 @@ 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(
             extract_module,
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index fcad1729848..4a14a7f849b 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -187,6 +187,7 @@ 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/mbe/src/expander/matcher.rs b/crates/mbe/src/expander/matcher.rs
index 944d3ef87d8..aefb3d059ff 100644
--- a/crates/mbe/src/expander/matcher.rs
+++ b/crates/mbe/src/expander/matcher.rs
@@ -690,9 +690,19 @@ 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 a40db15049d..5d652fb8dc8 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -593,7 +593,16 @@ pub(crate) fn record_expr_field_list(p: &mut Parser) {
             T![.] if p.at(T![..]) => {
                 m.abandon(p);
                 p.bump(T![..]);
-                expr(p);
+
+                // 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);
+                }
             }
             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 e2c1b1fec57..e5f8e8199e1 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -81,6 +81,17 @@ 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 d04b5dbf008..4e8a0cfe80a 100644
--- a/crates/parser/src/syntax_kind/generated.rs
+++ b/crates/parser/src/syntax_kind/generated.rs
@@ -188,6 +188,7 @@ pub enum SyntaxKind {
     RETURN_EXPR,
     YIELD_EXPR,
     LET_EXPR,
+    UNDERSCORE_EXPR,
     MATCH_EXPR,
     MATCH_ARM_LIST,
     MATCH_ARM,
diff --git a/crates/parser/test_data/parser/err/0025_nope.rast b/crates/parser/test_data/parser/err/0025_nope.rast
deleted file mode 100644
index b48b4aed8dd..00000000000
--- a/crates/parser/test_data/parser/err/0025_nope.rast
+++ /dev/null
@@ -1,204 +0,0 @@
-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 28726ed5138..c78abe80ab7 100644
--- a/crates/parser/test_data/parser/err/0025_nope.rs
+++ b/crates/parser/test_data/parser/err/0025_nope.rs
@@ -27,5 +27,6 @@ fn main() {
         }
     }
     // still recover later
-    let bad_syntax = _; //~ ERROR: expected expression, found reserved identifier `_`
+    let; //~ ERROR: expected pattern
+    let _ = 0;
 }
diff --git a/crates/parser/test_data/parser/err/0025_nope.txt b/crates/parser/test_data/parser/err/0025_nope.txt
index aca57faf96e..6b49724ec9a 100644
--- a/crates/parser/test_data/parser/err/0025_nope.txt
+++ b/crates/parser/test_data/parser/err/0025_nope.txt
@@ -176,18 +176,22 @@ SOURCE_FILE
         WHITESPACE "\n    "
         LET_STMT
           LET_KW "let"
+          ERROR
+            SEMICOLON ";"
+        WHITESPACE " "
+        COMMENT "//~ ERROR: expected pattern"
+        WHITESPACE "\n    "
+        LET_STMT
+          LET_KW "let"
           WHITESPACE " "
-          IDENT_PAT
-            NAME
-              IDENT "bad_syntax"
+          WILDCARD_PAT
+            UNDERSCORE "_"
           WHITESPACE " "
           EQ "="
           WHITESPACE " "
-          ERROR
-            UNDERSCORE "_"
+          LITERAL
+            INT_NUMBER "0"
           SEMICOLON ";"
-        WHITESPACE " "
-        COMMENT "//~ ERROR: expected expression, found reserved identifier `_`"
         WHITESPACE "\n"
         R_CURLY "}"
   WHITESPACE "\n"
@@ -201,4 +205,5 @@ error 372: expected R_PAREN
 error 372: expected COMMA
 error 372: expected enum variant
 error 374: expected enum variant
-error 508: expected expression
+error 494: expected pattern
+error 495: expected SEMICOLON
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
new file mode 100644
index 00000000000..22a5b5f3e31
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.rs
@@ -0,0 +1,3 @@
+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
new file mode 100644
index 00000000000..fb8aa5accb5
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0197_destructuring_assignment_struct_rest_pattern.txt
@@ -0,0 +1,44 @@
+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
new file mode 100644
index 00000000000..91acfb3a0ae
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.rs
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 00000000000..5f53d34510e
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/0198_destructuring_assignment_wildcard_pat.txt
@@ -0,0 +1,50 @@
+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
new file mode 100644
index 00000000000..9d3e86603f8
--- /dev/null
+++ b/crates/parser/test_data/parser/ok/0072_destructuring_assignment.rs
@@ -0,0 +1,14 @@
+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
new file mode 100644
index 00000000000..e8b836dfbd0
--- /dev/null
+++ b/crates/parser/test_data/parser/ok/0072_destructuring_assignment.txt
@@ -0,0 +1,352 @@
+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/Cargo.toml b/crates/syntax/Cargo.toml
index f59cd4f257c..8baea77075b 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.15.0"
+ungrammar = "=1.16.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 11247fbf1c9..fc1ee6f601d 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -262,6 +262,7 @@ 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,6 +1064,15 @@ impl LetExpr {
 }
 
 #[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,
 }
@@ -1522,6 +1532,7 @@ pub enum Expr {
     WhileExpr(WhileExpr),
     YieldExpr(YieldExpr),
     LetExpr(LetExpr),
+    UnderscoreExpr(UnderscoreExpr),
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -2673,6 +2684,17 @@ 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> {
@@ -3337,6 +3359,9 @@ 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 {
@@ -3345,7 +3370,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 => true,
+            | YIELD_EXPR | LET_EXPR | UNDERSCORE_EXPR => true,
             _ => false,
         }
     }
@@ -3383,6 +3408,7 @@ 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)
@@ -3421,6 +3447,7 @@ impl AstNode for Expr {
             Expr::WhileExpr(it) => &it.syntax,
             Expr::YieldExpr(it) => &it.syntax,
             Expr::LetExpr(it) => &it.syntax,
+            Expr::UnderscoreExpr(it) => &it.syntax,
         }
     }
 }
@@ -3887,6 +3914,7 @@ impl AstNode for AnyHasAttrs {
             | WHILE_EXPR
             | YIELD_EXPR
             | LET_EXPR
+            | UNDERSCORE_EXPR
             | STMT_LIST
             | RECORD_EXPR_FIELD_LIST
             | RECORD_EXPR_FIELD
@@ -4546,6 +4574,11 @@ 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/syntax/src/tests/ast_src.rs b/crates/syntax/src/tests/ast_src.rs
index aeff851ce4d..3152137fb8c 100644
--- a/crates/syntax/src/tests/ast_src.rs
+++ b/crates/syntax/src/tests/ast_src.rs
@@ -143,6 +143,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
         "RETURN_EXPR",
         "YIELD_EXPR",
         "LET_EXPR",
+        "UNDERSCORE_EXPR",
         "MATCH_EXPR",
         "MATCH_ARM_LIST",
         "MATCH_ARM",