fix(diagnostics): point reserved-type-name binding errors at the binding (issue 0076)
The reserved-type-name binding diagnostic fired correctly but underlined the enclosing statement / if / while / for / match / protocol / #objc_class block because every binding-name check reused the parent `node.span`. Thread each binding name's own span through the AST and parser, and pass it to `checkBindingNames`: - ast: add name spans to VarDecl, DestructureDecl, If/WhileExpr, ForExpr (capture + index), MatchArm, Catch/OnFailStmt, Protocol/ForeignMethodDecl. - parser: populate each span at the binding site from the name token's loc; destructure reuses each target identifier's own span. - semantic_diagnostics: every checkBindingName call now passes the binding's own span — no site falls back to node.span. fn/lambda params already used Param.name_span. Carets now land on the offending identifier itself. New regression examples/1125 asserts the protocol default-body and sx-defined #objc_class method param spans; 0125/1119-1124 expected updated to the precise carets.
This commit is contained in:
11
src/ast.zig
11
src/ast.zig
@@ -272,6 +272,7 @@ pub const IfExpr = struct {
|
||||
is_inline: bool, // true for `if cond then a else b`
|
||||
is_comptime: bool = false, // true for `inline if` — compile-time branch elimination
|
||||
binding_name: ?[]const u8 = null, // for `if val := expr { ... }` optional binding
|
||||
binding_span: ?Span = null, // span of `binding_name` (set iff `binding_name` is)
|
||||
};
|
||||
|
||||
pub const MatchExpr = struct {
|
||||
@@ -285,6 +286,7 @@ pub const MatchArm = struct {
|
||||
body: *Node,
|
||||
is_break: bool,
|
||||
capture: ?[]const u8 = null, // payload binding name: case .variant: (name) { ... }
|
||||
capture_span: ?Span = null, // span of `capture` (set iff `capture` is)
|
||||
};
|
||||
|
||||
pub const ConstDecl = struct {
|
||||
@@ -295,6 +297,7 @@ pub const ConstDecl = struct {
|
||||
|
||||
pub const VarDecl = struct {
|
||||
name: []const u8,
|
||||
name_span: Span,
|
||||
type_annotation: ?*Node,
|
||||
value: ?*Node,
|
||||
is_foreign: bool = false,
|
||||
@@ -329,6 +332,7 @@ pub const MultiAssign = struct {
|
||||
|
||||
pub const DestructureDecl = struct {
|
||||
names: []const []const u8,
|
||||
name_spans: []const Span, // one per entry in `names`, same order
|
||||
value: *Node,
|
||||
};
|
||||
|
||||
@@ -449,6 +453,7 @@ pub const TryExpr = struct {
|
||||
pub const CatchExpr = struct {
|
||||
operand: *Node,
|
||||
binding: ?[]const u8 = null,
|
||||
binding_span: ?Span = null, // span of `binding` (set iff `binding` is)
|
||||
body: *Node,
|
||||
is_match_body: bool = false,
|
||||
};
|
||||
@@ -458,6 +463,7 @@ pub const CatchExpr = struct {
|
||||
/// a bare expression (`onfail EXPR;`).
|
||||
pub const OnFailStmt = struct {
|
||||
binding: ?[]const u8 = null,
|
||||
binding_span: ?Span = null, // span of `binding` (set iff `binding` is)
|
||||
body: *Node,
|
||||
};
|
||||
|
||||
@@ -551,13 +557,16 @@ pub const WhileExpr = struct {
|
||||
condition: *Node,
|
||||
body: *Node,
|
||||
binding_name: ?[]const u8 = null, // for `while val := expr { ... }` optional binding
|
||||
binding_span: ?Span = null, // span of `binding_name` (set iff `binding_name` is)
|
||||
};
|
||||
|
||||
pub const ForExpr = struct {
|
||||
iterable: *Node,
|
||||
body: *Node,
|
||||
capture_name: []const u8,
|
||||
capture_span: ?Span = null, // span of `capture_name` (null when omitted, e.g. `for 0..N { }`)
|
||||
index_name: ?[]const u8 = null,
|
||||
index_span: ?Span = null, // span of `index_name` (set iff `index_name` is)
|
||||
/// Range form `for start..end (i) { }`: `iterable` is the start, `range_end`
|
||||
/// the (exclusive) end. Null for the iterate-a-collection form
|
||||
/// (`for coll : (x) { }`). For the range form `capture_name` is the cursor
|
||||
@@ -645,6 +654,7 @@ pub const ProtocolMethodDecl = struct {
|
||||
name: []const u8,
|
||||
params: []const *Node, // type_expr nodes for parameter types (excluding implicit self)
|
||||
param_names: []const []const u8, // parameter names (excluding implicit self)
|
||||
param_name_spans: []const Span = &.{}, // one per `param_names` entry; empty for synthesized methods
|
||||
return_type: ?*Node, // null = void return
|
||||
default_body: ?*Node, // null = required method, non-null = default implementation
|
||||
};
|
||||
@@ -670,6 +680,7 @@ pub const ForeignMethodDecl = struct {
|
||||
name: []const u8,
|
||||
params: []const *Node, // type_expr nodes — first is `*Self` for instance methods
|
||||
param_names: []const []const u8,
|
||||
param_name_spans: []const Span = &.{}, // one per `param_names` entry; empty for synthesized methods
|
||||
return_type: ?*Node, // null = void
|
||||
is_static: bool = false, // true for `static name :: ...`
|
||||
jni_descriptor_override: ?[]const u8 = null, // `#jni_method_descriptor("(Sig)Ret")` — JNI runtime only
|
||||
|
||||
@@ -117,11 +117,11 @@ pub const UnknownTypeChecker = struct {
|
||||
switch (node.data) {
|
||||
// ── Binding-introducing nodes: check the name(s), then recurse. ──
|
||||
.var_decl => |vd| {
|
||||
self.checkBindingName(vd.name, node.span);
|
||||
self.checkBindingName(vd.name, vd.name_span);
|
||||
if (vd.value) |v| self.checkBindingNames(v);
|
||||
},
|
||||
.destructure_decl => |dd| {
|
||||
for (dd.names) |n| self.checkBindingName(n, node.span);
|
||||
for (dd.names, dd.name_spans) |n, sp| self.checkBindingName(n, sp);
|
||||
self.checkBindingNames(dd.value);
|
||||
},
|
||||
.fn_decl => |fd| {
|
||||
@@ -137,19 +137,19 @@ pub const UnknownTypeChecker = struct {
|
||||
if (p.default_expr) |de| self.checkBindingNames(de);
|
||||
},
|
||||
.if_expr => |ie| {
|
||||
if (ie.binding_name) |bn| self.checkBindingName(bn, node.span);
|
||||
if (ie.binding_name) |bn| self.checkBindingName(bn, ie.binding_span);
|
||||
self.checkBindingNames(ie.condition);
|
||||
self.checkBindingNames(ie.then_branch);
|
||||
if (ie.else_branch) |e| self.checkBindingNames(e);
|
||||
},
|
||||
.while_expr => |we| {
|
||||
if (we.binding_name) |bn| self.checkBindingName(bn, node.span);
|
||||
if (we.binding_name) |bn| self.checkBindingName(bn, we.binding_span);
|
||||
self.checkBindingNames(we.condition);
|
||||
self.checkBindingNames(we.body);
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
if (fe.capture_name.len != 0) self.checkBindingName(fe.capture_name, node.span);
|
||||
if (fe.index_name) |idx| self.checkBindingName(idx, node.span);
|
||||
if (fe.capture_name.len != 0) self.checkBindingName(fe.capture_name, fe.capture_span);
|
||||
if (fe.index_name) |idx| self.checkBindingName(idx, fe.index_span);
|
||||
self.checkBindingNames(fe.iterable);
|
||||
if (fe.range_end) |re| self.checkBindingNames(re);
|
||||
self.checkBindingNames(fe.body);
|
||||
@@ -157,23 +157,23 @@ pub const UnknownTypeChecker = struct {
|
||||
.match_expr => |me| {
|
||||
self.checkBindingNames(me.subject);
|
||||
for (me.arms) |arm| {
|
||||
if (arm.capture) |cap| self.checkBindingName(cap, node.span);
|
||||
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span);
|
||||
if (arm.pattern) |p| self.checkBindingNames(p);
|
||||
self.checkBindingNames(arm.body);
|
||||
}
|
||||
},
|
||||
.match_arm => |arm| {
|
||||
if (arm.capture) |cap| self.checkBindingName(cap, node.span);
|
||||
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span);
|
||||
if (arm.pattern) |p| self.checkBindingNames(p);
|
||||
self.checkBindingNames(arm.body);
|
||||
},
|
||||
.catch_expr => |ce| {
|
||||
if (ce.binding) |b| self.checkBindingName(b, node.span);
|
||||
if (ce.binding) |b| self.checkBindingName(b, ce.binding_span);
|
||||
self.checkBindingNames(ce.operand);
|
||||
self.checkBindingNames(ce.body);
|
||||
},
|
||||
.onfail_stmt => |os| {
|
||||
if (os.binding) |b| self.checkBindingName(b, node.span);
|
||||
if (os.binding) |b| self.checkBindingName(b, os.binding_span);
|
||||
self.checkBindingNames(os.body);
|
||||
},
|
||||
// impl / protocol-default / foreign-class method bodies: each
|
||||
@@ -183,13 +183,13 @@ pub const UnknownTypeChecker = struct {
|
||||
.impl_block => |ib| for (ib.methods) |m| self.checkBindingNames(m),
|
||||
.protocol_decl => |pd| for (pd.methods) |m| {
|
||||
if (m.default_body) |body| {
|
||||
for (m.param_names) |pn| self.checkBindingName(pn, node.span);
|
||||
for (m.param_names, m.param_name_spans) |pn, sp| self.checkBindingName(pn, sp);
|
||||
self.checkBindingNames(body);
|
||||
}
|
||||
},
|
||||
.foreign_class_decl => |fcd| for (fcd.members) |member| switch (member) {
|
||||
.method => |m| if (m.body) |body| {
|
||||
for (m.param_names) |pn| self.checkBindingName(pn, node.span);
|
||||
for (m.param_names, m.param_name_spans) |pn, sp| self.checkBindingName(pn, sp);
|
||||
self.checkBindingNames(body);
|
||||
},
|
||||
.field, .extends, .implements => {},
|
||||
|
||||
@@ -145,6 +145,7 @@ pub const Parser = struct {
|
||||
return self.fail("expected identifier at top level");
|
||||
}
|
||||
const name = self.tokenSlice(self.current);
|
||||
const name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance();
|
||||
|
||||
// IDENT :: ...
|
||||
@@ -157,7 +158,7 @@ pub const Parser = struct {
|
||||
// IDENT : type = value; (typed variable)
|
||||
if (self.current.tag == .colon) {
|
||||
self.advance();
|
||||
return self.parseTypedBinding(name, start);
|
||||
return self.parseTypedBinding(name, name_span, start);
|
||||
}
|
||||
|
||||
// IDENT := value; (variable)
|
||||
@@ -165,7 +166,7 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
const value = try self.parseExpr();
|
||||
try self.expectSemicolonAfter(value);
|
||||
return try self.createNode(start, .{ .var_decl = .{ .name = name, .type_annotation = null, .value = value } });
|
||||
return try self.createNode(start, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = null, .value = value } });
|
||||
}
|
||||
|
||||
return self.fail("expected '::', ':=', or ':' after identifier");
|
||||
@@ -382,7 +383,7 @@ pub const Parser = struct {
|
||||
} });
|
||||
}
|
||||
|
||||
fn parseTypedBinding(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
||||
fn parseTypedBinding(self: *Parser, name: []const u8, name_span: ast.Span, start_pos: u32) anyerror!*Node {
|
||||
// After `name :`
|
||||
// Parse type
|
||||
const type_node = try self.parseTypeExpr();
|
||||
@@ -400,13 +401,13 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
const value = try self.parseExpr();
|
||||
try self.expectSemicolonAfter(value);
|
||||
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .type_annotation = type_node, .value = value } });
|
||||
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = type_node, .value = value } });
|
||||
}
|
||||
|
||||
if (self.current.tag == .semicolon) {
|
||||
// name : type; (default-initialized variable)
|
||||
self.advance();
|
||||
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .type_annotation = type_node, .value = null } });
|
||||
return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = type_node, .value = null } });
|
||||
}
|
||||
|
||||
if (self.current.tag == .hash_foreign) {
|
||||
@@ -426,6 +427,7 @@ pub const Parser = struct {
|
||||
try self.expect(.semicolon);
|
||||
return try self.createNode(start_pos, .{ .var_decl = .{
|
||||
.name = name,
|
||||
.name_span = name_span,
|
||||
.type_annotation = type_node,
|
||||
.value = null,
|
||||
.is_foreign = true,
|
||||
@@ -1167,6 +1169,7 @@ pub const Parser = struct {
|
||||
|
||||
var param_types = std.ArrayList(*Node).empty;
|
||||
var param_names = std.ArrayList([]const u8).empty;
|
||||
var param_name_spans = std.ArrayList(ast.Span).empty;
|
||||
|
||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||
if (param_types.items.len > 0) {
|
||||
@@ -1178,6 +1181,7 @@ pub const Parser = struct {
|
||||
return self.fail("expected parameter name in protocol method");
|
||||
}
|
||||
const pname = self.tokenSlice(self.current);
|
||||
try param_name_spans.append(self.allocator, .{ .start = self.current.loc.start, .end = self.current.loc.end });
|
||||
self.advance();
|
||||
try self.expect(.colon);
|
||||
const ptype = try self.parseTypeExpr();
|
||||
@@ -1205,6 +1209,7 @@ pub const Parser = struct {
|
||||
.name = method_name,
|
||||
.params = try param_types.toOwnedSlice(self.allocator),
|
||||
.param_names = try param_names.toOwnedSlice(self.allocator),
|
||||
.param_name_spans = try param_name_spans.toOwnedSlice(self.allocator),
|
||||
.return_type = return_type,
|
||||
.default_body = default_body,
|
||||
});
|
||||
@@ -1418,6 +1423,7 @@ pub const Parser = struct {
|
||||
.name = member_name,
|
||||
.params = &.{},
|
||||
.param_names = &.{},
|
||||
.param_name_spans = &.{},
|
||||
.return_type = ret_type,
|
||||
.is_static = true,
|
||||
.jni_descriptor_override = null,
|
||||
@@ -1431,6 +1437,7 @@ pub const Parser = struct {
|
||||
|
||||
var param_types = std.ArrayList(*Node).empty;
|
||||
var param_names = std.ArrayList([]const u8).empty;
|
||||
var param_name_spans = std.ArrayList(ast.Span).empty;
|
||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||
if (param_types.items.len > 0) {
|
||||
try self.expect(.comma);
|
||||
@@ -1440,6 +1447,7 @@ pub const Parser = struct {
|
||||
return self.fail("expected parameter name in '#jni_class' method");
|
||||
}
|
||||
const pname = self.tokenSlice(self.current);
|
||||
try param_name_spans.append(self.allocator, .{ .start = self.current.loc.start, .end = self.current.loc.end });
|
||||
self.advance();
|
||||
try self.expect(.colon);
|
||||
const ptype = try self.parseTypeExpr();
|
||||
@@ -1521,6 +1529,7 @@ pub const Parser = struct {
|
||||
.name = member_name,
|
||||
.params = try param_types.toOwnedSlice(self.allocator),
|
||||
.param_names = try param_names.toOwnedSlice(self.allocator),
|
||||
.param_name_spans = try param_name_spans.toOwnedSlice(self.allocator),
|
||||
.return_type = return_type,
|
||||
.is_static = is_static,
|
||||
.jni_descriptor_override = desc_override,
|
||||
@@ -1999,6 +2008,7 @@ pub const Parser = struct {
|
||||
const saved_prev_end = self.prev_end;
|
||||
const start = self.current.loc.start;
|
||||
const name = self.tokenSlice(self.current);
|
||||
const name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance();
|
||||
|
||||
if (self.current.tag == .colon_colon) {
|
||||
@@ -2009,11 +2019,11 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
const value = try self.parseExpr();
|
||||
try self.expectSemicolonAfter(value);
|
||||
return try self.createNode(start, .{ .var_decl = .{ .name = name, .type_annotation = null, .value = value } });
|
||||
return try self.createNode(start, .{ .var_decl = .{ .name = name, .name_span = name_span, .type_annotation = null, .value = value } });
|
||||
}
|
||||
if (self.current.tag == .colon) {
|
||||
self.advance();
|
||||
return self.parseTypedBinding(name, start);
|
||||
return self.parseTypedBinding(name, name_span, start);
|
||||
}
|
||||
|
||||
// Multi-target assignment: ident, expr, ... = expr, expr, ...;
|
||||
@@ -2094,8 +2104,10 @@ pub const Parser = struct {
|
||||
const start = self.current.loc.start;
|
||||
self.advance();
|
||||
var binding: ?[]const u8 = null;
|
||||
var binding_span: ?ast.Span = null;
|
||||
if (self.current.tag == .identifier and self.peekNext() == .l_brace) {
|
||||
binding = self.tokenSlice(self.current);
|
||||
binding_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance();
|
||||
}
|
||||
const saved_onfail = self.in_onfail_body;
|
||||
@@ -2108,7 +2120,7 @@ pub const Parser = struct {
|
||||
try self.expect(.semicolon);
|
||||
break :blk e;
|
||||
};
|
||||
return try self.createNode(start, .{ .onfail_stmt = .{ .binding = binding, .body = body } });
|
||||
return try self.createNode(start, .{ .onfail_stmt = .{ .binding = binding, .binding_span = binding_span, .body = body } });
|
||||
}
|
||||
|
||||
// Break statement: break;
|
||||
@@ -2539,8 +2551,10 @@ pub const Parser = struct {
|
||||
// catch e EXPR — binding + bare-expression body
|
||||
self.advance(); // consume 'catch'
|
||||
var binding: ?[]const u8 = null;
|
||||
var binding_span: ?ast.Span = null;
|
||||
if (self.current.tag == .identifier) {
|
||||
binding = self.tokenSlice(self.current);
|
||||
binding_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance();
|
||||
}
|
||||
var is_match_body = false;
|
||||
@@ -2559,6 +2573,7 @@ pub const Parser = struct {
|
||||
expr = try self.createNode(expr.span.start, .{ .catch_expr = .{
|
||||
.operand = expr,
|
||||
.binding = binding,
|
||||
.binding_span = binding_span,
|
||||
.body = body,
|
||||
.is_match_body = is_match_body,
|
||||
} });
|
||||
@@ -2906,6 +2921,7 @@ pub const Parser = struct {
|
||||
// Detect: identifier followed by :=
|
||||
if (self.current.tag == .identifier and self.peekNext() == .colon_equal) {
|
||||
const binding_name = self.tokenSlice(self.current);
|
||||
const binding_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance(); // skip identifier
|
||||
self.advance(); // skip :=
|
||||
const source_expr = try self.parseExpr();
|
||||
@@ -2925,6 +2941,7 @@ pub const Parser = struct {
|
||||
.else_branch = else_branch,
|
||||
.is_inline = false,
|
||||
.binding_name = binding_name,
|
||||
.binding_span = binding_span,
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -3026,6 +3043,7 @@ pub const Parser = struct {
|
||||
// Optional binding: while val := expr { ... }
|
||||
if (self.current.tag == .identifier and self.peekNext() == .colon_equal) {
|
||||
const binding_name = self.tokenSlice(self.current);
|
||||
const binding_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance(); // skip identifier
|
||||
self.advance(); // skip :=
|
||||
const source_expr = try self.parseExpr();
|
||||
@@ -3034,6 +3052,7 @@ pub const Parser = struct {
|
||||
.condition = source_expr,
|
||||
.body = body,
|
||||
.binding_name = binding_name,
|
||||
.binding_span = binding_span,
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -3087,7 +3106,9 @@ pub const Parser = struct {
|
||||
}
|
||||
|
||||
var capture_name: []const u8 = "";
|
||||
var capture_span: ?ast.Span = null;
|
||||
var index_name: ?[]const u8 = null;
|
||||
var index_span: ?ast.Span = null;
|
||||
var capture_by_ref = false;
|
||||
|
||||
if (range_end != null) {
|
||||
@@ -3099,6 +3120,7 @@ pub const Parser = struct {
|
||||
try self.expect(.l_paren);
|
||||
if (self.current.tag != .identifier) return self.fail("expected cursor variable name");
|
||||
capture_name = self.tokenSlice(self.current);
|
||||
capture_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance();
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
@@ -3113,11 +3135,13 @@ pub const Parser = struct {
|
||||
}
|
||||
if (self.current.tag != .identifier) return self.fail("expected capture variable name");
|
||||
capture_name = self.tokenSlice(self.current);
|
||||
capture_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance();
|
||||
if (self.current.tag == .comma) {
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) return self.fail("expected index variable name");
|
||||
index_name = self.tokenSlice(self.current);
|
||||
index_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance();
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
@@ -3129,7 +3153,9 @@ pub const Parser = struct {
|
||||
.iterable = iterable,
|
||||
.body = body,
|
||||
.capture_name = capture_name,
|
||||
.capture_span = capture_span,
|
||||
.index_name = index_name,
|
||||
.index_span = index_span,
|
||||
.range_end = range_end,
|
||||
.capture_by_ref = capture_by_ref,
|
||||
} });
|
||||
@@ -3154,9 +3180,11 @@ pub const Parser = struct {
|
||||
// a capture is exactly `( <identifier> )`; anything else is the
|
||||
// arm body (an expression) and is left for the body parse below.
|
||||
var capture: ?[]const u8 = null;
|
||||
var capture_span: ?ast.Span = null;
|
||||
if (self.current.tag == .l_paren and self.isLoneIdentParen()) {
|
||||
self.advance(); // '('
|
||||
capture = self.tokenSlice(self.current);
|
||||
capture_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
self.advance(); // ident
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
@@ -3165,7 +3193,7 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
try self.expect(.semicolon);
|
||||
const body = try self.createNode(arm_start, .{ .block = .{ .stmts = &.{} } });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = true, .capture = capture });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = true, .capture = capture, .capture_span = capture_span });
|
||||
} else if (self.current.tag == .fat_arrow) {
|
||||
// Short form: (ident) => expr;
|
||||
self.advance();
|
||||
@@ -3175,7 +3203,7 @@ pub const Parser = struct {
|
||||
// `;` is an arm terminator, not a value-discard — match arms are
|
||||
// exempt from the block trailing-`;` rule).
|
||||
const body = try self.createNode(arm_start, .{ .block = .{ .stmts = try self.allocator.dupe(*Node, &.{expr}), .produces_value = true } });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture, .capture_span = capture_span });
|
||||
} else {
|
||||
const stmts_start = self.current.loc.start;
|
||||
var stmts = std.ArrayList(*Node).empty;
|
||||
@@ -3186,7 +3214,7 @@ pub const Parser = struct {
|
||||
// yields its last statement's value — which, for a braced-block
|
||||
// arm body, still respects that inner block's own flag.
|
||||
const body = try self.createNode(stmts_start, .{ .block = .{ .stmts = try stmts.toOwnedSlice(self.allocator), .produces_value = true } });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture, .capture_span = capture_span });
|
||||
}
|
||||
}
|
||||
// Optional else arm (default)
|
||||
@@ -3539,16 +3567,19 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
// All targets must be plain identifiers
|
||||
var names = std.ArrayList([]const u8).empty;
|
||||
var name_spans = std.ArrayList(ast.Span).empty;
|
||||
for (targets.items) |target| {
|
||||
if (target.data != .identifier) {
|
||||
return self.fail("destructuring targets must be identifiers");
|
||||
}
|
||||
try names.append(self.allocator, target.data.identifier.name);
|
||||
try name_spans.append(self.allocator, target.span);
|
||||
}
|
||||
const value = try self.parseExpr();
|
||||
try self.expectSemicolonAfter(value);
|
||||
return try self.createNode(start, .{ .destructure_decl = .{
|
||||
.names = try names.toOwnedSlice(self.allocator),
|
||||
.name_spans = try name_spans.toOwnedSlice(self.allocator),
|
||||
.value = value,
|
||||
} });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user