feat(lang): backtick raw identifier in every binding form + raw-not-a-type + foreign reserved-name fn bare-call [F0.6]

Completes the issue-0089 backtick raw-identifier / `#import c` exemption
across all remaining identifier positions and closes three boundary gaps
the F0.6 review found.

1. Exhaustive raw-binding coverage. The `is_raw` bit now threads through
   `ast.Identifier` and EVERY binding/capture form — `IfExpr`/`WhileExpr`
   optional bindings, `ForExpr` capture + index, `MatchArm` capture,
   `CatchExpr`/`OnFailStmt` tag bindings, `DestructureDecl` per-name, and
   the protocol-default-body / foreign-class method param lists — not just
   `var_decl`/`param`. `UnknownTypeChecker` skips the reserved-name check at
   each arm when raw, so a backtick works in every identifier position while
   a bare reserved spelling still errors (issue 0076 preserved).

2. Raw identifier is never a type. `parseTypeExpr`'s atom rejects a raw
   identifier in type position (`x : `s2 = 1`, `List(`s2)`) with an accurate
   diagnostic instead of silently type-classifying it.

3. Reserved-name function bare-callable. A bare `s2(4)` parses its callee as
   a `.type_expr` (reserved spelling); `lowerCall` now rewrites a type_expr
   callee to an identifier when a function of that name is in scope, so a
   backtick-declared sx fn and a `#import c` foreign fn whose C name collides
   with a reserved type spelling both resolve by their bare name.
   (`TypeName(val)` is not a cast, so there is no ambiguity.)

Tests: examples/0152 (every control-flow/capture form + bare ref/call/member
access), examples/1054 (catch/onfail tag bindings), examples/1139 (raw in
type position rejected), examples/1220 extended (foreign reserved-name
function bare-call). 0076 negatives 1119/1121/1122/1123/1124/1125 stay green.
Gate: zig build + zig build test + 422 examples pass. specs.md + readme.md
updated; issues/0089 RESOLVED banner refreshed.
This commit is contained in:
agra
2026-06-04 18:31:08 +03:00
parent 0dbdc530ba
commit 640f59dc54
23 changed files with 356 additions and 56 deletions

View File

@@ -629,6 +629,12 @@ pub const Parser = struct {
}
if (self.current.tag.isTypeKeyword() or self.isIdentLike()) {
// A backtick raw identifier (`` `s2 ``) is a VALUE-name escape; it is
// never a type. Reject it in type position rather than silently
// type-classifying it (issue 0089).
if (self.current.is_raw) {
return self.failFmt("`{s}` is a raw identifier, not a type — the backtick escape names a value, never a type", .{self.tokenSlice(self.current)});
}
var name = self.tokenSlice(self.current);
self.advance();
@@ -1186,6 +1192,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;
var param_name_is_raw = std.ArrayList(bool).empty;
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (param_types.items.len > 0) {
@@ -1198,6 +1205,7 @@ pub const Parser = struct {
}
const pname = self.tokenSlice(self.current);
try param_name_spans.append(self.allocator, .{ .start = self.current.loc.start, .end = self.current.loc.end });
try param_name_is_raw.append(self.allocator, self.current.is_raw);
self.advance();
try self.expect(.colon);
const ptype = try self.parseTypeExpr();
@@ -1226,6 +1234,7 @@ pub const Parser = struct {
.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),
.param_name_is_raw = try param_name_is_raw.toOwnedSlice(self.allocator),
.return_type = return_type,
.default_body = default_body,
});
@@ -1454,6 +1463,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;
var param_name_is_raw = std.ArrayList(bool).empty;
while (self.current.tag != .r_paren and self.current.tag != .eof) {
if (param_types.items.len > 0) {
try self.expect(.comma);
@@ -1464,6 +1474,7 @@ pub const Parser = struct {
}
const pname = self.tokenSlice(self.current);
try param_name_spans.append(self.allocator, .{ .start = self.current.loc.start, .end = self.current.loc.end });
try param_name_is_raw.append(self.allocator, self.current.is_raw);
self.advance();
try self.expect(.colon);
const ptype = try self.parseTypeExpr();
@@ -1546,6 +1557,7 @@ pub const Parser = struct {
.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),
.param_name_is_raw = try param_name_is_raw.toOwnedSlice(self.allocator),
.return_type = return_type,
.is_static = is_static,
.jni_descriptor_override = desc_override,
@@ -2046,7 +2058,7 @@ pub const Parser = struct {
// Multi-target assignment: ident, expr, ... = expr, expr, ...;
if (self.current.tag == .comma) {
const first_target = try self.createNode(start, .{ .identifier = .{ .name = name } });
const first_target = try self.createNode(start, .{ .identifier = .{ .name = name, .is_raw = name_is_raw } });
return try self.parseMultiAssign(first_target, start);
}
@@ -2056,7 +2068,7 @@ pub const Parser = struct {
self.advance();
const value = try self.parseExpr();
try self.expect(.semicolon);
const target = try self.createNode(start, .{ .identifier = .{ .name = name } });
const target = try self.createNode(start, .{ .identifier = .{ .name = name, .is_raw = name_is_raw } });
return try self.createNode(start, .{ .assignment = .{ .target = target, .op = op, .value = value } });
}
@@ -2123,9 +2135,11 @@ pub const Parser = struct {
self.advance();
var binding: ?[]const u8 = null;
var binding_span: ?ast.Span = null;
var binding_is_raw = false;
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 };
binding_is_raw = self.current.is_raw;
self.advance();
}
const saved_onfail = self.in_onfail_body;
@@ -2138,7 +2152,7 @@ pub const Parser = struct {
try self.expect(.semicolon);
break :blk e;
};
return try self.createNode(start, .{ .onfail_stmt = .{ .binding = binding, .binding_span = binding_span, .body = body } });
return try self.createNode(start, .{ .onfail_stmt = .{ .binding = binding, .binding_span = binding_span, .binding_is_raw = binding_is_raw, .body = body } });
}
// Break statement: break;
@@ -2570,9 +2584,11 @@ pub const Parser = struct {
self.advance(); // consume 'catch'
var binding: ?[]const u8 = null;
var binding_span: ?ast.Span = null;
var binding_is_raw = false;
if (self.current.tag == .identifier) {
binding = self.tokenSlice(self.current);
binding_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
binding_is_raw = self.current.is_raw;
self.advance();
}
var is_match_body = false;
@@ -2582,7 +2598,7 @@ pub const Parser = struct {
const m_start = self.current.loc.start;
self.advance(); // consume '=='
is_match_body = true;
const subject = try self.createNode(m_start, .{ .identifier = .{ .name = binding.? } });
const subject = try self.createNode(m_start, .{ .identifier = .{ .name = binding.?, .is_raw = binding_is_raw } });
break :blk try self.parseMatchBody(subject, m_start);
} else if (binding != null)
try self.parseExpr()
@@ -2592,6 +2608,7 @@ pub const Parser = struct {
.operand = expr,
.binding = binding,
.binding_span = binding_span,
.binding_is_raw = binding_is_raw,
.body = body,
.is_match_body = is_match_body,
} });
@@ -2690,16 +2707,17 @@ pub const Parser = struct {
},
.identifier => {
const name = self.tokenSlice(self.current);
const is_raw = self.current.is_raw;
// A backtick raw identifier (`` `s2 ``) is NEVER type-classified —
// it is always a value identifier, bypassing the reserved-type-name
// rule (issue 0089). Only a bare spelling is checked for a type name
// (e.g. s32, u8, s128).
if (!self.current.is_raw and Type.fromName(name) != null) {
if (!is_raw and Type.fromName(name) != null) {
self.advance();
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
}
self.advance();
return try self.createNode(start, .{ .identifier = .{ .name = name } });
return try self.createNode(start, .{ .identifier = .{ .name = name, .is_raw = is_raw } });
},
.kw_closure, .kw_protocol, .kw_impl, .kw_ufcs => {
// Contextual keywords used as identifiers in expressions
@@ -2943,6 +2961,7 @@ pub const Parser = struct {
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 };
const binding_is_raw = self.current.is_raw;
self.advance(); // skip identifier
self.advance(); // skip :=
const source_expr = try self.parseExpr();
@@ -2963,6 +2982,7 @@ pub const Parser = struct {
.is_inline = false,
.binding_name = binding_name,
.binding_span = binding_span,
.binding_is_raw = binding_is_raw,
} });
}
@@ -3065,6 +3085,7 @@ pub const Parser = struct {
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 };
const binding_is_raw = self.current.is_raw;
self.advance(); // skip identifier
self.advance(); // skip :=
const source_expr = try self.parseExpr();
@@ -3074,6 +3095,7 @@ pub const Parser = struct {
.body = body,
.binding_name = binding_name,
.binding_span = binding_span,
.binding_is_raw = binding_is_raw,
} });
}
@@ -3128,8 +3150,10 @@ pub const Parser = struct {
var capture_name: []const u8 = "";
var capture_span: ?ast.Span = null;
var capture_is_raw = false;
var index_name: ?[]const u8 = null;
var index_span: ?ast.Span = null;
var index_is_raw = false;
var capture_by_ref = false;
if (range_end != null) {
@@ -3142,6 +3166,7 @@ pub const Parser = struct {
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 };
capture_is_raw = self.current.is_raw;
self.advance();
try self.expect(.r_paren);
}
@@ -3157,12 +3182,14 @@ 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 };
capture_is_raw = self.current.is_raw;
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 };
index_is_raw = self.current.is_raw;
self.advance();
}
try self.expect(.r_paren);
@@ -3175,8 +3202,10 @@ pub const Parser = struct {
.body = body,
.capture_name = capture_name,
.capture_span = capture_span,
.capture_is_raw = capture_is_raw,
.index_name = index_name,
.index_span = index_span,
.index_is_raw = index_is_raw,
.range_end = range_end,
.capture_by_ref = capture_by_ref,
} });
@@ -3202,10 +3231,12 @@ pub const Parser = struct {
// arm body (an expression) and is left for the body parse below.
var capture: ?[]const u8 = null;
var capture_span: ?ast.Span = null;
var capture_is_raw = false;
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 };
capture_is_raw = self.current.is_raw;
self.advance(); // ident
try self.expect(.r_paren);
}
@@ -3214,7 +3245,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, .capture_span = capture_span });
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = true, .capture = capture, .capture_span = capture_span, .capture_is_raw = capture_is_raw });
} else if (self.current.tag == .fat_arrow) {
// Short form: (ident) => expr;
self.advance();
@@ -3224,7 +3255,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, .capture_span = capture_span });
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture, .capture_span = capture_span, .capture_is_raw = capture_is_raw });
} else {
const stmts_start = self.current.loc.start;
var stmts = std.ArrayList(*Node).empty;
@@ -3235,7 +3266,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, .capture_span = capture_span });
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture, .capture_span = capture_span, .capture_is_raw = capture_is_raw });
}
}
// Optional else arm (default)
@@ -3597,18 +3628,21 @@ pub const Parser = struct {
// All targets must be plain identifiers
var names = std.ArrayList([]const u8).empty;
var name_spans = std.ArrayList(ast.Span).empty;
var name_is_raw = std.ArrayList(bool).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);
try name_is_raw.append(self.allocator, target.data.identifier.is_raw);
}
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),
.name_is_raw = try name_is_raw.toOwnedSlice(self.allocator),
.value = value,
} });
}