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.
5.4 KiB
0089 — backtick raw-identifier escape + #import c foreign-name exemption from the reserved-type-name rule
✅ RESOLVED (foundation step F0.6). Two mechanisms, per Agra's design ruling:
- Backtick raw identifier. The lexer recognises a leading backtick (
`s2) and emits an.identifiertoken whose span excludes the backtick, carrying aToken.is_rawflag ([src/lexer.zig], [src/token.zig]). A raw identifier is NEVER type-classified — the parser skipsType.fromNamefor it in expression position ([src/parser.zig]parsePrimary), so it is always a value identifier. Theis_rawflag threads throughast.Identifierand EVERY binding/capture form ([src/ast.zig]):VarDecl/ParamplusIfExpr/WhileExproptional bindings,ForExprcapture + index,MatchArmcapture,CatchExpr/OnFailStmttag bindings,DestructureDeclper-name, and the protocol-default-body / foreign-class method param lists.UnknownTypeCheckerskips the reserved-name check at each of those arms when raw ([src/ir/semantic_diagnostics.zig]). The backtick works in every identifier position (local, global, param, field, function name, struct member, later reference, and all the control-flow/capture/binding forms).#import cforeign-name exemption.c_import.zigsynthesizes foreign#foreigndecls withParam.is_raw = true, so generated C param names that collide with reserved type names (s1,s2) import unedited.Boundary rules. A raw identifier is a value name and is NEVER a type: using one in type position (
x :s2 = 1) is a clean parse error ([src/parser.zig]parseTypeExpratom). A reserved-spelled FUNCTION (backtick-declared or#import cforeign) is bare-callable:lowerCallrewrites a.type_exprcallee to an identifier when a function of that name is in scope ([src/ir/lower.zig]), sos2(4)resolves to the function (TypeName(val)is not a cast). A later BARE reference in value position resolves to the binding; a bares2` in type position is still the type.A bare reserved-name binding in sx still errors (issue 0076 preserved): the
is_raw-gated skip only fires for backtick / foreign names. Regression tests:examples/0151-types-backtick-raw-identifier.sx(backtick, decl positions),examples/0152-types-backtick-control-flow.sx(every control-flow/capture form
- bare ref/call/member access),
examples/1054-errors-backtick-reserved-binding.sx(catch/onfailtag bindings),examples/1220-ffi-c-import-reserved-name-params.{sx,h,c}(foreign param + function-name exemption, bare-callable foreign fn),examples/1139-diagnostics-backtick-raw-not-a-type.sx(negative — raw in type position),examples/1119/1121/1123(negative — bare reserved binding still rejected across all forms). Backtick lexer unit tests insrc/lexer.zig.The original report is preserved below.
Symptom
Importing non-sx source whose names collide with sx reserved type names is
rejected. library/modules/stb_truetype.sx is a #import c { ... } block over a
vendored C header (vendors/stb_truetype/stb_truetype.h); C identifiers s1,
s2 (which collide with sx's signed-int type keywords s1..sN) produce:
error: 's1' is a reserved type name and cannot be used as an identifier
error: 's2' is a reserved type name and cannot be used as an identifier
The user cannot hand-edit these — they are generated from the vendored C header. Separately, sx-authored code has NO way to deliberately use a reserved-name-spelled identifier even when it wants to.
Root cause
The parser classifies any reserved-type-name spelling (s2, u8, f64, …) as a
.type_expr via name_class.Type.fromName, never as an .identifier. The F0.1 /
issue-0076 fix added UnknownTypeChecker.checkBindingName
(src/ir/semantic_diagnostics.zig) to reject a value binding / param spelled as
a reserved type name (the .type_expr-vs-.identifier mismatch otherwise breaks
address-of / autoref lowering). F0.1 deliberately extended this check to imported
declarations — which is what now fires on the C-imported s1/s2.
Desired behaviour (Agra ruling)
External / imported source does NOT need to conform to sx naming standards. Two mechanisms:
-
Auto-exempt imports.
#import c(and other foreign) declarations are treated as RAW identifiers: foreign names are never type-classified and never reserved-checked, so generated bindings "just work" with zero user edits. -
Backtick raw-identifier for sx code. A leading backtick makes the following identifier raw — an identifier that is NEVER type-classified, so it bypasses the reserved-name rule:
`s2 := 2.5; // OK — identifier "s2", distinct from the s2 signed-int type s2 := 2.5; // ERROR — bare s2 is still the reserved type namePrefix form (single leading backtick on the identifier). The raw identifier's TEXT is
s2(the backtick is not part of the name). A bares2used as a TYPE remains the signed-int type.
Reproduction
sx-side (minimal):
#import "modules/std.sx";
main :: () {
`s2 := 2.5; // must compile: identifier s2 = 2.5
print("{}\n", `s2); // 2.5
}
Import-side: a #import c block over a header declaring int s1, s2; (or
stb_truetype.sx) must NOT emit the reserved-type-name error.