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:
bors[bot] 2021-10-07 15:09:42 +00:00 committed by GitHub
commit 545b068a77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 162 additions and 8 deletions

4
Cargo.lock generated
View File

@ -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"

View File

@ -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() {

View File

@ -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);
}

View File

@ -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 {

View File

@ -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, .. } => {

View File

@ -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 ()
"#]],
);
}

View File

@ -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 => {

View File

@ -234,6 +234,7 @@ pub enum SyntaxKind {
NAME,
NAME_REF,
LET_STMT,
LET_ELSE,
EXPR_STMT,
GENERIC_PARAM_LIST,
GENERIC_PARAM,

View File

@ -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" }

View File

@ -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)

View File

@ -198,6 +198,7 @@ pub(crate) struct KindsSrc<'a> {
"NAME",
"NAME_REF",
"LET_STMT",
"LET_ELSE",
"EXPR_STMT",
"GENERIC_PARAM_LIST",
"GENERIC_PARAM",

View 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"

View File

@ -0,0 +1 @@
fn f() { let Some(x) = opt else { return }; }