Reserved type-name spellings (s1, s2, u8, …) can now be used as value identifiers two ways, resolving issue 0089: 1. Backtick raw identifier: a leading backtick (`s2) lexes to an .identifier token carrying a new Token.is_raw flag, with the backtick excluded from the text. A raw identifier is never type-classified — the parser skips Type.fromName for it — so it is always a value identifier. The flag threads to VarDecl.is_raw / Param.is_raw at binding sites, and the reserved-type-name check (UnknownTypeChecker) skips raw bindings. Because the token tag stays .identifier, the escape works in every position (local, global, param, field, fn name, struct member, later reference) with no per-site parser change. 2. #import c exemption: c_import.zig synthesizes foreign decls with Param.is_raw = true, so generated C param names that collide with reserved type names (s1, s2) import unedited. A bare reserved-name binding in sx still errors (issue 0076 preserved): the is_raw-gated skip only fires for backtick / foreign names, and a raw binding's address-of / autoref lowering stays correct because every occurrence is an .identifier, never a .type_expr. Tests: examples/0151 (backtick, every position), examples/1220 (foreign exemption, compiled+run), lexer unit tests. 1119 (bare-binding rejection) stays green. specs.md + readme.md updated.
4.2 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 a newToken.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. The flag threads toVarDecl.is_raw/Param.is_raw([src/ast.zig]) at the binding sites, andUnknownTypeCheckerskips the reserved-name check for raw bindings ([src/ir/semantic_diagnostics.zig]). Because the token tag stays.identifier, the escape works in every position (local, global, param, field, function name, struct member, later reference) with no per-site parser change.#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.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, every position),examples/1220-ffi-c-import-reserved-name-params.{sx,h,c}(foreign exemption),examples/1119-diagnostics-reserved-type-name-as-identifier.sx(negative — bare binding still rejected). 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.