about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-10-07 15:09:42 +0000
committerGitHub <noreply@github.com>2021-10-07 15:09:42 +0000
commit545b068a7720374a13e04db24e0bef6237822f2a (patch)
treea3a13e4412eb9402ad0e301bbbde6594c1402872
parent4675410f07239d46ac636a008b4a9f3d77f41b43 (diff)
parentf8acae78955ed27cc569a14ac36c48d56f9cea58 (diff)
downloadrust-545b068a7720374a13e04db24e0bef6237822f2a.tar.gz
rust-545b068a7720374a13e04db24e0bef6237822f2a.zip
Merge #10474
10474: feat: Support `let...else` r=jonas-schievink a=jonas-schievink

bors r+

closes https://github.com/rust-analyzer/rust-analyzer/issues/10469

Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
-rw-r--r--Cargo.lock4
-rw-r--r--crates/hir_def/src/body/lower.rs11
-rw-r--r--crates/hir_def/src/body/scope.rs6
-rw-r--r--crates/hir_def/src/expr.rs12
-rw-r--r--crates/hir_ty/src/infer/expr.rs9
-rw-r--r--crates/hir_ty/src/tests/never_type.rs36
-rw-r--r--crates/parser/src/grammar/expressions.rs10
-rw-r--r--crates/parser/src/syntax_kind/generated.rs1
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/ast/generated/nodes.rs26
-rw-r--r--crates/syntax/src/tests/ast_src.rs1
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0194_let_else.rast51
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0194_let_else.rs1
13 files changed, 162 insertions, 8 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 17795ead23e..5aba6387f0b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1761,9 +1761,9 @@ dependencies = [
 
 [[package]]
 name = "ungrammar"
-version = "1.14.6"
+version = "1.14.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb1cd6666863b2ff36bab1ced85c4b5e651638705f306f3cfad0a367f85ea715"
+checksum = "403c1892ad46cacffb28c73550172999c6c75f70ca9c97bcafc8ce99d6421529"
 
 [[package]]
 name = "unicase"
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 372cf9aba8a..31362067612 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -639,7 +639,16 @@ impl ExprCollector<'_> {
                 let type_ref =
                     stmt.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it)));
                 let initializer = stmt.initializer().map(|e| self.collect_expr(e));
