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.
108 lines
5.4 KiB
Markdown
108 lines
5.4 KiB
Markdown
# 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:
|
|
>
|
|
> 1. **Backtick raw identifier.** The lexer recognises a leading backtick
|
|
> (`` `s2 ``) and emits an `.identifier` token whose span excludes the backtick,
|
|
> carrying a `Token.is_raw` flag ([src/lexer.zig], [src/token.zig]). A raw
|
|
> identifier is NEVER type-classified — the parser skips `Type.fromName` for it
|
|
> in expression position ([src/parser.zig] `parsePrimary`), so it is always a
|
|
> value identifier. The `is_raw` flag threads through `ast.Identifier` and EVERY
|
|
> binding/capture form ([src/ast.zig]): `VarDecl` / `Param` plus `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. `UnknownTypeChecker`
|
|
> skips 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).
|
|
> 2. **`#import c` foreign-name exemption.** `c_import.zig` synthesizes foreign
|
|
> `#foreign` decls with `Param.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]
|
|
> `parseTypeExpr` atom). A reserved-spelled FUNCTION (backtick-declared or
|
|
> `#import c` foreign) is bare-callable: `lowerCall` rewrites a `.type_expr` callee
|
|
> to an identifier when a function of that name is in scope ([src/ir/lower.zig]),
|
|
> so `s2(4)` resolves to the function (`TypeName(val)` is not a cast). A later BARE
|
|
> reference in value position resolves to the binding; a bare `s2` 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`/`onfail` tag 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 in `src/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:
|
|
|
|
1. **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.
|
|
2. **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:
|
|
|
|
```sx
|
|
`s2 := 2.5; // OK — identifier "s2", distinct from the s2 signed-int type
|
|
s2 := 2.5; // ERROR — bare s2 is still the reserved type name
|
|
```
|
|
|
|
Prefix form (single leading backtick on the identifier). The raw identifier's
|
|
TEXT is `s2` (the backtick is not part of the name). A bare `s2` used as a TYPE
|
|
remains the signed-int type.
|
|
|
|
## Reproduction
|
|
|
|
sx-side (minimal):
|
|
|
|
```sx
|
|
#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.
|