diff --git a/Cargo.lock b/Cargo.lock index 17795ead23..5aba6387f0 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 372cf9aba8..3136206761 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 5d9dd73a12..8a48ad9199 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 b508d875e8..17af48b7bf 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>, initializer: Option }, - Expr { expr: ExprId, has_semi: bool }, + Let { + pat: PatId, + type_ref: Option>, + initializer: Option, + else_branch: Option, + }, + 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 f5bc898a85..ada5717f1a 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 335c474df7..8be2b8ec10 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 aa171674ed..54eb96d84e 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 842d66755c..a9010c0c31 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 3c5adb97fb..fba5db9a93 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 f69c873663..15b8371c3e 100644 --- a/crates/syntax/src/ast/generated/nodes.rs +++ b/crates/syntax/src/ast/generated/nodes.rs @@ -722,9 +722,19 @@ impl LetStmt { pub fn ty(&self) -> Option { support::child(&self.syntax) } pub fn eq_token(&self) -> Option { support::token(&self.syntax, T![=]) } pub fn initializer(&self) -> Option { support::child(&self.syntax) } + pub fn let_else(&self) -> Option { support::child(&self.syntax) } pub fn semicolon_token(&self) -> Option { 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 { support::token(&self.syntax, T![else]) } + pub fn block_expr(&self) -> Option { 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 { + 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 { @@ -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 efbb7ca0cc..2344fc59d1 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 0000000000..ed0613a38f --- /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 0000000000..8303de06f1 --- /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 }; }