-                self.statements_in_scope.push(Statement::Let { pat, type_ref, initializer });
+                let else_branch = stmt
+                    .let_else()
+                    .and_then(|let_else| let_else.block_expr())
+                    .map(|block| self.collect_block(block));
+                self.statements_in_scope.push(Statement::Let {
+                    pat,
+                    type_ref,
+                    initializer,
+                    else_branch,
+                });
             }
             ast::Stmt::ExprStmt(stmt) => {
                 if let Some(expr) = stmt.expr() {
diff --git a/crates/hir_def/src/body/scope.rs b/crates/hir_def/src/body/scope.rs
index 5d9dd73a120..8a48ad91991 100644
--- a/crates/hir_def/src/body/scope.rs
+++ b/crates/hir_def/src/body/scope.rs
@@ -149,11 +149,15 @@ fn compute_block_scopes(
 ) {
     for stmt in statements {
         match stmt {
-            Statement::Let { pat, initializer, .. } => {
+            Statement::Let { pat, initializer, else_branch, .. } => {
                 if let Some(expr) = initializer {
                     scopes.set_scope(*expr, scope);
                     compute_expr_scopes(*expr, body, scopes, scope);
                 }
+                if let Some(expr) = else_branch {
+                    scopes.set_scope(*expr, scope);
+                    compute_expr_scopes(*expr, body, scopes, scope);
+                }
                 scope = scopes.new_scope(scope);
                 scopes.add_bindings(body, scope, *pat);
             }
diff --git a/crates/hir_def/src/expr.rs b/crates/hir_def/src/expr.rs
index b508d875e81..17af48b7bf6 100644
--- a/crates/hir_def/src/expr.rs
+++ b/crates/hir_def/src/expr.rs
@@ -208,8 +208,16 @@ pub struct RecordLitField {
 
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub enum Statement {
-    Let { pat: PatId, type_ref: Option<Interned<TypeRef>>, initializer: Option<ExprId> },
-    Expr { expr: ExprId, has_semi: bool },
+    Let {
+        pat: PatId,
+        type_ref: Option<Interned<TypeRef>>,
+        initializer: Option<ExprId>,
+        else_branch: Option<ExprId>,
+    },
+    Expr {
+        expr: ExprId,
+        has_semi: bool,
+    },
 }
 
 impl Expr {
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index f5bc898a85b..ada5717f1a5 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -914,7 +914,7 @@ impl<'a> InferenceContext<'a> {
     ) -> Ty {
         for stmt in statements {
             match stmt {
-                Statement::Let { pat, type_ref, initializer } => {
+                Statement::Let { pat, type_ref, initializer, else_branch } => {
                     let decl_ty = type_ref
                         .as_ref()
                         .map(|tr| self.make_ty(tr))
@@ -931,6 +931,13 @@ impl<'a> InferenceContext<'a> {
                         }
                     }
 
+                    if let Some(expr) = else_branch {
+                        self.infer_expr_coerce(
+                            *expr,
+                            &Expectation::has_type(Ty::new(&Interner, TyKind::Never)),
+                        );
+                    }
+
                     self.infer_pat(*pat, &ty, BindingMode::default());
                 }
                 Statement::Expr { expr, .. } => {
diff --git a/crates/hir_ty/src/tests/never_type.rs b/crates/hir_ty/src/tests/never_type.rs
index 335c474df76..8be2b8ec108 100644
--- a/crates/hir_ty/src/tests/never_type.rs
+++ b/crates/hir_ty/src/tests/never_type.rs
@@ -407,3 +407,39 @@ fn diverging_expression_3_break() {
         "]],
     );
 }
+
+#[test]
+fn let_else_must_diverge() {
+    check_infer_with_mismatches(
+        r#"
+        fn f() {
+            let 1 = 2 else {
+                return;
+            };
+        }
+        "#,
+        expect![[r#"
+            7..54 '{     ...  }; }': ()
+            17..18 '1': i32
+            17..18 '1': i32
+            21..22 '2': i32
+            28..51 '{     ...     }': !
+            38..44 'return': !
+        "#]],
+    );
+    check_infer_with_mismatches(
+        r#"
+        fn f() {
+            let 1 = 2 else {};
+        }
+        "#,
+        expect![[r#"
+            7..33 '{     ... {}; }': ()
+            17..18 '1': i32
+            17..18 '1': i32
+            21..22 '2': i32
+            28..30 '{}': ()
+            28..30: expected !, got ()
+        "#]],
+    );
+}
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index aa171674ed9..54eb96d84e5 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -102,6 +102,16 @@ pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi, prefer_expr: bool) {
             expressions::expr(p);
         }
 
+        if p.at(T![else]) {
+            // test let_else
+            // fn f() { let Some(x) = opt else { return }; }
+
+            let m = p.start();
+            p.bump(T![else]);
+            block_expr(p);
+            m.complete(p, LET_ELSE);
+        }
+
         match with_semi {
             StmtWithSemi::No => (),
             StmtWithSemi::Optional => {
diff --git a/crates/parser/src/syntax_kind/generated.rs b/crates/parser/src/syntax_kind/generated.rs
index 842d66755c6..a9010c0c319 100644
--- a/crates/parser/src/syntax_kind/generated.rs
+++ b/crates/parser/src/syntax_kind/generated.rs
@@ -234,6 +234,7 @@ pub enum SyntaxKind {
     NAME,
     NAME_REF,
     LET_STMT,
+    LET_ELSE,
     EXPR_STMT,
     GENERIC_PARAM_LIST,
     GENERIC_PARAM,
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 3c5adb97fba..fba5db9a93e 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -29,7 +29,7 @@ rayon = "1"
 expect-test = "1.1"
 proc-macro2 = "1.0.8"
 quote = "1.0.2"
-ungrammar = "=1.14.6"
+ungrammar = "=1.14.8"
 
 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 f69c8736638..15b8371c3eb 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -722,10 +722,20 @@ impl LetStmt {
     pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
     pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
     pub fn initializer(&self) -> Option<Expr> { support::child(&self.syntax) }
+    pub fn let_else(&self) -> Option<LetElse> { support::child(&self.syntax) }
     pub fn semicolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![;]) }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct LetElse {
+    pub(crate) syntax: SyntaxNode,
+}
+impl LetElse {
+    pub fn else_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![else]) }
+    pub fn block_expr(&self) -> Option<BlockExpr> { support::child(&self.syntax) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ArrayExpr {
     pub(crate) syntax: SyntaxNode,
 }
@@ -2304,6 +2314,17 @@ impl AstNode for LetStmt {
     }
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
+impl AstNode for LetElse {
+    fn can_cast(kind: SyntaxKind) -> bool { kind == LET_ELSE }
+    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 ArrayExpr {
     fn can_cast(kind: SyntaxKind) -> bool { kind == ARRAY_EXPR }
     fn cast(syntax: SyntaxNode) -> Option<Self> {
@@ -4320,6 +4341,11 @@ impl std::fmt::Display for LetStmt {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
+impl std::fmt::Display for LetElse {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
 impl std::fmt::Display for ArrayExpr {
     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 efbb7ca0ccd..2344fc59d16 100644
--- a/crates/syntax/src/tests/ast_src.rs
+++ b/crates/syntax/src/tests/ast_src.rs
@@ -198,6 +198,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
         "NAME",
         "NAME_REF",
         "LET_STMT",
+        "LET_ELSE",
         "EXPR_STMT",
         "GENERIC_PARAM_LIST",
         "GENERIC_PARAM",
diff --git a/crates/syntax/test_data/parser/inline/ok/0194_let_else.rast b/crates/syntax/test_data/parser/inline/ok/0194_let_else.rast
new file mode 100644
index 00000000000..ed0613a38f9
--- /dev/null
+++ b/crates/syntax/test_data/parser/inline/ok/0194_let_else.rast
@@ -0,0 +1,51 @@
+SOURCE_FILE@0..46
+  FN@0..45
+    FN_KW@0..2 "fn"
+    WHITESPACE@2..3 " "
+    NAME@3..4
+      IDENT@3..4 "f"
+    PARAM_LIST@4..6
+      L_PAREN@4..5 "("
+      R_PAREN@5..6 ")"
+    WHITESPACE@6..7 " "
+    BLOCK_EXPR@7..45
+      STMT_LIST@7..45
+        L_CURLY@7..8 "{"
+        WHITESPACE@8..9 " "
+        LET_STMT@9..43
+          LET_KW@9..12 "let"
+          WHITESPACE@12..13 " "
+          TUPLE_STRUCT_PAT@13..20
+            PATH@13..17
+              PATH_SEGMENT@13..17
+                NAME_REF@13..17
+                  IDENT@13..17 "Some"
+            L_PAREN@17..18 "("
+            IDENT_PAT@18..19
+              NAME@18..19
+                IDENT@18..19 "x"
+            R_PAREN@19..20 ")"
+          WHITESPACE@20..21 " "
+          EQ@21..22 "="
+          WHITESPACE@22..23 " "
+          PATH_EXPR@23..26
+            PATH@23..26
+              PATH_SEGMENT@23..26
+                NAME_REF@23..26
+                  IDENT@23..26 "opt"
+          WHITESPACE@26..27 " "
+          LET_ELSE@27..42
+            ELSE_KW@27..31 "else"
+            WHITESPACE@31..32 " "
+            BLOCK_EXPR@32..42
+              STMT_LIST@32..42
+                L_CURLY@32..33 "{"
+                WHITESPACE@33..34 " "
+                RETURN_EXPR@34..40
+                  RETURN_KW@34..40 "return"
+                WHITESPACE@40..41 " "
+                R_CURLY@41..42 "}"
+          SEMICOLON@42..43 ";"
+        WHITESPACE@43..44 " "
+        R_CURLY@44..45 "}"
+  WHITESPACE@45..46 "\n"
diff --git a/crates/syntax/test_data/parser/inline/ok/0194_let_else.rs b/crates/syntax/test_data/parser/inline/ok/0194_let_else.rs
new file mode 100644
index 00000000000..8303de06f1e
--- /dev/null
+++ b/crates/syntax/test_data/parser/inline/ok/0194_let_else.rs
@@ -0,0 +1 @@
+fn f() { let Some(x) = opt else { return }; }