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>
This commit is contained in:
commit
545b068a77
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -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"
|
||||
|
@ -639,7 +639,16 @@ fn collect_stmt(&mut self, s: ast::Stmt) {
|
||||
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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -914,7 +914,7 @@ fn infer_block(
|
||||
) -> 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 @@ fn infer_block(
|
||||
}
|
||||
}
|
||||
|
||||
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, .. } => {
|
||||
|
@ -407,3 +407,39 @@ fn test3() {
|
||||
"]],
|
||||
);
|
||||
}
|
||||
|
||||
#[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 ()
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -102,6 +102,16 @@ fn let_stmt(p: &mut Parser, m: Marker, with_semi: StmtWithSemi) {
|
||||
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 => {
|
||||
|
@ -234,6 +234,7 @@ pub enum SyntaxKind {
|
||||
NAME,
|
||||
NAME_REF,
|
||||
LET_STMT,
|
||||
LET_ELSE,
|
||||
EXPR_STMT,
|
||||
GENERIC_PARAM_LIST,
|
||||
GENERIC_PARAM,
|
||||
|
@ -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" }
|
||||
|
@ -722,9 +722,19 @@ pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax,
|
||||
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 @@ fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||
}
|
||||
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 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
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)
|
||||
|
@ -198,6 +198,7 @@ pub(crate) struct KindsSrc<'a> {
|
||||
"NAME",
|
||||
"NAME_REF",
|
||||
"LET_STMT",
|
||||
"LET_ELSE",
|
||||
"EXPR_STMT",
|
||||
"GENERIC_PARAM_LIST",
|
||||
"GENERIC_PARAM",
|
||||
|
51
crates/syntax/test_data/parser/inline/ok/0194_let_else.rast
Normal file
51
crates/syntax/test_data/parser/inline/ok/0194_let_else.rast
Normal file
@ -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"
|
@ -0,0 +1 @@
|
||||
fn f() { let Some(x) = opt else { return }; }
|
Loading…
Reference in New Issue
Block a user