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:
agra
2026-06-03 22:06:56 +03:00
parent fcc76b9391
commit 6433eb6155
15 changed files with 144 additions and 51 deletions

View File

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