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

@@ -6621,10 +6621,30 @@ pub const Lowering = struct {
// ── Calls ───────────────────────────────────────────────────────
fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
var c = c_in;
// A bare reserved-type-name spelling in call position parses as a
// `.type_expr` (e.g. `s2(4)`), but if a function of that name is in
// scope — a backtick-declared sx fn or a `#import c` foreign fn whose C
// name collides with a reserved type spelling — it is a CALL to that
// function. `TypeName(val)` is not a cast (casts are `cast(T, val)`), so
// there is no ambiguity. Rewrite the callee to an identifier so the
// normal call machinery resolves it, symmetric to the bare-value
// reference that already resolves via scope/globals (issue 0089).
if (c.callee.data == .type_expr) {
const tname = c.callee.data.type_expr.name;
const is_fn = self.program_index.fn_ast_map.contains(tname) or
(if (self.scope) |scope| scope.lookupFn(tname) != null else false);
if (is_fn) {
const id_node = self.alloc.create(Node) catch unreachable;
id_node.* = .{ .span = c.callee.span, .data = .{ .identifier = .{ .name = tname, .is_raw = true } } };
const rewritten = self.alloc.create(ast.Call) catch unreachable;
rewritten.* = .{ .callee = id_node, .args = c.args };
c = rewritten;
}
}
// Expand default parameter values for bare identifier callees:
// when the caller omits trailing positional args, fill them in
// from the callee's `param: T = expr` declarations.
var c = c_in;
if (self.expandCallDefaults(c)) |expanded| c = expanded;
// Check reflection builtins first (before lowering args — some args are type names, not values)
if (c.callee.data == .identifier) {

View File

@@ -121,7 +121,9 @@ pub const UnknownTypeChecker = struct {
if (vd.value) |v| self.checkBindingNames(v);
},
.destructure_decl => |dd| {
for (dd.names, dd.name_spans) |n, sp| self.checkBindingName(n, sp);
for (dd.names, dd.name_spans, dd.name_is_raw) |n, sp, raw| {
if (!raw) self.checkBindingName(n, sp);
}
self.checkBindingNames(dd.value);
},
.fn_decl => |fd| {
@@ -137,19 +139,25 @@ pub const UnknownTypeChecker = struct {
if (p.default_expr) |de| self.checkBindingNames(de);
},
.if_expr => |ie| {
if (ie.binding_name) |bn| self.checkBindingName(bn, ie.binding_span);
if (ie.binding_name) |bn| {
if (!ie.binding_is_raw) 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, we.binding_span);
if (we.binding_name) |bn| {
if (!we.binding_is_raw) 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, fe.capture_span);
if (fe.index_name) |idx| self.checkBindingName(idx, fe.index_span);
if (fe.capture_name.len != 0 and !fe.capture_is_raw) self.checkBindingName(fe.capture_name, fe.capture_span);
if (fe.index_name) |idx| {
if (!fe.index_is_raw) self.checkBindingName(idx, fe.index_span);
}
self.checkBindingNames(fe.iterable);
if (fe.range_end) |re| self.checkBindingNames(re);
self.checkBindingNames(fe.body);
@@ -157,23 +165,31 @@ pub const UnknownTypeChecker = struct {
.match_expr => |me| {
self.checkBindingNames(me.subject);
for (me.arms) |arm| {
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span);
if (arm.capture) |cap| {
if (!arm.capture_is_raw) 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, arm.capture_span);
if (arm.capture) |cap| {
if (!arm.capture_is_raw) 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, ce.binding_span);
if (ce.binding) |b| {
if (!ce.binding_is_raw) self.checkBindingName(b, ce.binding_span);
}
self.checkBindingNames(ce.operand);
self.checkBindingNames(ce.body);
},
.onfail_stmt => |os| {
if (os.binding) |b| self.checkBindingName(b, os.binding_span);
if (os.binding) |b| {
if (!os.binding_is_raw) self.checkBindingName(b, os.binding_span);
}
self.checkBindingNames(os.body);
},
// impl / protocol-default / foreign-class method bodies: each
@@ -183,13 +199,19 @@ 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, m.param_name_spans) |pn, sp| self.checkBindingName(pn, sp);
for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| {
if (i < m.param_name_is_raw.len and m.param_name_is_raw[i]) continue;
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, m.param_name_spans) |pn, sp| self.checkBindingName(pn, sp);
for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| {
if (i < m.param_name_is_raw.len and m.param_name_is_raw[i]) continue;
self.checkBindingName(pn, sp);
}
self.checkBindingNames(body);
},
.field, .extends, .implements => {},