parser: refactor expectVarDeclExprStatement to match upstream

Restructure expectVarDeclExprStatement to match upstream Parse.zig's
approach: check for '=' first, then handle var decl init vs expression
statement separately. This fixes parsing of var decls with container
types (e.g., `const x: struct {} = val`), where the '}' of the type
was incorrectly treated as a block-terminated expression.

Also make container member parsing strict (longjmp on unexpected tokens
instead of recovery), and add for/while/labeled-block handling in
parseTypeExpr for function return types.

376/381 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-02-11 12:02:24 +00:00
parent fdefdc98c2
commit e5cbd806c4

131
parser.c
View File

@@ -2650,88 +2650,66 @@ static AstNodeIndex expectVarDeclExprStatement(
const uint32_t lhs_count = p->scratch.len - scratch_top.old_len;
assert(lhs_count > 0);
if (lhs_count == 1) {
// Try to eat '=' for assignment/initialization
// (matches upstream: `const equal_token = p.eatToken(.equal) orelse eql:`)
AstTokenIndex equal_token = eatToken(p, TOKEN_EQUAL);
if (equal_token == null_token) {
if (lhs_count > 1) {
// Destructure requires '='
fprintf(stderr, "expected '='\n");
longjmp(p->error_jmp, 1);
}
const AstNodeIndex lhs = p->scratch.arr[scratch_top.old_len];
switch (p->token_tags[p->tok_i]) {
case TOKEN_SEMICOLON:
p->tok_i++;
if (comptime_token != null_token) {
const AstNodeTag lhs_tag = p->nodes.tags[lhs];
if (lhs_tag != AST_NODE_SIMPLE_VAR_DECL
&& lhs_tag != AST_NODE_ALIGNED_VAR_DECL
&& lhs_tag != AST_NODE_LOCAL_VAR_DECL
&& lhs_tag != AST_NODE_GLOBAL_VAR_DECL) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_COMPTIME,
.main_token = comptime_token,
.data = { .lhs = lhs, .rhs = 0 },
});
}
}
return lhs;
case TOKEN_R_BRACE:
// Expression that doesn't need semicolon (block-terminated)
if (comptime_token != null_token) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_COMPTIME,
.main_token = comptime_token,
.data = { .lhs = lhs, .rhs = 0 },
});
}
return lhs;
default: {
// Check if expression ended with a block (previous token is })
// and thus doesn't need a semicolon
if (p->token_tags[p->tok_i - 1] == TOKEN_R_BRACE) {
if (comptime_token != null_token) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_COMPTIME,
.main_token = comptime_token,
.data = { .lhs = lhs, .rhs = 0 },
});
}
return lhs;
}
const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
if (assign_tag == AST_NODE_ROOT) {
fprintf(stderr,
"expectVarDeclExprStatement: unexpected token %s\n",
tokenizerGetTagString(p->token_tags[p->tok_i]));
longjmp(p->error_jmp, 1);
}
if (assign_tag == AST_NODE_ASSIGN) {
// Check if lhs is a var decl that needs initialization
const AstNodeTag lhs_tag = p->nodes.tags[lhs];
if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL
|| lhs_tag == AST_NODE_ALIGNED_VAR_DECL
|| lhs_tag == AST_NODE_LOCAL_VAR_DECL
|| lhs_tag == AST_NODE_GLOBAL_VAR_DECL) {
p->tok_i++;
p->nodes.datas[lhs].rhs = expectExpr(p);
expectSemicolon(p);
return lhs;
}
}
const AstTokenIndex op_token = nextToken(p);
const AstNodeIndex rhs = expectExpr(p);
expectSemicolon(p);
const AstNodeTag lhs_tag = p->nodes.tags[lhs];
if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL
|| lhs_tag == AST_NODE_ALIGNED_VAR_DECL
|| lhs_tag == AST_NODE_LOCAL_VAR_DECL
|| lhs_tag == AST_NODE_GLOBAL_VAR_DECL) {
// var decl without init requires '='
fprintf(stderr, "expected '='\n");
longjmp(p->error_jmp, 1);
}
// Expression statement: finish with assignment operators or semicolon
const AstNodeIndex expr = finishAssignExpr(p, lhs);
// Semicolon is optional for block-terminated expressions
eatToken(p, TOKEN_SEMICOLON);
if (comptime_token != null_token) {
return addNode(&p->nodes,
(AstNodeItem) {
.tag = assign_tag,
.main_token = op_token,
.data = { .lhs = lhs, .rhs = rhs },
.tag = AST_NODE_COMPTIME,
.main_token = comptime_token,
.data = { .lhs = expr, .rhs = 0 },
});
}
return expr;
}
// Have '=', parse RHS and semicolon
const AstNodeIndex rhs = expectExpr(p);
expectSemicolon(p);
if (lhs_count == 1) {
const AstNodeIndex lhs = p->scratch.arr[scratch_top.old_len];
const AstNodeTag lhs_tag = p->nodes.tags[lhs];
if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL
|| lhs_tag == AST_NODE_ALIGNED_VAR_DECL
|| lhs_tag == AST_NODE_LOCAL_VAR_DECL
|| lhs_tag == AST_NODE_GLOBAL_VAR_DECL) {
// var decl initialization: const x = val;
p->nodes.datas[lhs].rhs = rhs;
return lhs;
}
// Simple assignment: x = val;
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_ASSIGN,
.main_token = equal_token,
.data = { .lhs = lhs, .rhs = rhs },
});
}
// Destructure: a, b, c = rhs
const AstTokenIndex equal_token = expectToken(p, TOKEN_EQUAL);
const AstNodeIndex rhs = expectExpr(p);
expectSemicolon(p);
// rhs and semicolon already parsed above
// Store count + lhs nodes in extra_data
const AstNodeIndex extra_start = p->extra_data.len;
@@ -3181,11 +3159,10 @@ static Members parseContainerMembers(Parser* p) {
case TOKEN_EOF:
trailing = false;
goto break_loop;
default:;
default:
fprintf(stderr, "expected comma after field\n");
longjmp(p->error_jmp, 1);
}
findNextContainerMember(p);
continue;
}
}