Unnamed repository; edit this file 'description' to name the repository.
Improve error handling for top-level `let` statements
This commit addresses the issue of excessive and unrelated errors generated by top-level `let` statements. Now, only a single error is produced, indicating that `let` statements are invalid at the top level.
Yutaro Ohno 2023-12-01
parent c9d189d · commit e076192
-rw-r--r--crates/parser/src/grammar.rs10
-rw-r--r--crates/parser/src/grammar/expressions.rs84
-rw-r--r--crates/parser/src/grammar/items.rs1
-rw-r--r--crates/parser/test_data/parser/inline/err/0024_top_level_let.rast30
-rw-r--r--crates/parser/test_data/parser/inline/err/0024_top_level_let.rs1
5 files changed, 84 insertions, 42 deletions
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index 6a2a9adce1..19da297b58 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -376,6 +376,16 @@ fn error_block(p: &mut Parser<'_>, message: &str) {
m.complete(p, ERROR);
}
+// test_err top_level_let
+// let ref foo: fn() = 1 + 3;
+fn error_let_stmt(p: &mut Parser<'_>, message: &str) {
+ assert!(p.at(T![let]));
+ let m = p.start();
+ p.error(message);
+ expressions::let_stmt(p, expressions::Semicolon::Optional);
+ m.complete(p, ERROR);
+}
+
/// The `parser` passed this is required to at least consume one token if it returns `true`.
/// If the `parser` returns false, parsing will stop.
fn delimited(
diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs
index 8542d6b86b..e346ece2f9 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -59,7 +59,8 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) {
attributes::outer_attrs(p);
if p.at(T![let]) {
- let_stmt(p, m, semicolon);
+ let_stmt(p, semicolon);
+ m.complete(p, LET_STMT);
return;
}
@@ -109,54 +110,53 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) {
m.complete(p, EXPR_STMT);
}
}
+}
- // test let_stmt
- // fn f() { let x: i32 = 92; }
- fn let_stmt(p: &mut Parser<'_>, m: Marker, with_semi: Semicolon) {
- p.bump(T![let]);
- patterns::pattern(p);
- if p.at(T![:]) {
- // test let_stmt_ascription
- // fn f() { let x: i32; }
- types::ascription(p);
- }
+// test let_stmt
+// fn f() { let x: i32 = 92; }
+pub(super) fn let_stmt(p: &mut Parser<'_>, with_semi: Semicolon) {
+ p.bump(T![let]);
+ patterns::pattern(p);
+ if p.at(T![:]) {
+ // test let_stmt_ascription
+ // fn f() { let x: i32; }
+ types::ascription(p);
+ }
- let mut expr_after_eq: Option<CompletedMarker> = None;
- if p.eat(T![=]) {
- // test let_stmt_init
- // fn f() { let x = 92; }
- expr_after_eq = expressions::expr(p);
- }
+ let mut expr_after_eq: Option<CompletedMarker> = None;
+ if p.eat(T![=]) {
+ // test let_stmt_init
+ // fn f() { let x = 92; }
+ expr_after_eq = expressions::expr(p);
+ }
- if p.at(T![else]) {
- // test_err let_else_right_curly_brace
- // fn func() { let Some(_) = {Some(1)} else { panic!("h") };}
- if let Some(expr) = expr_after_eq {
- if BlockLike::is_blocklike(expr.kind()) {
- p.error(
- "right curly brace `}` before `else` in a `let...else` statement not allowed",
- )
- }
+ if p.at(T![else]) {
+ // test_err let_else_right_curly_brace
+ // fn func() { let Some(_) = {Some(1)} else { panic!("h") };}
+ if let Some(expr) = expr_after_eq {
+ if BlockLike::is_blocklike(expr.kind()) {
+ p.error(
+ "right curly brace `}` before `else` in a `let...else` statement not allowed",
+ )
}
-
- // 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 {
- Semicolon::Forbidden => (),
- Semicolon::Optional => {
- p.eat(T![;]);
- }
- Semicolon::Required => {
- p.expect(T![;]);
- }
+ // 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 {
+ Semicolon::Forbidden => (),
+ Semicolon::Optional => {
+ p.eat(T![;]);
+ }
+ Semicolon::Required => {
+ p.expect(T![;]);
}
- m.complete(p, LET_STMT);
}
}
diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs
index 4e850b1f74..34fd3420f1 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -79,6 +79,7 @@ pub(super) fn item_or_macro(p: &mut Parser<'_>, stop_on_r_curly: bool) {
e.complete(p, ERROR);
}
EOF | T!['}'] => p.error("expected an item"),
+ T![let] => error_let_stmt(p, "expected an item"),
_ => p.err_and_bump("expected an item"),
}
}
diff --git a/crates/parser/test_data/parser/inline/err/0024_top_level_let.rast b/crates/parser/test_data/parser/inline/err/0024_top_level_let.rast
new file mode 100644
index 0000000000..5ddef5f3f0
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0024_top_level_let.rast
@@ -0,0 +1,30 @@
+SOURCE_FILE
+ ERROR
+ LET_KW "let"
+ WHITESPACE " "
+ IDENT_PAT
+ REF_KW "ref"
+ WHITESPACE " "
+ NAME
+ IDENT "foo"
+ COLON ":"
+ WHITESPACE " "
+ FN_PTR_TYPE
+ FN_KW "fn"
+ PARAM_LIST
+ L_PAREN "("
+ R_PAREN ")"
+ WHITESPACE " "
+ EQ "="
+ WHITESPACE " "
+ BIN_EXPR
+ LITERAL
+ INT_NUMBER "1"
+ WHITESPACE " "
+ PLUS "+"
+ WHITESPACE " "
+ LITERAL
+ INT_NUMBER "3"
+ SEMICOLON ";"
+ WHITESPACE "\n"
+error 0: expected an item
diff --git a/crates/parser/test_data/parser/inline/err/0024_top_level_let.rs b/crates/parser/test_data/parser/inline/err/0024_top_level_let.rs
new file mode 100644
index 0000000000..3d3e7dd56c
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/err/0024_top_level_let.rs
@@ -0,0 +1 @@
+let ref foo: fn() = 1 + 3;