mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-27 20:35:09 +00:00
Support let...else
This commit is contained in:
parent
4675410f07
commit
f8acae7895
13 changed files with 162 additions and 8 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1761,9 +1761,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ungrammar"
|
name = "ungrammar"
|
||||||
version = "1.14.6"
|
version = "1.14.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fb1cd6666863b2ff36bab1ced85c4b5e651638705f306f3cfad0a367f85ea715"
|
checksum = "403c1892ad46cacffb28c73550172999c6c75f70ca9c97bcafc8ce99d6421529"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
|
|
|
@ -639,7 +639,16 @@ impl ExprCollector<'_> {
|
||||||
let type_ref =
|
let type_ref =
|
||||||
stmt.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it)));
|
stmt.ty().map(|it| Interned::new(TypeRef::from_ast(&self.ctx(), it)));
|
||||||
let initializer = stmt.initializer().map(|e| self.collect_expr(e));
|
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) => {
|
ast::Stmt::ExprStmt(stmt) => {
|
||||||
if let Some(expr) = stmt.expr() {
|
if let Some(expr) = stmt.expr() {
|
||||||
|
|
|
@ -149,11 +149,15 @@ fn compute_block_scopes(
|
||||||
) {
|
) {
|
||||||
for stmt in statements {
|
for stmt in statements {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::Let { pat, initializer, .. } => {
|
Statement::Let { pat, initializer, else_branch, .. } => {
|
||||||
if let Some(expr) = initializer {
|
if let Some(expr) = initializer {
|
||||||
scopes.set_scope(*expr, scope);
|
scopes.set_scope(*expr, scope);
|
||||||
compute_expr_scopes(*expr, body, scopes, 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);
|
scope = scopes.new_scope(scope);
|
||||||
scopes.add_bindings(body, scope, *pat);
|
scopes.add_bindings(body, scope, *pat);
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,8 +208,16 @@ pub struct RecordLitField {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum Statement {
|
pub enum Statement {
|
||||||
Let { pat: PatId, type_ref: Option<Interned<TypeRef>>, initializer: Option<ExprId> },
|
Let {
|
||||||
Expr { expr: ExprId, has_semi: bool },
|
pat: PatId,
|
||||||
|
type_ref: Option<Interned<TypeRef>>,
|
||||||
|
initializer: Option<ExprId>,
|
||||||
|
else_branch: Option<ExprId>,
|
||||||
|
},
|
||||||
|
Expr {
|
||||||
|
expr: ExprId,
|
||||||
|
has_semi: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
|
|
|
@ -914,7 +914,7 @@ impl<'a> InferenceContext<'a> {
|
||||||
) -> Ty {
|
) -> Ty {
|
||||||
for stmt in statements {
|
for stmt in statements {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::Let { pat, type_ref, initializer } => {
|
Statement::Let { pat, type_ref, initializer, else_branch } => {
|
||||||
let decl_ty = type_ref
|
let decl_ty = type_ref
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|tr| self.make_ty(tr))
|
.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());
|
self.infer_pat(*pat, &ty, BindingMode::default());
|
||||||
}
|
}
|
||||||
Statement::Expr { expr, .. } => {
|
Statement::Expr { expr, .. } => {
|
||||||
|
|
|
@ -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 ()
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -102,6 +102,16 @@ pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi, prefer_expr: bool) {
|
||||||
expressions::expr(p);
|
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 {
|
match with_semi {
|
||||||
StmtWithSemi::No => (),
|
StmtWithSemi::No => (),
|
||||||
StmtWithSemi::Optional => {
|
StmtWithSemi::Optional => {
|
||||||
|
|
|
@ -234,6 +234,7 @@ pub enum SyntaxKind {
|
||||||
NAME,
|
NAME,
|
||||||
NAME_REF,
|
NAME_REF,
|
||||||
LET_STMT,
|
LET_STMT,
|
||||||
|
LET_ELSE,
|
||||||
EXPR_STMT,
|
EXPR_STMT,
|
||||||
GENERIC_PARAM_LIST,
|
GENERIC_PARAM_LIST,
|
||||||
GENERIC_PARAM,
|
GENERIC_PARAM,
|
||||||
|
|
|
@ -29,7 +29,7 @@ rayon = "1"
|
||||||
expect-test = "1.1"
|
expect-test = "1.1"
|
||||||
proc-macro2 = "1.0.8"
|
proc-macro2 = "1.0.8"
|
||||||
quote = "1.0.2"
|
quote = "1.0.2"
|
||||||
ungrammar = "=1.14.6"
|
ungrammar = "=1.14.8"
|
||||||
|
|
||||||
test_utils = { path = "../test_utils" }
|
test_utils = { path = "../test_utils" }
|
||||||
sourcegen = { path = "../sourcegen" }
|
sourcegen = { path = "../sourcegen" }
|
||||||
|
|
|
@ -722,9 +722,19 @@ impl LetStmt {
|
||||||
pub fn ty(&self) -> Option<Type> { support::child(&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 eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
|
||||||
pub fn initializer(&self) -> Option<Expr> { support::child(&self.syntax) }
|
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![;]) }
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ArrayExpr {
|
pub struct ArrayExpr {
|
||||||
pub(crate) syntax: SyntaxNode,
|
pub(crate) syntax: SyntaxNode,
|
||||||
|
@ -2304,6 +2314,17 @@ impl AstNode for LetStmt {
|
||||||
}
|
}
|
||||||
fn syntax(&self) -> &SyntaxNode { &self.syntax }
|
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 {
|
impl AstNode for ArrayExpr {
|
||||||
fn can_cast(kind: SyntaxKind) -> bool { kind == ARRAY_EXPR }
|
fn can_cast(kind: SyntaxKind) -> bool { kind == ARRAY_EXPR }
|
||||||
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||||
|
@ -4320,6 +4341,11 @@ impl std::fmt::Display for LetStmt {
|
||||||
std::fmt::Display::fmt(self.syntax(), f)
|
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 {
|
impl std::fmt::Display for ArrayExpr {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
std::fmt::Display::fmt(self.syntax(), f)
|
std::fmt::Display::fmt(self.syntax(), f)
|
||||||
|
|
|
@ -198,6 +198,7 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
|
||||||
"NAME",
|
"NAME",
|
||||||
"NAME_REF",
|
"NAME_REF",
|
||||||
"LET_STMT",
|
"LET_STMT",
|
||||||
|
"LET_ELSE",
|
||||||
"EXPR_STMT",
|
"EXPR_STMT",
|
||||||
"GENERIC_PARAM_LIST",
|
"GENERIC_PARAM_LIST",
|
||||||
"GENERIC_PARAM",
|
"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 a new issue