feat(lang): backtick raw-identifier escape + #import c foreign-name exemption [F0.6]
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.
This commit is contained in:
91
issues/0089-backtick-raw-identifier.md
Normal file
91
issues/0089-backtick-raw-identifier.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 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 new `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 flag threads to `VarDecl.is_raw` / `Param.is_raw`
|
||||
> ([src/ast.zig]) at the binding sites, and `UnknownTypeChecker` skips 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.
|
||||
> 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.
|
||||
>
|
||||
> 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 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.
|
||||
Reference in New Issue
Block a user