feat(lang): universal backtick raw identifier — valid in value, decl, AND type position [F0.6]
AGRA ruling (attempt 4): `` `name `` is THE LITERAL identifier `name`, usable in EVERY position — the backtick only means "treat this token as a plain identifier, never the reserved keyword/type", and is never part of the name's text. - Raw in TYPE position is now VALID (reverses attempt-2 "raw is not a type"): `parseTypeExpr` emits a raw `type_expr`; `TypeResolver.resolveNamed` gains a `skip_builtin` flag (threaded from `te.is_raw` via lower.zig + type_bridge) so a `` `s2 `` reference resolves to a `` `s2 ``-declared type (struct/enum/union/alias), else a normal "unknown type 's2'" error (reportIfUnknownType skips the builtin exemption when raw). Bare `s2` in type position stays the builtin int. - Every declaration-name site is is_raw-exemptible: `is_raw` added to TypeExpr + StructDecl/EnumDecl/UnionDecl/ErrorSetDecl/ProtocolDecl/ForeignClassDecl/UfcsAlias/ NamespaceDecl/ImportDecl/CImportDecl/LibraryDecl; parser threads name_is_raw to every decl parse fn; namespace imports carry it through imports.addNamespace. Typed-const path (`` `s2 : s64 : 5 ``) now threads name_span+is_raw (fixes the 1:1-caret bug). - Check<->exemption made structurally symmetric: checkBindingName/checkDeclName take is_raw as a REQUIRED argument and skip inside the check, so no call site can validate a name without honoring the exemption (the desync cause of prior rounds). - Bare reserved-name declarations of every kind still error (0076 preserved); `#import c` foreign names stay auto-raw + bare-callable. specs.md + readme.md updated to the universal model. issue 0089 RESOLVED banner rewritten. Examples: replace 1139 (raw-not-a-type) with 0154 (raw type reference); add 0155 (typed const + union tag) and 1141 (bare type-decl negatives). Gate: zig build + zig build test + run_examples (426 passed, 0 failed).
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
// Backtick raw-identifier escape: a leading backtick makes the following
|
// Backtick raw-identifier escape: a leading backtick makes the following
|
||||||
// identifier RAW — its text excludes the backtick and it is NEVER
|
// identifier RAW — its text excludes the backtick and it is never the
|
||||||
// type-classified, so a reserved type-name spelling (`s2`, `u8`, …) can be
|
// reserved/builtin keyword, so a reserved type-name spelling (`s2`, `u8`, …)
|
||||||
// used as a value identifier. Exercised in every position: global, local,
|
// can be used as an ordinary identifier. Exercised in every VALUE position:
|
||||||
// param, struct field + member access, function name + call, and a later
|
// global, local, param, struct field + member access, function name + call,
|
||||||
// reference. A *bare* `s2` is still the reserved type name (see
|
// and a later reference. (A raw identifier in TYPE position references a
|
||||||
// examples/1119), so the escape is the only way to spell these as values.
|
// backtick-declared type instead — see examples/0154.) A *bare* `s2` is still
|
||||||
|
// the reserved type name (see examples/1119), so the escape is the only way to
|
||||||
|
// spell these as values.
|
||||||
// Regression (issue 0089).
|
// Regression (issue 0089).
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
|||||||
42
examples/0154-types-backtick-raw-type-reference.sx
Normal file
42
examples/0154-types-backtick-raw-type-reference.sx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Backtick raw identifier in TYPE position (the universal model, issue 0089):
|
||||||
|
// `` `name `` is the LITERAL identifier `name` used as a type reference, never
|
||||||
|
// the builtin/reserved spelling. A reserved type spelling (`s2`, `u8`, …) can
|
||||||
|
// therefore both DECLARE a type (struct / enum / union / error-set / alias) and
|
||||||
|
// be REFERENCED as that type via the backtick — while a BARE `s2` in type
|
||||||
|
// position remains the signed-int type (see `add` below) and a bare reserved-
|
||||||
|
// name declaration still errors (see examples/1141). The backtick is required
|
||||||
|
// to declare or reference these names; it is never part of the name's text.
|
||||||
|
// Regression (issue 0089 — attempt-4 universal raw identifier).
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
// Type-introducing decls whose NAME is a reserved spelling.
|
||||||
|
`s2 :: struct { x: s64; }
|
||||||
|
`s8 :: enum { A; B; }
|
||||||
|
`u16 :: union { i: s32; f: f32; }
|
||||||
|
`u32 :: error { Bad, Empty }
|
||||||
|
RawAlias :: `s2; // alias to a backtick-declared struct
|
||||||
|
|
||||||
|
// A bare `s2` in type position is still the 2-bit signed int.
|
||||||
|
add :: (a: s2, b: s2) -> s2 { return a + b; }
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
// Reference the backtick struct as a type; field access works.
|
||||||
|
v : `s2 = ---;
|
||||||
|
v.x = 7;
|
||||||
|
|
||||||
|
// Reference via a normal alias too.
|
||||||
|
a : RawAlias = ---;
|
||||||
|
a.x = 11;
|
||||||
|
|
||||||
|
// Backtick enum / union type references.
|
||||||
|
e : `s8 = .A;
|
||||||
|
u : `u16 = ---;
|
||||||
|
u.i = 5;
|
||||||
|
|
||||||
|
print("struct = {}\n", v.x);
|
||||||
|
print("alias = {}\n", a.x);
|
||||||
|
print("enum = {}\n", e == .A);
|
||||||
|
print("union = {}\n", u.i);
|
||||||
|
print("bare = {}\n", add(1, 0)); // bare s2 = the 2-bit int type
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
24
examples/0155-types-backtick-typed-const-union-tag.sx
Normal file
24
examples/0155-types-backtick-typed-const-union-tag.sx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Backtick raw identifier at the two remaining binding positions (issue 0089,
|
||||||
|
// attempt-4): a TYPED constant (`` `s2 : s64 : 5 ``) and a union TAG / field
|
||||||
|
// (`` `s2: s32 ``). The typed-const form previously slipped past the decl check
|
||||||
|
// without a name span (caret at 1:1); a bare `s2 : s64 : 5` is still rejected
|
||||||
|
// with the caret ON the name (see examples/1141). A union tag spelled with a
|
||||||
|
// reserved name works and is accessible bare or backticked.
|
||||||
|
// Regression (issue 0089 — attempt-4 typed const + union tag).
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
// Typed constant whose name is a reserved type spelling.
|
||||||
|
`s2 : s64 : 5;
|
||||||
|
|
||||||
|
// Union whose tags are reserved type spellings.
|
||||||
|
Mix :: union { `s1: s32; `u8: f32; }
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
print("typed const = {}\n", `s2);
|
||||||
|
|
||||||
|
m : Mix = ---;
|
||||||
|
m.`s1 = 42;
|
||||||
|
print("union tick = {}\n", m.`s1); // backtick member access
|
||||||
|
print("union bare = {}\n", m.s1); // bare member access — same field
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// A backtick raw identifier is a VALUE-name escape; it is never a type. Using
|
|
||||||
// one in type position (`x : `s2 = 1`) is a clean parse error, not a silent
|
|
||||||
// type-classification — reserved type names are the lowercase `sN`/`uN`/`fNN`
|
|
||||||
// spellings, and a real type never needs a backtick. A *bare* `s2` in type
|
|
||||||
// position remains the reserved signed-int type.
|
|
||||||
// Regression (issue 0089 — attempt-2: raw identifier rejected in type position).
|
|
||||||
#import "modules/std.sx";
|
|
||||||
|
|
||||||
main :: () -> s32 {
|
|
||||||
x : `s2 = 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
22
examples/1141-diagnostics-reserved-name-type-decl.sx
Normal file
22
examples/1141-diagnostics-reserved-name-type-decl.sx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// A reserved/builtin type-name spelling is rejected as the NAME of EVERY
|
||||||
|
// type-introducing `::` declaration too — struct, enum, union, error-set, and
|
||||||
|
// a typed constant — not just `:=` / value-const / function names (those are
|
||||||
|
// examples/1140). Each is a declaration-name binding site: a bare reserved
|
||||||
|
// spelling there mis-classifies and is rejected, exactly like `s2 := …`. The
|
||||||
|
// backtick escape (`` `s2 :: struct{…} ``, examples/0154) is the only way to
|
||||||
|
// spell these names in handwritten sx; `#import c` foreign decls stay exempt
|
||||||
|
// (examples/1220).
|
||||||
|
//
|
||||||
|
// Regression (issue 0089 — attempt-4: 0076 holds across every decl kind).
|
||||||
|
// Expected: one error per declaration, each caret ON the declared name; exit 1.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
s8 :: struct { v: s64; }
|
||||||
|
s16 :: enum { A; B; }
|
||||||
|
u16 :: union { a: s32; b: f32; }
|
||||||
|
u32 :: error { Bad, Empty }
|
||||||
|
s2 : s64 : 5;
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
struct = 7
|
||||||
|
alias = 11
|
||||||
|
enum = true
|
||||||
|
union = 5
|
||||||
|
bare = 1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
typed const = 5
|
||||||
|
union tick = 42
|
||||||
|
union bare = 42
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
error: `s2` is a raw identifier, not a type — the backtick escape names a value, never a type
|
|
||||||
--> examples/1139-diagnostics-backtick-raw-not-a-type.sx:10:10
|
|
||||||
|
|
|
||||||
10 | x : `s2 = 1;
|
|
||||||
| ^^
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
error: 's8' is a reserved type name and cannot be used as an identifier
|
||||||
|
--> examples/1141-diagnostics-reserved-name-type-decl.sx:14:1
|
||||||
|
|
|
||||||
|
14 | s8 :: struct { v: s64; }
|
||||||
|
| ^^
|
||||||
|
|
||||||
|
error: 's16' is a reserved type name and cannot be used as an identifier
|
||||||
|
--> examples/1141-diagnostics-reserved-name-type-decl.sx:15:1
|
||||||
|
|
|
||||||
|
15 | s16 :: enum { A; B; }
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error: 'u16' is a reserved type name and cannot be used as an identifier
|
||||||
|
--> examples/1141-diagnostics-reserved-name-type-decl.sx:16:1
|
||||||
|
|
|
||||||
|
16 | u16 :: union { a: s32; b: f32; }
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error: 'u32' is a reserved type name and cannot be used as an identifier
|
||||||
|
--> examples/1141-diagnostics-reserved-name-type-decl.sx:17:1
|
||||||
|
|
|
||||||
|
17 | u32 :: error { Bad, Empty }
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error: 's2' is a reserved type name and cannot be used as an identifier
|
||||||
|
--> examples/1141-diagnostics-reserved-name-type-decl.sx:18:1
|
||||||
|
|
|
||||||
|
18 | s2 : s64 : 5;
|
||||||
|
| ^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,62 +1,68 @@
|
|||||||
# 0089 — backtick raw-identifier escape + `#import c` foreign-name exemption from the reserved-type-name rule
|
# 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:
|
> **✅ RESOLVED** (foundation step F0.6). Two mechanisms, per Agra's design
|
||||||
|
> ruling; the final shape is the **universal raw identifier** (attempt 4):
|
||||||
|
> `` `name `` is THE LITERAL identifier `name`, usable in EVERY position — value,
|
||||||
|
> declaration, AND type — meaning only "treat this token as a plain identifier,
|
||||||
|
> never the reserved keyword/type." The backtick is never part of the name's text.
|
||||||
>
|
>
|
||||||
> 1. **Backtick raw identifier.** The lexer recognises a leading backtick
|
> 1. **Backtick raw identifier.** The lexer recognises a leading backtick
|
||||||
> (`` `s2 ``) and emits an `.identifier` token whose span excludes the 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
|
> carrying a `Token.is_raw` flag ([src/lexer.zig], [src/token.zig]). The flag
|
||||||
> identifier is NEVER type-classified — the parser skips `Type.fromName` for it
|
> threads through `ast.Identifier`, `ast.TypeExpr`, and EVERY binding / capture /
|
||||||
> in expression position ([src/parser.zig] `parsePrimary`), so it is always a
|
> declaration node ([src/ast.zig]): `VarDecl` / `ConstDecl` / `Param` / `FnDecl`
|
||||||
> value identifier. The `is_raw` flag threads through `ast.Identifier` and EVERY
|
> plus `IfExpr` / `WhileExpr` optional bindings, `ForExpr` capture + index,
|
||||||
> binding/capture form ([src/ast.zig]): `VarDecl` / `Param` plus `IfExpr` /
|
> `MatchArm` capture, `CatchExpr` / `OnFailStmt` tag bindings, `DestructureDecl`
|
||||||
> `WhileExpr` optional bindings, `ForExpr` capture + index, `MatchArm` capture,
|
> per-name, protocol-default / foreign-class method params, AND every
|
||||||
> `CatchExpr` / `OnFailStmt` tag bindings, `DestructureDecl` per-name, and the
|
> type-introducing decl — `StructDecl` / `EnumDecl` / `UnionDecl` /
|
||||||
> protocol-default-body / foreign-class method param lists. `UnknownTypeChecker`
|
> `ErrorSetDecl` / `ProtocolDecl` / `ForeignClassDecl` / `UfcsAlias` /
|
||||||
> skips the reserved-name check at each of those arms when raw
|
> `NamespaceDecl` / `ImportDecl` / `CImportDecl` / `LibraryDecl`.
|
||||||
> ([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).
|
|
||||||
>
|
>
|
||||||
> The `::` DECLARATION forms are binding sites too and are equally covered
|
> - **Value position.** The parser skips `Type.fromName` for a raw identifier
|
||||||
> (F0.6 attempt-3): a bare reserved-name **constant** (`s2 :: 5`), **function**
|
> in expression position ([src/parser.zig] `parsePrimary`), so `` `s2 `` is a
|
||||||
> (`s2 :: (…) {…}`, incl. struct/impl methods), or **type** declaration
|
> value identifier; a later bare reference resolves to the binding.
|
||||||
> (`struct`/`enum`/`union`/`error`/alias/`protocol`/foreign-class/ufcs/namespace)
|
> - **Type position.** `parseTypeExpr` emits a raw `type_expr` (no qualified /
|
||||||
> is rejected, exactly like `s2 := …`. `ConstDecl`/`FnDecl` carry `is_raw` +
|
> `Closure` / parameterized continuation). Resolution skips the builtin
|
||||||
> `name_span` threaded from the parser (`parseConstBinding`/`parseFnDecl`), so the
|
> classifier (`TypeResolver.resolveNamed`'s `skip_builtin`, threaded from
|
||||||
> backtick form (`` `s2 :: … ``) is exempt; the compiler's own builtin definition
|
> `te.is_raw` in [src/ir/lower.zig] and [src/ir/type_bridge.zig]) and looks up
|
||||||
> (`string :: []u8 #builtin`) is the sole non-backtick exemption (a `#builtin`
|
> a `` `s2 ``-declared type (struct / enum / union / alias), else a NORMAL
|
||||||
> constant defines the reserved type). This closed the attempt-2 hole where a
|
> "unknown type 's2'" error (`UnknownTypeChecker.reportIfUnknownType` skips the
|
||||||
> bare `s2 :: (…) {…}` compiled silently and the call rewrite made it callable.
|
> builtin-name exemption when raw). A bare `s2` in type position is still the
|
||||||
|
> builtin int.
|
||||||
|
> - **Declaration position.** A bare reserved-name declaration of EVERY kind
|
||||||
|
> still errors (issue 0076 preserved); the backtick form is exempt. The check
|
||||||
|
> and the exemption are made structurally symmetric:
|
||||||
|
> `checkBindingName` / `checkDeclName` ([src/ir/semantic_diagnostics.zig]) take
|
||||||
|
> `is_raw` as a REQUIRED argument and skip inside the check — no call site can
|
||||||
|
> validate a name without also honoring the exemption, which is what kept the
|
||||||
|
> two from desyncing across the earlier attempts.
|
||||||
> 2. **`#import c` foreign-name exemption.** `c_import.zig` synthesizes foreign
|
> 2. **`#import c` foreign-name exemption.** `c_import.zig` synthesizes foreign
|
||||||
> `#foreign` decls with `Param.is_raw = true`, so generated C param names that
|
> `#foreign` decls with `Param.is_raw = true` (and the synthesized `FnDecl`
|
||||||
> collide with reserved type names (`s1`, `s2`) import unedited.
|
> `is_raw = true`), so generated C names that collide with reserved type names
|
||||||
|
> (`s1`, `s2`) import unedited and a reserved-name foreign fn is bare-callable.
|
||||||
>
|
>
|
||||||
> **Boundary rules.** A raw identifier is a value name and is NEVER a type: using
|
> **Bare-callable foreign / backtick fn.** `lowerCall` rewrites a `.type_expr`
|
||||||
> one in type position (`x : `s2 = 1`) is a clean parse error ([src/parser.zig]
|
> callee to an identifier when a function **of RAW provenance** of that name is in
|
||||||
> `parseTypeExpr` atom). A reserved-spelled FUNCTION (backtick-declared or
|
> scope ([src/ir/lower.zig]) — scoped to the callee `FnDecl`'s `is_raw` flag, so it
|
||||||
> `#import c` foreign) is bare-callable: `lowerCall` rewrites a `.type_expr` callee
|
> only ever fires for a backtick / `#import c` foreign fn (the decl check guarantees
|
||||||
> to an identifier when a function **of RAW provenance** of that name is in scope
|
> no bare reserved-name fn exists). `s2(4)` resolves to the function (`TypeName(val)`
|
||||||
> ([src/ir/lower.zig]) — the rewrite is scoped to the callee `FnDecl`'s `is_raw`
|
> is not a cast).
|
||||||
> flag (F0.6 attempt-3), so it only ever fires for a backtick / `#import c` foreign
|
|
||||||
> fn (the decl check guarantees no bare reserved-name fn exists), 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
|
> **Regression tests.** `examples/0151-types-backtick-raw-identifier.sx` (every
|
||||||
> `is_raw`-gated skip only fires for backtick / foreign names. Regression tests:
|
> VALUE position), `examples/0152-types-backtick-control-flow.sx` (every
|
||||||
> `examples/0151-types-backtick-raw-identifier.sx` (backtick, decl positions),
|
> control-flow / capture form), `examples/0153-types-backtick-const-fn-decl.sx`
|
||||||
> `examples/0152-types-backtick-control-flow.sx` (every control-flow/capture form
|
> (backtick `::` const + fn decl, bare + backtick call),
|
||||||
> + bare ref/call/member access), `examples/1054-errors-backtick-reserved-binding.sx`
|
> `examples/0154-types-backtick-raw-type-reference.sx` (raw in TYPE position —
|
||||||
> (`catch`/`onfail` tag bindings), `examples/1220-ffi-c-import-reserved-name-params.{sx,h,c}`
|
> struct / enum / union / alias decl + reference; bare `s2` still the int),
|
||||||
> (foreign param + function-name exemption, bare-callable foreign fn),
|
> `examples/0155-types-backtick-typed-const-union-tag.sx` (typed const + union tag),
|
||||||
> `examples/1139-diagnostics-backtick-raw-not-a-type.sx` (negative — raw in type
|
> `examples/1054-errors-backtick-reserved-binding.sx` (`catch`/`onfail` tag
|
||||||
> position), `examples/1119`/`1121`/`1123` (negative — bare reserved binding still
|
> bindings), `examples/1220-ffi-c-import-reserved-name-params.{sx,h,c}` (foreign
|
||||||
> rejected across all forms),
|
> param + fn-name exemption, bare-callable foreign fn); negatives
|
||||||
> `examples/0153-types-backtick-const-fn-decl.sx` (positive — backtick `::` const +
|
> `examples/1119`/`1121`/`1123` (bare reserved binding across forms),
|
||||||
> function decl, bare + backtick call), and
|
> `examples/1140-diagnostics-reserved-name-const-fn-decl.sx` (bare const + fn decl),
|
||||||
> `examples/1140-diagnostics-reserved-name-const-fn-decl.sx` (negative — bare `::`
|
> `examples/1141-diagnostics-reserved-name-type-decl.sx` (bare struct / enum / union
|
||||||
> const + function decl rejected). Backtick lexer unit tests in `src/lexer.zig`.
|
> / error / typed-const decl). Backtick lexer + `resolveNamed(skip_builtin)` unit
|
||||||
|
> tests in `src/lexer.zig` / `src/ir/type_resolver.test.zig`.
|
||||||
>
|
>
|
||||||
> The original report is preserved below.
|
> The original report is preserved below.
|
||||||
|
|
||||||
|
|||||||
37
readme.md
37
readme.md
@@ -105,26 +105,31 @@ y : s32 = 0; // explicit type
|
|||||||
z : s32 = ---; // uninitialized
|
z : s32 = ---; // uninitialized
|
||||||
```
|
```
|
||||||
|
|
||||||
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and can't be used
|
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and a *bare*
|
||||||
as bare identifiers at **any** binding site — a value binding (`:=` / typed local /
|
spelling can't be used as an identifier at **any** binding site — a value binding
|
||||||
parameter), a `::` constant or function declaration, or a `::` type declaration
|
(`:=` / typed local / parameter), a `::` constant or function declaration, or a
|
||||||
(`struct` / `enum` / `union` / alias / `protocol` / …) — each is an error
|
`::` type declaration (`struct` / `enum` / `union` / alias / `protocol` / …) — each
|
||||||
(`s2 :: 5` and `s2 :: (n) { … }` are rejected just like `s2 := 5`). A leading
|
is an error (`s2 :: 5` and `s2 :: (n) { … }` are rejected just like `s2 := 5`). A
|
||||||
backtick at the binding site escapes one into a raw identifier — its text drops the
|
leading backtick escapes one into a **raw identifier**: `` `name `` is the literal
|
||||||
backtick and it's never read as a type — so reserved spellings (and keywords) work
|
identifier `name` (the backtick drops out of the text), usable in **every**
|
||||||
as ordinary names. The backtick is needed only where the name is declared; a later
|
position — value, declaration, and type. It is the only way handwritten sx can
|
||||||
bare reference in value position resolves to the binding, while a bare `s2` in type
|
spell a reserved name.
|
||||||
position is still the type. It works in every identifier position (local, global,
|
|
||||||
parameter, field, function name, constant, and the control-flow / capture / binding
|
|
||||||
forms — destructure, `if`/`while` binding, `for` capture, match capture,
|
|
||||||
`catch`/`onfail` tag), and a reserved-spelled function is bare-callable:
|
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
`s2 := 2.5; // value identifier "s2", distinct from the s2 type
|
`s2 := 2.5; // identifier "s2", distinct from the s2 type
|
||||||
print("{}\n", `s2); // 2.5 (or bare `s2`)
|
print("{}\n", `s2); // 2.5 (or bare `s2` in value position)
|
||||||
|
|
||||||
|
`s2 :: struct { x: s64; } // declare a type named with a reserved spelling
|
||||||
|
v : `s2 = ---; // and reference it as a type — resolves to the struct
|
||||||
|
x : s2 = 3; // bare `s2` in type position is still the int type
|
||||||
```
|
```
|
||||||
|
|
||||||
A raw identifier is a value name, never a type — `x : `s2 = 1` is an error.
|
It works in every identifier position — local, global, parameter, struct field,
|
||||||
|
union tag, function name, type/alias/import name, constant, and the control-flow /
|
||||||
|
capture / binding forms (destructure, `if`/`while` binding, `for` capture, match
|
||||||
|
capture, `catch`/`onfail` tag) — and a reserved-spelled function is bare-callable
|
||||||
|
(`s2(10)`). A backtick name used as a type resolves to a `` `name ``-declared type,
|
||||||
|
else a normal `unknown type` error.
|
||||||
|
|
||||||
Foreign declarations from `#import c { … }` are exempt automatically: C names that
|
Foreign declarations from `#import c { … }` are exempt automatically: C names that
|
||||||
collide with reserved type names (e.g. `s1`, `s2`) import unedited, and a foreign
|
collide with reserved type names (e.g. `s1`, `s2`) import unedited, and a foreign
|
||||||
|
|||||||
51
specs.md
51
specs.md
@@ -38,34 +38,49 @@ sole exception: a `#builtin` constant defines the reserved type and is allowed.)
|
|||||||
|
|
||||||
#### Backtick raw-identifier escape
|
#### Backtick raw-identifier escape
|
||||||
|
|
||||||
A leading backtick makes the following identifier **raw**: its text excludes the
|
A leading backtick makes the following token a **raw identifier**: `` `name `` is
|
||||||
backtick and it is never type-classified, so a reserved-type-name spelling can be
|
the **literal identifier** `name` — "treat this token as a plain identifier, never
|
||||||
used as an ordinary value identifier. The backtick is required at the **binding
|
the reserved keyword/type." The backtick is not part of the name's text (the text
|
||||||
site** — the declaration that introduces the name — to escape the reserved-name
|
is `name`), and the escape is usable in **every position**: value, declaration,
|
||||||
rule. A later reference is resolved by position: in **value** position a bare `s2`
|
**and type**. It is the only way handwritten sx can spell a reserved name.
|
||||||
resolves to the binding; in **type** position a bare `s2` is still the signed-int
|
|
||||||
type.
|
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
`s2 := 2.5; // OK — value identifier "s2", distinct from the s2 type
|
`s2 := 2.5; // OK — identifier "s2", distinct from the s2 type
|
||||||
print("{}\n", `s2); // 2.5 (backtick reference)
|
print("{}\n", `s2); // 2.5 (backtick reference)
|
||||||
print("{}\n", s2); // 2.5 (bare reference, resolves to the binding)
|
print("{}\n", s2); // 2.5 (bare reference in value position → the binding)
|
||||||
x : s2 = 3; // bare `s2` in TYPE position is still the s2 type
|
x : s2 = 3; // bare `s2` in TYPE position is still the s2 int type
|
||||||
```
|
```
|
||||||
|
|
||||||
A raw identifier is a value name and is **never a type**: using one in type
|
**Type position.** A backtick in type position is the literal name used as a type
|
||||||
position (`x : `s2 = 1`) is a parse error.
|
reference: it resolves to a `` `s2 ``-declared type (struct / enum / union / type
|
||||||
|
alias / …), and never the builtin. A bare `s2` in type position stays the builtin
|
||||||
|
int; a backtick name with no matching declaration is a normal `unknown type 's2'`
|
||||||
|
error.
|
||||||
|
|
||||||
The escape works in **every identifier position** — local, global, parameter,
|
```sx
|
||||||
struct field, function name, a later reference, and every control-flow / capture /
|
`s2 :: struct { x: s64; } // declare a type whose name is a reserved spelling
|
||||||
binding form: a destructure name, an `if` / `while` optional binding, a `for`
|
v : `s2 = ---; // reference it as a type — resolves to the struct
|
||||||
capture and index, a match-arm capture, and a `catch` / `onfail` tag binding:
|
v.x = 7;
|
||||||
|
x : s2 = 3; // bare `s2` is still the 2-bit signed int
|
||||||
|
```
|
||||||
|
|
||||||
|
**Declaration position.** A *bare* reserved-name declaration of every kind still
|
||||||
|
errors (a value binding, a `::` constant / function, and a `::` type / alias /
|
||||||
|
protocol / foreign-class / ufcs / namespaced-import name); the backtick form is
|
||||||
|
exempt. The escape works in **every identifier position** — local, global,
|
||||||
|
parameter, struct field, union tag, function name, type/alias/import name, a later
|
||||||
|
reference, and every control-flow / capture / binding form (destructure name,
|
||||||
|
`if` / `while` optional binding, `for` capture and index, match-arm capture, and a
|
||||||
|
`catch` / `onfail` tag binding):
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
`u8 := 100; // global
|
`u8 := 100; // global
|
||||||
`s2 :: 2.5; // constant declaration
|
`s2 :: 2.5; // constant declaration
|
||||||
|
`s2 : s64 : 5; // typed constant declaration
|
||||||
`u8 :: (`s1: s64) -> s64 { `s1 } // function name + parameter
|
`u8 :: (`s1: s64) -> s64 { `s1 } // function name + parameter
|
||||||
P :: struct { `s2: f64; } // struct field
|
P :: struct { `s2: f64; } // struct field
|
||||||
|
M :: union { `s1: s32; } // union tag
|
||||||
|
`u16 :: enum { A; B; } // type-declaration name
|
||||||
`u8, rest := pair(); // destructure name
|
`u8, rest := pair(); // destructure name
|
||||||
if `s16 := maybe() { } // optional binding
|
if `s16 := maybe() { } // optional binding
|
||||||
for xs: (`bool, `u16) { } // for capture + index
|
for xs: (`bool, `u16) { } // for capture + index
|
||||||
@@ -73,8 +88,8 @@ x catch `s2 { } // catch tag binding
|
|||||||
```
|
```
|
||||||
|
|
||||||
A reserved-spelled **function** is bare-callable: `` `s2 :: (n: s64) -> s64 { … } ``
|
A reserved-spelled **function** is bare-callable: `` `s2 :: (n: s64) -> s64 { … } ``
|
||||||
can be invoked as `s2(10)` (the callee spelling parses as a type but resolves to
|
can be invoked as `s2(10)` (the bare callee spelling parses as a type but resolves
|
||||||
the function when one of that name is in scope; `TypeName(val)` is not a cast).
|
to the function when one of that name is in scope; `TypeName(val)` is not a cast).
|
||||||
|
|
||||||
A backtick may also escape a keyword spelling (`` `for ``, `` `struct ``), yielding
|
A backtick may also escape a keyword spelling (`` `for ``, `` `struct ``), yielding
|
||||||
an identifier with that text.
|
an identifier with that text.
|
||||||
|
|||||||
42
src/ast.zig
42
src/ast.zig
@@ -380,12 +380,19 @@ pub const EnumDecl = struct {
|
|||||||
is_flags: bool = false,
|
is_flags: bool = false,
|
||||||
variant_values: []const ?*Node = &.{}, // explicit value per variant (null = auto), empty = all auto
|
variant_values: []const ?*Node = &.{}, // explicit value per variant (null = auto), empty = all auto
|
||||||
backing_type: ?*Node = null, // optional backing type: enum u8 { ... }
|
backing_type: ?*Node = null, // optional backing type: enum u8 { ... }
|
||||||
|
/// True when the declared NAME was a backtick raw identifier
|
||||||
|
/// (`` `s2 :: enum { … } ``) — exempt from the reserved-type-name decl
|
||||||
|
/// check (issue 0089). A bare reserved-name decl still errors.
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const UnionDecl = struct {
|
pub const UnionDecl = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
field_names: []const []const u8,
|
field_names: []const []const u8,
|
||||||
field_types: []const *Node,
|
field_types: []const *Node,
|
||||||
|
/// True when the declared NAME was a backtick raw identifier — exempt from
|
||||||
|
/// the reserved-type-name decl check (issue 0089).
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `Foo :: error { TagA, TagB }` — a named error set. Tags are bare
|
/// `Foo :: error { TagA, TagB }` — a named error set. Tags are bare
|
||||||
@@ -393,6 +400,9 @@ pub const UnionDecl = struct {
|
|||||||
pub const ErrorSetDecl = struct {
|
pub const ErrorSetDecl = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
tag_names: []const []const u8,
|
tag_names: []const []const u8,
|
||||||
|
/// True when the declared NAME was a backtick raw identifier — exempt from
|
||||||
|
/// the reserved-type-name decl check (issue 0089).
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const StructTypeParam = struct {
|
pub const StructTypeParam = struct {
|
||||||
@@ -418,6 +428,10 @@ pub const StructDecl = struct {
|
|||||||
using_entries: []const UsingEntry = &.{},
|
using_entries: []const UsingEntry = &.{},
|
||||||
methods: []const *Node = &.{}, // fn_decl nodes for struct methods
|
methods: []const *Node = &.{}, // fn_decl nodes for struct methods
|
||||||
constants: []const *Node = &.{}, // const_decl nodes for struct-level constants
|
constants: []const *Node = &.{}, // const_decl nodes for struct-level constants
|
||||||
|
/// True when the declared NAME was a backtick raw identifier
|
||||||
|
/// (`` `s2 :: struct { … } ``) — exempt from the reserved-type-name decl
|
||||||
|
/// check (issue 0089). A bare reserved-name decl still errors.
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const StructFieldInit = struct {
|
pub const StructFieldInit = struct {
|
||||||
@@ -444,6 +458,12 @@ pub const TypeExpr = struct {
|
|||||||
name: []const u8,
|
name: []const u8,
|
||||||
is_generic: bool = false,
|
is_generic: bool = false,
|
||||||
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
|
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
|
||||||
|
/// True when written as a backtick raw identifier in type position
|
||||||
|
/// (`` `s2 ``). Such a reference is the LITERAL name `s2` used as a type —
|
||||||
|
/// resolution skips the builtin/reserved classifier and looks up a
|
||||||
|
/// `` `s2 ``-declared type (struct/enum/union/alias), else "unknown type"
|
||||||
|
/// (issue 0089). A bare `s2` keeps `is_raw = false` and is the int type.
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `$<pack_name>[<index>]` in type position. Resolves to the i-th
|
/// `$<pack_name>[<index>]` in type position. Resolves to the i-th
|
||||||
@@ -530,6 +550,10 @@ pub const ReturnStmt = struct {
|
|||||||
pub const ImportDecl = struct {
|
pub const ImportDecl = struct {
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
name: ?[]const u8,
|
name: ?[]const u8,
|
||||||
|
/// True when the namespace NAME was a backtick raw identifier
|
||||||
|
/// (`` `s2 :: #import "…" ``) — exempt from the reserved-type-name decl
|
||||||
|
/// check (issue 0089). A flat `#import` (name == null) binds nothing.
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ArrayTypeExpr = struct {
|
pub const ArrayTypeExpr = struct {
|
||||||
@@ -638,6 +662,9 @@ pub const SpreadExpr = struct {
|
|||||||
pub const NamespaceDecl = struct {
|
pub const NamespaceDecl = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
decls: []const *Node,
|
decls: []const *Node,
|
||||||
|
/// True when the namespace NAME was a backtick raw identifier — exempt
|
||||||
|
/// from the reserved-type-name decl check (issue 0089).
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ForeignExpr = struct {
|
pub const ForeignExpr = struct {
|
||||||
@@ -648,6 +675,9 @@ pub const ForeignExpr = struct {
|
|||||||
pub const LibraryDecl = struct {
|
pub const LibraryDecl = struct {
|
||||||
lib_name: []const u8,
|
lib_name: []const u8,
|
||||||
name: []const u8, // sx-side constant name
|
name: []const u8, // sx-side constant name
|
||||||
|
/// True when the constant NAME was a backtick raw identifier — exempt from
|
||||||
|
/// the reserved-type-name decl check (issue 0089).
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FrameworkDecl = struct {
|
pub const FrameworkDecl = struct {
|
||||||
@@ -691,6 +721,9 @@ pub const TupleElement = struct {
|
|||||||
pub const UfcsAlias = struct {
|
pub const UfcsAlias = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
target: []const u8,
|
target: []const u8,
|
||||||
|
/// True when the alias NAME was a backtick raw identifier — exempt from
|
||||||
|
/// the reserved-type-name decl check (issue 0089).
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CImportDecl = struct {
|
pub const CImportDecl = struct {
|
||||||
@@ -700,6 +733,9 @@ pub const CImportDecl = struct {
|
|||||||
flags: []const []const u8,
|
flags: []const []const u8,
|
||||||
name: ?[]const u8 = null,
|
name: ?[]const u8 = null,
|
||||||
bitcode_paths: []const []const u8 = &.{}, // populated during import resolution
|
bitcode_paths: []const []const u8 = &.{}, // populated during import resolution
|
||||||
|
/// True when the namespace NAME was a backtick raw identifier — exempt
|
||||||
|
/// from the reserved-type-name decl check (issue 0089).
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ProtocolMethodDecl = struct {
|
pub const ProtocolMethodDecl = struct {
|
||||||
@@ -720,6 +756,9 @@ pub const ProtocolDecl = struct {
|
|||||||
methods: []const ProtocolMethodDecl,
|
methods: []const ProtocolMethodDecl,
|
||||||
is_inline: bool = false, // #inline — embedded fn ptrs instead of vtable pointer
|
is_inline: bool = false, // #inline — embedded fn ptrs instead of vtable pointer
|
||||||
type_params: []const StructTypeParam = &.{}, // for `protocol(Target: Type) { ... }`
|
type_params: []const StructTypeParam = &.{}, // for `protocol(Target: Type) { ... }`
|
||||||
|
/// True when the declared NAME was a backtick raw identifier — exempt from
|
||||||
|
/// the reserved-type-name decl check (issue 0089).
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ForeignRuntime = enum {
|
pub const ForeignRuntime = enum {
|
||||||
@@ -776,6 +815,9 @@ pub const ForeignClassDecl = struct {
|
|||||||
members: []const ForeignClassMember = &.{},
|
members: []const ForeignClassMember = &.{},
|
||||||
is_foreign: bool = false, // `#foreign #...` prefix — class is provided by the foreign runtime; we only reference it
|
is_foreign: bool = false, // `#foreign #...` prefix — class is provided by the foreign runtime; we only reference it
|
||||||
is_main: bool = false, // `#jni_main` / `#objc_main` — class is the launchable entry (Activity / UIApplicationDelegate / ...)
|
is_main: bool = false, // `#jni_main` / `#objc_main` — class is the launchable entry (Activity / UIApplicationDelegate / ...)
|
||||||
|
/// True when the sx-side alias NAME was a backtick raw identifier — exempt
|
||||||
|
/// from the reserved-type-name decl check (issue 0089).
|
||||||
|
is_raw: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const JniEnvBlock = struct {
|
pub const JniEnvBlock = struct {
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ pub const ResolvedModule = struct {
|
|||||||
name: []const u8,
|
name: []const u8,
|
||||||
other: ResolvedModule,
|
other: ResolvedModule,
|
||||||
span: ast.Span,
|
span: ast.Span,
|
||||||
|
is_raw: bool,
|
||||||
) !void {
|
) !void {
|
||||||
const ns_node = try allocator.create(Node);
|
const ns_node = try allocator.create(Node);
|
||||||
ns_node.* = .{
|
ns_node.* = .{
|
||||||
@@ -361,6 +362,10 @@ pub const ResolvedModule = struct {
|
|||||||
.data = .{ .namespace_decl = .{
|
.data = .{ .namespace_decl = .{
|
||||||
.name = name,
|
.name = name,
|
||||||
.decls = other.decls,
|
.decls = other.decls,
|
||||||
|
// Carry the backtick raw escape from the `name :: #import …`
|
||||||
|
// form so a reserved-name namespace is exempt from the decl
|
||||||
|
// check, symmetric to every other decl site (issue 0089).
|
||||||
|
.is_raw = is_raw,
|
||||||
} },
|
} },
|
||||||
};
|
};
|
||||||
try self.scope.put(name, {});
|
try self.scope.put(name, {});
|
||||||
@@ -487,6 +492,7 @@ pub fn resolveImports(
|
|||||||
.data = .{ .namespace_decl = .{
|
.data = .{ .namespace_decl = .{
|
||||||
.name = ns_name,
|
.name = ns_name,
|
||||||
.decls = try ns_decls.toOwnedSlice(allocator),
|
.decls = try ns_decls.toOwnedSlice(allocator),
|
||||||
|
.is_raw = ci.is_raw,
|
||||||
} },
|
} },
|
||||||
};
|
};
|
||||||
ns_node.source_file = file_path;
|
ns_node.source_file = file_path;
|
||||||
@@ -569,7 +575,7 @@ pub fn resolveImports(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (imp.name) |ns_name| {
|
if (imp.name) |ns_name| {
|
||||||
try mod.addNamespace(allocator, &decl_list, &own_decl_list, &seen_in_list, ns_name, imported_mod, decl.span);
|
try mod.addNamespace(allocator, &decl_list, &own_decl_list, &seen_in_list, ns_name, imported_mod, decl.span, imp.is_raw);
|
||||||
} else {
|
} else {
|
||||||
try mod.mergeFlat(allocator, &decl_list, &seen_in_list, &seen_nodes, imported_mod);
|
try mod.mergeFlat(allocator, &decl_list, &seen_in_list, &seen_nodes, imported_mod);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11892,8 +11892,8 @@ pub const Lowering = struct {
|
|||||||
// type_bridge, which now takes the alias map as an explicit argument
|
// type_bridge, which now takes the alias map as an explicit argument
|
||||||
// (the `TypeTable.aliases` borrow is gone, A2.3).
|
// (the `TypeTable.aliases` borrow is gone, A2.3).
|
||||||
switch (node.data) {
|
switch (node.data) {
|
||||||
.type_expr => |te| return self.typeResolver().resolveName(te.name),
|
.type_expr => |te| return self.typeResolver().resolveName(te.name, te.is_raw),
|
||||||
.identifier => |id| return self.typeResolver().resolveName(id.name),
|
.identifier => |id| return self.typeResolver().resolveName(id.name, id.is_raw),
|
||||||
// A non-spread tuple literal in a type position is a tuple-type
|
// A non-spread tuple literal in a type position is a tuple-type
|
||||||
// literal (`(s32, s32)`); validate its elements are types and reject
|
// literal (`(s32, s32)`); validate its elements are types and reject
|
||||||
// non-type elements loudly (issue 0067).
|
// non-type elements loudly (issue 0067).
|
||||||
|
|||||||
@@ -116,13 +116,16 @@ pub const UnknownTypeChecker = struct {
|
|||||||
if (node.source_file) |sf| self.diagnostics.current_source_file = sf;
|
if (node.source_file) |sf| self.diagnostics.current_source_file = sf;
|
||||||
switch (node.data) {
|
switch (node.data) {
|
||||||
// ── Binding-introducing nodes: check the name(s), then recurse. ──
|
// ── Binding-introducing nodes: check the name(s), then recurse. ──
|
||||||
|
// Every site passes the node's own `is_raw` straight to the check —
|
||||||
|
// never an `if (!is_raw)` call-site guard — so the check and its
|
||||||
|
// exemption are one operation that cannot be threaded apart (0089).
|
||||||
.var_decl => |vd| {
|
.var_decl => |vd| {
|
||||||
if (!vd.is_raw) self.checkBindingName(vd.name, vd.name_span);
|
self.checkBindingName(vd.name, vd.name_span, vd.is_raw);
|
||||||
if (vd.value) |v| self.checkBindingNames(v);
|
if (vd.value) |v| self.checkBindingNames(v);
|
||||||
},
|
},
|
||||||
.destructure_decl => |dd| {
|
.destructure_decl => |dd| {
|
||||||
for (dd.names, dd.name_spans, dd.name_is_raw) |n, sp, raw| {
|
for (dd.names, dd.name_spans, dd.name_is_raw) |n, sp, raw| {
|
||||||
if (!raw) self.checkBindingName(n, sp);
|
self.checkBindingName(n, sp, raw);
|
||||||
}
|
}
|
||||||
self.checkBindingNames(dd.value);
|
self.checkBindingNames(dd.value);
|
||||||
},
|
},
|
||||||
@@ -131,7 +134,7 @@ pub const UnknownTypeChecker = struct {
|
|||||||
// `s2 :: (…) {…}` (free fn or struct/impl method) is rejected,
|
// `s2 :: (…) {…}` (free fn or struct/impl method) is rejected,
|
||||||
// exactly like `s2 := …`. Backtick (`` `s2 :: … ``) and
|
// exactly like `s2 := …`. Backtick (`` `s2 :: … ``) and
|
||||||
// `#import c` foreign fns set `is_raw` and are exempt (0089).
|
// `#import c` foreign fns set `is_raw` and are exempt (0089).
|
||||||
if (!fd.is_raw) self.checkBindingName(fd.name, fd.name_span);
|
self.checkBindingName(fd.name, fd.name_span, fd.is_raw);
|
||||||
self.checkParamNames(fd.params);
|
self.checkParamNames(fd.params);
|
||||||
self.checkBindingNames(fd.body);
|
self.checkBindingNames(fd.body);
|
||||||
},
|
},
|
||||||
@@ -140,29 +143,23 @@ pub const UnknownTypeChecker = struct {
|
|||||||
self.checkBindingNames(lm.body);
|
self.checkBindingNames(lm.body);
|
||||||
},
|
},
|
||||||
.param => |p| {
|
.param => |p| {
|
||||||
if (!p.is_raw) self.checkBindingName(p.name, p.name_span);
|
self.checkBindingName(p.name, p.name_span, p.is_raw);
|
||||||
if (p.default_expr) |de| self.checkBindingNames(de);
|
if (p.default_expr) |de| self.checkBindingNames(de);
|
||||||
},
|
},
|
||||||
.if_expr => |ie| {
|
.if_expr => |ie| {
|
||||||
if (ie.binding_name) |bn| {
|
if (ie.binding_name) |bn| self.checkBindingName(bn, ie.binding_span, ie.binding_is_raw);
|
||||||
if (!ie.binding_is_raw) self.checkBindingName(bn, ie.binding_span);
|
|
||||||
}
|
|
||||||
self.checkBindingNames(ie.condition);
|
self.checkBindingNames(ie.condition);
|
||||||
self.checkBindingNames(ie.then_branch);
|
self.checkBindingNames(ie.then_branch);
|
||||||
if (ie.else_branch) |e| self.checkBindingNames(e);
|
if (ie.else_branch) |e| self.checkBindingNames(e);
|
||||||
},
|
},
|
||||||
.while_expr => |we| {
|
.while_expr => |we| {
|
||||||
if (we.binding_name) |bn| {
|
if (we.binding_name) |bn| self.checkBindingName(bn, we.binding_span, we.binding_is_raw);
|
||||||
if (!we.binding_is_raw) self.checkBindingName(bn, we.binding_span);
|
|
||||||
}
|
|
||||||
self.checkBindingNames(we.condition);
|
self.checkBindingNames(we.condition);
|
||||||
self.checkBindingNames(we.body);
|
self.checkBindingNames(we.body);
|
||||||
},
|
},
|
||||||
.for_expr => |fe| {
|
.for_expr => |fe| {
|
||||||
if (fe.capture_name.len != 0 and !fe.capture_is_raw) self.checkBindingName(fe.capture_name, fe.capture_span);
|
if (fe.capture_name.len != 0) self.checkBindingName(fe.capture_name, fe.capture_span, fe.capture_is_raw);
|
||||||
if (fe.index_name) |idx| {
|
if (fe.index_name) |idx| self.checkBindingName(idx, fe.index_span, fe.index_is_raw);
|
||||||
if (!fe.index_is_raw) self.checkBindingName(idx, fe.index_span);
|
|
||||||
}
|
|
||||||
self.checkBindingNames(fe.iterable);
|
self.checkBindingNames(fe.iterable);
|
||||||
if (fe.range_end) |re| self.checkBindingNames(re);
|
if (fe.range_end) |re| self.checkBindingNames(re);
|
||||||
self.checkBindingNames(fe.body);
|
self.checkBindingNames(fe.body);
|
||||||
@@ -170,31 +167,23 @@ pub const UnknownTypeChecker = struct {
|
|||||||
.match_expr => |me| {
|
.match_expr => |me| {
|
||||||
self.checkBindingNames(me.subject);
|
self.checkBindingNames(me.subject);
|
||||||
for (me.arms) |arm| {
|
for (me.arms) |arm| {
|
||||||
if (arm.capture) |cap| {
|
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span, arm.capture_is_raw);
|
||||||
if (!arm.capture_is_raw) self.checkBindingName(cap, arm.capture_span);
|
|
||||||
}
|
|
||||||
if (arm.pattern) |p| self.checkBindingNames(p);
|
if (arm.pattern) |p| self.checkBindingNames(p);
|
||||||
self.checkBindingNames(arm.body);
|
self.checkBindingNames(arm.body);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.match_arm => |arm| {
|
.match_arm => |arm| {
|
||||||
if (arm.capture) |cap| {
|
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span, arm.capture_is_raw);
|
||||||
if (!arm.capture_is_raw) self.checkBindingName(cap, arm.capture_span);
|
|
||||||
}
|
|
||||||
if (arm.pattern) |p| self.checkBindingNames(p);
|
if (arm.pattern) |p| self.checkBindingNames(p);
|
||||||
self.checkBindingNames(arm.body);
|
self.checkBindingNames(arm.body);
|
||||||
},
|
},
|
||||||
.catch_expr => |ce| {
|
.catch_expr => |ce| {
|
||||||
if (ce.binding) |b| {
|
if (ce.binding) |b| self.checkBindingName(b, ce.binding_span, ce.binding_is_raw);
|
||||||
if (!ce.binding_is_raw) self.checkBindingName(b, ce.binding_span);
|
|
||||||
}
|
|
||||||
self.checkBindingNames(ce.operand);
|
self.checkBindingNames(ce.operand);
|
||||||
self.checkBindingNames(ce.body);
|
self.checkBindingNames(ce.body);
|
||||||
},
|
},
|
||||||
.onfail_stmt => |os| {
|
.onfail_stmt => |os| {
|
||||||
if (os.binding) |b| {
|
if (os.binding) |b| self.checkBindingName(b, os.binding_span, os.binding_is_raw);
|
||||||
if (!os.binding_is_raw) self.checkBindingName(b, os.binding_span);
|
|
||||||
}
|
|
||||||
self.checkBindingNames(os.body);
|
self.checkBindingNames(os.body);
|
||||||
},
|
},
|
||||||
// impl / protocol-default / foreign-class method bodies: each
|
// impl / protocol-default / foreign-class method bodies: each
|
||||||
@@ -203,12 +192,12 @@ pub const UnknownTypeChecker = struct {
|
|||||||
// param/local names mis-lower the same as any other.
|
// param/local names mis-lower the same as any other.
|
||||||
.impl_block => |ib| for (ib.methods) |m| self.checkBindingNames(m),
|
.impl_block => |ib| for (ib.methods) |m| self.checkBindingNames(m),
|
||||||
.protocol_decl => |pd| {
|
.protocol_decl => |pd| {
|
||||||
self.checkDeclName(node, pd.name);
|
self.checkDeclName(node, pd.name, pd.is_raw);
|
||||||
for (pd.methods) |m| {
|
for (pd.methods) |m| {
|
||||||
if (m.default_body) |body| {
|
if (m.default_body) |body| {
|
||||||
for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| {
|
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;
|
const raw = i < m.param_name_is_raw.len and m.param_name_is_raw[i];
|
||||||
self.checkBindingName(pn, sp);
|
self.checkBindingName(pn, sp, raw);
|
||||||
}
|
}
|
||||||
self.checkBindingNames(body);
|
self.checkBindingNames(body);
|
||||||
}
|
}
|
||||||
@@ -217,12 +206,12 @@ pub const UnknownTypeChecker = struct {
|
|||||||
.foreign_class_decl => |fcd| {
|
.foreign_class_decl => |fcd| {
|
||||||
// The sx-side alias (left of `::`) is a user-chosen name, so a
|
// The sx-side alias (left of `::`) is a user-chosen name, so a
|
||||||
// reserved spelling is rejected like any other type decl (0089).
|
// reserved spelling is rejected like any other type decl (0089).
|
||||||
self.checkDeclName(node, fcd.name);
|
self.checkDeclName(node, fcd.name, fcd.is_raw);
|
||||||
for (fcd.members) |member| switch (member) {
|
for (fcd.members) |member| switch (member) {
|
||||||
.method => |m| if (m.body) |body| {
|
.method => |m| if (m.body) |body| {
|
||||||
for (m.param_names, m.param_name_spans, 0..) |pn, sp, i| {
|
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;
|
const raw = i < m.param_name_is_raw.len and m.param_name_is_raw[i];
|
||||||
self.checkBindingName(pn, sp);
|
self.checkBindingName(pn, sp, raw);
|
||||||
}
|
}
|
||||||
self.checkBindingNames(body);
|
self.checkBindingNames(body);
|
||||||
},
|
},
|
||||||
@@ -235,7 +224,7 @@ pub const UnknownTypeChecker = struct {
|
|||||||
// module decls held inline; descend so an imported module's
|
// module decls held inline; descend so an imported module's
|
||||||
// reserved-name binding is rejected too (issue 0077).
|
// reserved-name binding is rejected too (issue 0077).
|
||||||
.namespace_decl => |nd| {
|
.namespace_decl => |nd| {
|
||||||
self.checkDeclName(node, nd.name);
|
self.checkDeclName(node, nd.name, nd.is_raw);
|
||||||
for (nd.decls) |d| self.checkBindingNames(d);
|
for (nd.decls) |d| self.checkBindingNames(d);
|
||||||
},
|
},
|
||||||
.const_decl => |cd| {
|
.const_decl => |cd| {
|
||||||
@@ -247,12 +236,12 @@ pub const UnknownTypeChecker = struct {
|
|||||||
// own name on recursion — don't double-check it here (0089).
|
// own name on recursion — don't double-check it here (0089).
|
||||||
switch (cd.value.data) {
|
switch (cd.value.data) {
|
||||||
.builtin_expr, .struct_decl, .enum_decl, .union_decl, .error_set_decl, .fn_decl => {},
|
.builtin_expr, .struct_decl, .enum_decl, .union_decl, .error_set_decl, .fn_decl => {},
|
||||||
else => if (!cd.is_raw) self.checkBindingName(cd.name, cd.name_span),
|
else => self.checkBindingName(cd.name, cd.name_span, cd.is_raw),
|
||||||
}
|
}
|
||||||
self.checkBindingNames(cd.value);
|
self.checkBindingNames(cd.value);
|
||||||
},
|
},
|
||||||
.struct_decl => |sd| {
|
.struct_decl => |sd| {
|
||||||
self.checkDeclName(node, sd.name);
|
self.checkDeclName(node, sd.name, sd.is_raw);
|
||||||
for (sd.methods) |m| self.checkBindingNames(m);
|
for (sd.methods) |m| self.checkBindingNames(m);
|
||||||
for (sd.constants) |c| self.checkBindingNames(c);
|
for (sd.constants) |c| self.checkBindingNames(c);
|
||||||
for (sd.field_defaults) |fdef| if (fdef) |d| self.checkBindingNames(d);
|
for (sd.field_defaults) |fdef| if (fdef) |d| self.checkBindingNames(d);
|
||||||
@@ -319,13 +308,13 @@ pub const UnknownTypeChecker = struct {
|
|||||||
// spelling as the declared name is rejected (issue 0089). These
|
// spelling as the declared name is rejected (issue 0089). These
|
||||||
// have no nested binding sites, so only the name is checked. A
|
// have no nested binding sites, so only the name is checked. A
|
||||||
// flat `#import`/`#import c` (name == null) binds nothing. ──
|
// flat `#import`/`#import c` (name == null) binds nothing. ──
|
||||||
.enum_decl => |ed| self.checkDeclName(node, ed.name),
|
.enum_decl => |ed| self.checkDeclName(node, ed.name, ed.is_raw),
|
||||||
.union_decl => |ud| self.checkDeclName(node, ud.name),
|
.union_decl => |ud| self.checkDeclName(node, ud.name, ud.is_raw),
|
||||||
.error_set_decl => |esd| self.checkDeclName(node, esd.name),
|
.error_set_decl => |esd| self.checkDeclName(node, esd.name, esd.is_raw),
|
||||||
.ufcs_alias => |ua| self.checkDeclName(node, ua.name),
|
.ufcs_alias => |ua| self.checkDeclName(node, ua.name, ua.is_raw),
|
||||||
.library_decl => |ld| self.checkDeclName(node, ld.name),
|
.library_decl => |ld| self.checkDeclName(node, ld.name, ld.is_raw),
|
||||||
.import_decl => |imp| if (imp.name) |n| self.checkDeclName(node, n),
|
.import_decl => |imp| if (imp.name) |n| self.checkDeclName(node, n, imp.is_raw),
|
||||||
.c_import_decl => |cid| if (cid.name) |n| self.checkDeclName(node, n),
|
.c_import_decl => |cid| if (cid.name) |n| self.checkDeclName(node, n, cid.is_raw),
|
||||||
// ── Leaves & pure type-expression nodes: no binding sites below. ──
|
// ── Leaves & pure type-expression nodes: no binding sites below. ──
|
||||||
// Type-expression subtrees carry only type names (no value
|
// Type-expression subtrees carry only type names (no value
|
||||||
// bindings). Listing each tag explicitly (rather than an `else`) is
|
// bindings). Listing each tag explicitly (rather than an `else`) is
|
||||||
@@ -370,8 +359,9 @@ pub const UnknownTypeChecker = struct {
|
|||||||
fn checkParamNames(self: UnknownTypeChecker, params: []const ast.Param) void {
|
fn checkParamNames(self: UnknownTypeChecker, params: []const ast.Param) void {
|
||||||
for (params) |p| {
|
for (params) |p| {
|
||||||
// A backtick raw param (`` (`s2: T) ``) or a `#import c` foreign
|
// A backtick raw param (`` (`s2: T) ``) or a `#import c` foreign
|
||||||
// param is exempt from the reserved-type-name rule (issue 0089).
|
// param is exempt from the reserved-type-name rule (issue 0089) —
|
||||||
if (!p.is_raw) self.checkBindingName(p.name, p.name_span);
|
// the exemption is honored inside `checkBindingName` via `p.is_raw`.
|
||||||
|
self.checkBindingName(p.name, p.name_span, p.is_raw);
|
||||||
if (p.default_expr) |de| self.checkBindingNames(de);
|
if (p.default_expr) |de| self.checkBindingNames(de);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -708,8 +698,8 @@ pub const UnknownTypeChecker = struct {
|
|||||||
switch (node.data) {
|
switch (node.data) {
|
||||||
// A `$`-prefixed name (`-> $R`) introduces/references a generic type
|
// A `$`-prefixed name (`-> $R`) introduces/references a generic type
|
||||||
// param inline — always valid in a type position.
|
// param inline — always valid in a type position.
|
||||||
.type_expr => |te| if (!te.is_generic) self.reportIfUnknownType(te.name, node.span, declared, in_scope, type_vals),
|
.type_expr => |te| if (!te.is_generic) self.reportIfUnknownType(te.name, node.span, declared, in_scope, type_vals, te.is_raw),
|
||||||
.identifier => |id| self.reportIfUnknownType(id.name, node.span, declared, in_scope, type_vals),
|
.identifier => |id| self.reportIfUnknownType(id.name, node.span, declared, in_scope, type_vals, id.is_raw),
|
||||||
.pointer_type_expr => |pt| self.checkTypeNodeForUnknown(pt.pointee_type, declared, in_scope, type_vals),
|
.pointer_type_expr => |pt| self.checkTypeNodeForUnknown(pt.pointee_type, declared, in_scope, type_vals),
|
||||||
.many_pointer_type_expr => |mp| self.checkTypeNodeForUnknown(mp.element_type, declared, in_scope, type_vals),
|
.many_pointer_type_expr => |mp| self.checkTypeNodeForUnknown(mp.element_type, declared, in_scope, type_vals),
|
||||||
.slice_type_expr => |st| self.checkTypeNodeForUnknown(st.element_type, declared, in_scope, type_vals),
|
.slice_type_expr => |st| self.checkTypeNodeForUnknown(st.element_type, declared, in_scope, type_vals),
|
||||||
@@ -753,11 +743,17 @@ pub const UnknownTypeChecker = struct {
|
|||||||
declared: *std.StringHashMap(void),
|
declared: *std.StringHashMap(void),
|
||||||
in_scope: []const ast.StructTypeParam,
|
in_scope: []const ast.StructTypeParam,
|
||||||
type_vals: []const []const u8,
|
type_vals: []const []const u8,
|
||||||
|
is_raw: bool,
|
||||||
) void {
|
) void {
|
||||||
// Only bare identifiers are validated. Inline-spelled compound types
|
// Only bare identifiers are validated. Inline-spelled compound types
|
||||||
// (`[:0]u8`, `mod.Type`, …) carry non-identifier characters — trust them.
|
// (`[:0]u8`, `mod.Type`, …) carry non-identifier characters — trust them.
|
||||||
if (!isIdentLike(name)) return;
|
if (!isIdentLike(name)) return;
|
||||||
if (isBuiltinTypeName(name)) return;
|
// A backtick raw reference (`` `s2 ``) is the LITERAL name used as a
|
||||||
|
// type — explicitly NOT the builtin/reserved spelling — so it must
|
||||||
|
// resolve to a `` `s2 ``-declared type, else a normal "unknown type"
|
||||||
|
// error. Skip the builtin-name exemption that would otherwise wave a
|
||||||
|
// bare `s2` through (issue 0089).
|
||||||
|
if (!is_raw and isBuiltinTypeName(name)) return;
|
||||||
for (in_scope) |tp| if (std.mem.eql(u8, tp.name, name)) return;
|
for (in_scope) |tp| if (std.mem.eql(u8, tp.name, name)) return;
|
||||||
if (declared.contains(name)) return;
|
if (declared.contains(name)) return;
|
||||||
// Registered as a real (non-stub) type — covers imported concrete
|
// Registered as a real (non-stub) type — covers imported concrete
|
||||||
@@ -789,7 +785,14 @@ pub const UnknownTypeChecker = struct {
|
|||||||
/// (LLVM verifier abort, or a silent mutation-losing copy). Rejecting the
|
/// (LLVM verifier abort, or a silent mutation-losing copy). Rejecting the
|
||||||
/// name here, before lowering, keeps the `.identifier`-only address-of paths
|
/// name here, before lowering, keeps the `.identifier`-only address-of paths
|
||||||
/// correct without any lowering special-case.
|
/// correct without any lowering special-case.
|
||||||
fn checkBindingName(self: UnknownTypeChecker, name: []const u8, span: ?ast.Span) void {
|
/// `is_raw` is a REQUIRED argument, not a call-site guard: the exemption
|
||||||
|
/// lives INSIDE the check so no caller can validate a name without also
|
||||||
|
/// honoring the backtick / `#import c` foreign exemption. This is what keeps
|
||||||
|
/// the check and the exemption from desyncing — the recurring failure of the
|
||||||
|
/// earlier attempts, where each site threaded an `if (!is_raw)` guard
|
||||||
|
/// separately and one was forgotten (issue 0089).
|
||||||
|
fn checkBindingName(self: UnknownTypeChecker, name: []const u8, span: ?ast.Span, is_raw: bool) void {
|
||||||
|
if (is_raw) return;
|
||||||
if (isReservedTypeName(name))
|
if (isReservedTypeName(name))
|
||||||
self.diagnostics.addFmt(.err, span, "'{s}' is a reserved type name and cannot be used as an identifier", .{name});
|
self.diagnostics.addFmt(.err, span, "'{s}' is a reserved type name and cannot be used as an identifier", .{name});
|
||||||
}
|
}
|
||||||
@@ -798,12 +801,14 @@ pub const UnknownTypeChecker = struct {
|
|||||||
/// identifier but carries no dedicated `name_span` field — struct / enum /
|
/// identifier but carries no dedicated `name_span` field — struct / enum /
|
||||||
/// union / error-set / protocol / foreign-class type decls, ufcs aliases,
|
/// union / error-set / protocol / foreign-class type decls, ufcs aliases,
|
||||||
/// and namespaced imports (issue 0089). Each such node begins at its name
|
/// and namespaced imports (issue 0089). Each such node begins at its name
|
||||||
/// token, so the name's length isolates the caret onto the name. A
|
/// token (`createNode(name_start, …)`), so the name's length isolates the
|
||||||
/// backtick raw / `#import c` foreign name never reaches here (those forms
|
/// caret onto the name — a single source for the span, no separate stored
|
||||||
/// are exempt at their own decl path).
|
/// field to drift from `node.span`. `is_raw` is REQUIRED, exactly as in
|
||||||
fn checkDeclName(self: UnknownTypeChecker, node: *const Node, name: []const u8) void {
|
/// `checkBindingName`: a backtick raw / `#import c` foreign name is exempt
|
||||||
|
/// by construction.
|
||||||
|
fn checkDeclName(self: UnknownTypeChecker, node: *const Node, name: []const u8, is_raw: bool) void {
|
||||||
const span = ast.Span{ .start = node.span.start, .end = node.span.start + @as(u32, @intCast(name.len)) };
|
const span = ast.Span{ .start = node.span.start, .end = node.span.start + @as(u32, @intCast(name.len)) };
|
||||||
self.checkBindingName(name, span);
|
self.checkBindingName(name, span, is_raw);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
|||||||
const n = node orelse return .unresolved;
|
const n = node orelse return .unresolved;
|
||||||
const si = StatelessInner{ .table = table, .alias_map = alias_map, .consts = consts };
|
const si = StatelessInner{ .table = table, .alias_map = alias_map, .consts = consts };
|
||||||
return switch (n.data) {
|
return switch (n.data) {
|
||||||
.type_expr => |te| resolveTypeName(te.name, table, alias_map),
|
.type_expr => |te| resolveTypeName(te.name, table, alias_map, te.is_raw),
|
||||||
.identifier => |id| resolveTypeName(id.name, table, alias_map),
|
.identifier => |id| resolveTypeName(id.name, table, alias_map, id.is_raw),
|
||||||
// Structural shapes (`*T`/`[*]T`/`[]T`/`?T`/`[N]T`, functions, plain
|
// Structural shapes (`*T`/`[*]T`/`[]T`/`?T`/`[N]T`, functions, plain
|
||||||
// closures, plain tuples) are owned by the single canonical
|
// closures, plain tuples) are owned by the single canonical
|
||||||
// `TypeResolver.resolveCompound` — no independent compound algorithm
|
// `TypeResolver.resolveCompound` — no independent compound algorithm
|
||||||
@@ -174,8 +174,9 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
|||||||
/// Resolve a bare type name. The algorithm lives in `type_resolver.zig`
|
/// Resolve a bare type name. The algorithm lives in `type_resolver.zig`
|
||||||
/// (`TypeResolver.resolveNamed`, the single source); `type_bridge` forwards the
|
/// (`TypeResolver.resolveNamed`, the single source); `type_bridge` forwards the
|
||||||
/// caller-threaded `alias_map` (the single-source `ProgramIndex.type_alias_map`).
|
/// caller-threaded `alias_map` (the single-source `ProgramIndex.type_alias_map`).
|
||||||
fn resolveTypeName(name: []const u8, table: *TypeTable, alias_map: AliasMap) TypeId {
|
/// `skip_builtin` carries the backtick raw escape (issue 0089).
|
||||||
return type_resolver.TypeResolver.resolveNamed(name, table, alias_map);
|
fn resolveTypeName(name: []const u8, table: *TypeTable, alias_map: AliasMap, skip_builtin: bool) TypeId {
|
||||||
|
return type_resolver.TypeResolver.resolveNamed(name, table, alias_map, skip_builtin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builtin primitive keyword → TypeId. The keyword table now lives in
|
/// Builtin primitive keyword → TypeId. The keyword table now lives in
|
||||||
@@ -535,7 +536,7 @@ fn resolveInlineErrorSet(esd: *const ast.ErrorSetDecl, table: *TypeTable) TypeId
|
|||||||
/// resolves to the same empty inferred set, which is correct while no
|
/// resolves to the same empty inferred set, which is correct while no
|
||||||
/// function raises (E1.3+).
|
/// function raises (E1.3+).
|
||||||
fn resolveErrorType(ete: *const ast.ErrorTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
fn resolveErrorType(ete: *const ast.ErrorTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||||
if (ete.name) |name| return resolveTypeName(name, table, alias_map);
|
if (ete.name) |name| return resolveTypeName(name, table, alias_map, false);
|
||||||
// `!` is not a legal type/identifier name, so this reserved StringId can
|
// `!` is not a legal type/identifier name, so this reserved StringId can
|
||||||
// never collide with a user-declared set.
|
// never collide with a user-declared set.
|
||||||
const name_id = table.internString("!");
|
const name_id = table.internString("!");
|
||||||
|
|||||||
@@ -144,21 +144,33 @@ test "TypeResolver.resolveName resolves aliases via ProgramIndex (not the TypeTa
|
|||||||
try index.type_alias_map.put("NodeRef", ptr_s64); // alias → pointer
|
try index.type_alias_map.put("NodeRef", ptr_s64); // alias → pointer
|
||||||
const tr = TypeResolver{ .alloc = alloc, .types = &table, .diagnostics = null, .index = &index };
|
const tr = TypeResolver{ .alloc = alloc, .types = &table, .diagnostics = null, .index = &index };
|
||||||
|
|
||||||
try std.testing.expectEqual(@as(TypeId, .u32), tr.resolveName("ShaderHandle"));
|
try std.testing.expectEqual(@as(TypeId, .u32), tr.resolveName("ShaderHandle", false));
|
||||||
try std.testing.expectEqual(ptr_s64, tr.resolveName("NodeRef"));
|
try std.testing.expectEqual(ptr_s64, tr.resolveName("NodeRef", false));
|
||||||
// Primitive is checked before alias.
|
// Primitive is checked before alias.
|
||||||
try std.testing.expectEqual(@as(TypeId, .s64), tr.resolveName("s64"));
|
try std.testing.expectEqual(@as(TypeId, .s64), tr.resolveName("s64", false));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "TypeResolver.resolveNamed: width-int, string-prefix, unknown→stub" {
|
test "TypeResolver.resolveNamed: width-int, string-prefix, unknown→stub" {
|
||||||
const alloc = std.testing.allocator;
|
const alloc = std.testing.allocator;
|
||||||
var table = TypeTable.init(alloc);
|
var table = TypeTable.init(alloc);
|
||||||
defer table.deinit();
|
defer table.deinit();
|
||||||
try std.testing.expectEqual(table.intern(.{ .signed = 7 }), TypeResolver.resolveNamed("s7", &table, null));
|
try std.testing.expectEqual(table.intern(.{ .signed = 7 }), TypeResolver.resolveNamed("s7", &table, null, false));
|
||||||
try std.testing.expectEqual(table.ptrTo(.s64), TypeResolver.resolveNamed("*s64", &table, null));
|
try std.testing.expectEqual(table.ptrTo(.s64), TypeResolver.resolveNamed("*s64", &table, null, false));
|
||||||
// Unknown name, no alias map → empty-struct stub (preserved behavior;
|
// Unknown name, no alias map → empty-struct stub (preserved behavior;
|
||||||
// never `.unresolved`, which is reserved for failed *generic* resolution).
|
// never `.unresolved`, which is reserved for failed *generic* resolution).
|
||||||
try std.testing.expect(TypeResolver.resolveNamed("Unknown", &table, null) != .unresolved);
|
try std.testing.expect(TypeResolver.resolveNamed("Unknown", &table, null, false) != .unresolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "TypeResolver.resolveNamed: skip_builtin resolves a raw reserved-name type, not the builtin" {
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
var table = TypeTable.init(alloc);
|
||||||
|
defer table.deinit();
|
||||||
|
// A registered user type named "s2" (a reserved int spelling).
|
||||||
|
const name_id = table.internString("s2");
|
||||||
|
const user_s2 = table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } });
|
||||||
|
// Bare lookup → the builtin 2-bit signed int; raw lookup → the user type.
|
||||||
|
try std.testing.expectEqual(table.intern(.{ .signed = 2 }), TypeResolver.resolveNamed("s2", &table, null, false));
|
||||||
|
try std.testing.expectEqual(user_s2, TypeResolver.resolveNamed("s2", &table, null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "TypeResolver.parseWidthInt: every width 1..64, both signs; rejects out-of-range / non-int" {
|
test "TypeResolver.parseWidthInt: every width 1..64, both signs; rejects out-of-range / non-int" {
|
||||||
|
|||||||
@@ -244,11 +244,21 @@ pub const TypeResolver = struct {
|
|||||||
/// `type_bridge` via the alias map threaded through `resolveAstType`. The
|
/// `type_bridge` via the alias map threaded through `resolveAstType`. The
|
||||||
/// stub fall-through preserves long-standing behavior for as-yet-
|
/// stub fall-through preserves long-standing behavior for as-yet-
|
||||||
/// unregistered names.
|
/// unregistered names.
|
||||||
pub fn resolveNamed(name: []const u8, table: *TypeTable, alias_map: ?*const std.StringHashMap(TypeId)) TypeId {
|
///
|
||||||
|
/// `skip_builtin` is the backtick raw-identifier escape (`` `s2 `` in type
|
||||||
|
/// position, issue 0089): a raw reference is the LITERAL name used as a
|
||||||
|
/// type, so it bypasses the builtin/reserved classifier and resolves only
|
||||||
|
/// through registered-type → alias → stub. A bare `s2` keeps the default
|
||||||
|
/// (`false`) and resolves to the builtin int type. The string-prefix
|
||||||
|
/// recursion always passes `false`: the inner names (`*T`/`?T`) are bare,
|
||||||
|
/// never raw.
|
||||||
|
pub fn resolveNamed(name: []const u8, table: *TypeTable, alias_map: ?*const std.StringHashMap(TypeId), skip_builtin: bool) TypeId {
|
||||||
// Builtin primitive keyword or arbitrary-width integer (`s1`-`s64`,
|
// Builtin primitive keyword or arbitrary-width integer (`s1`-`s64`,
|
||||||
// `u1`-`u64`) — the single builtin classifier, also reused by the
|
// `u1`-`u64`) — the single builtin classifier, also reused by the
|
||||||
// numeric-limit accessor intercept.
|
// numeric-limit accessor intercept.
|
||||||
if (resolveBuiltinName(name, table)) |id| return id;
|
if (!skip_builtin) {
|
||||||
|
if (resolveBuiltinName(name, table)) |id| return id;
|
||||||
|
}
|
||||||
// Sentinel-terminated slice: [:0]u8 → string.
|
// Sentinel-terminated slice: [:0]u8 → string.
|
||||||
if (name.len >= 5 and name[0] == '[' and name[1] == ':') {
|
if (name.len >= 5 and name[0] == '[' and name[1] == ':') {
|
||||||
if (std.mem.indexOfScalar(u8, name, ']')) |close| {
|
if (std.mem.indexOfScalar(u8, name, ']')) |close| {
|
||||||
@@ -259,15 +269,15 @@ pub const TypeResolver = struct {
|
|||||||
}
|
}
|
||||||
// Many-pointer: [*]T.
|
// Many-pointer: [*]T.
|
||||||
if (name.len >= 4 and name[0] == '[' and name[1] == '*' and name[2] == ']') {
|
if (name.len >= 4 and name[0] == '[' and name[1] == '*' and name[2] == ']') {
|
||||||
return table.manyPtrTo(resolveNamed(name[3..], table, alias_map));
|
return table.manyPtrTo(resolveNamed(name[3..], table, alias_map, false));
|
||||||
}
|
}
|
||||||
// Pointer: *T.
|
// Pointer: *T.
|
||||||
if (name.len >= 2 and name[0] == '*') {
|
if (name.len >= 2 and name[0] == '*') {
|
||||||
return table.ptrTo(resolveNamed(name[1..], table, alias_map));
|
return table.ptrTo(resolveNamed(name[1..], table, alias_map, false));
|
||||||
}
|
}
|
||||||
// Optional: ?T.
|
// Optional: ?T.
|
||||||
if (name.len >= 2 and name[0] == '?') {
|
if (name.len >= 2 and name[0] == '?') {
|
||||||
return table.optionalOf(resolveNamed(name[1..], table, alias_map));
|
return table.optionalOf(resolveNamed(name[1..], table, alias_map, false));
|
||||||
}
|
}
|
||||||
// Named struct/enum/union — already-registered wins, then alias, then
|
// Named struct/enum/union — already-registered wins, then alias, then
|
||||||
// a fresh empty-struct stub for an as-yet-unregistered name.
|
// a fresh empty-struct stub for an as-yet-unregistered name.
|
||||||
@@ -280,8 +290,9 @@ pub const TypeResolver = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a bare type name through the canonical alias source
|
/// Resolve a bare type name through the canonical alias source
|
||||||
/// (`ProgramIndex.type_alias_map`).
|
/// (`ProgramIndex.type_alias_map`). `skip_builtin` carries the backtick raw
|
||||||
pub fn resolveName(self: TypeResolver, name: []const u8) TypeId {
|
/// escape (issue 0089) — see `resolveNamed`.
|
||||||
return resolveNamed(name, self.types, &self.index.type_alias_map);
|
pub fn resolveName(self: TypeResolver, name: []const u8, skip_builtin: bool) TypeId {
|
||||||
|
return resolveNamed(name, self.types, &self.index.type_alias_map, skip_builtin);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ pub const Parser = struct {
|
|||||||
// Check for #import c { ... } (C import block)
|
// Check for #import c { ... } (C import block)
|
||||||
if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "c") and self.peekNext() == .l_brace) {
|
if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "c") and self.peekNext() == .l_brace) {
|
||||||
self.advance(); // consume 'c'
|
self.advance(); // consume 'c'
|
||||||
return self.parseCImportBlock(start, null);
|
return self.parseCImportBlock(start, null, false);
|
||||||
}
|
}
|
||||||
if (self.current.tag != .string_literal) {
|
if (self.current.tag != .string_literal) {
|
||||||
return self.fail("expected string path after '#import'");
|
return self.fail("expected string path after '#import'");
|
||||||
@@ -183,7 +183,7 @@ pub const Parser = struct {
|
|||||||
// Check for name :: #import c { ... }
|
// Check for name :: #import c { ... }
|
||||||
if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "c") and self.peekNext() == .l_brace) {
|
if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "c") and self.peekNext() == .l_brace) {
|
||||||
self.advance(); // consume 'c'
|
self.advance(); // consume 'c'
|
||||||
return self.parseCImportBlock(start_pos, name);
|
return self.parseCImportBlock(start_pos, name, name_is_raw);
|
||||||
}
|
}
|
||||||
if (self.current.tag != .string_literal) {
|
if (self.current.tag != .string_literal) {
|
||||||
return self.fail("expected string path after '#import'");
|
return self.fail("expected string path after '#import'");
|
||||||
@@ -192,7 +192,7 @@ pub const Parser = struct {
|
|||||||
const path = raw[1 .. raw.len - 1];
|
const path = raw[1 .. raw.len - 1];
|
||||||
self.advance();
|
self.advance();
|
||||||
try self.expect(.semicolon);
|
try self.expect(.semicolon);
|
||||||
return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name } });
|
return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name, .is_raw = name_is_raw } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Named library: name :: #library "libname";
|
// Named library: name :: #library "libname";
|
||||||
@@ -205,7 +205,7 @@ pub const Parser = struct {
|
|||||||
const lib_name = raw[1 .. raw.len - 1];
|
const lib_name = raw[1 .. raw.len - 1];
|
||||||
self.advance();
|
self.advance();
|
||||||
try self.expect(.semicolon);
|
try self.expect(.semicolon);
|
||||||
return try self.createNode(start_pos, .{ .library_decl = .{ .lib_name = lib_name, .name = name } });
|
return try self.createNode(start_pos, .{ .library_decl = .{ .lib_name = lib_name, .name = name, .is_raw = name_is_raw } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile-time evaluation: name :: #run expr;
|
// Compile-time evaluation: name :: #run expr;
|
||||||
@@ -229,22 +229,22 @@ pub const Parser = struct {
|
|||||||
|
|
||||||
// Enum declaration
|
// Enum declaration
|
||||||
if (self.current.tag == .kw_enum) {
|
if (self.current.tag == .kw_enum) {
|
||||||
return self.parseEnumDecl(name, start_pos);
|
return self.parseEnumDecl(name, start_pos, name_is_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error-set declaration: name :: error { TagA, TagB }
|
// Error-set declaration: name :: error { TagA, TagB }
|
||||||
if (self.current.tag == .kw_error) {
|
if (self.current.tag == .kw_error) {
|
||||||
return self.parseErrorSetDecl(name, start_pos);
|
return self.parseErrorSetDecl(name, start_pos, name_is_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Struct declaration
|
// Struct declaration
|
||||||
if (self.current.tag == .kw_struct) {
|
if (self.current.tag == .kw_struct) {
|
||||||
return self.parseStructDecl(name, start_pos);
|
return self.parseStructDecl(name, start_pos, name_is_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocol declaration
|
// Protocol declaration
|
||||||
if (self.current.tag == .kw_protocol) {
|
if (self.current.tag == .kw_protocol) {
|
||||||
return self.parseProtocolDecl(name, start_pos);
|
return self.parseProtocolDecl(name, start_pos, name_is_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Foreign-type binding with optional prefix modifiers:
|
// Foreign-type binding with optional prefix modifiers:
|
||||||
@@ -255,12 +255,12 @@ pub const Parser = struct {
|
|||||||
// `#foreign` flips that to "reference an existing class on the foreign side."
|
// `#foreign` flips that to "reference an existing class on the foreign side."
|
||||||
// `#jni_main` flags the class as the launchable entry (Android Activity).
|
// `#jni_main` flags the class as the launchable entry (Android Activity).
|
||||||
if (self.tryParseForeignClassPrefix()) |prefix| {
|
if (self.tryParseForeignClassPrefix()) |prefix| {
|
||||||
return self.parseForeignClassDecl(name, start_pos, prefix.runtime, prefix.is_foreign, prefix.is_main);
|
return self.parseForeignClassDecl(name, start_pos, prefix.runtime, prefix.is_foreign, prefix.is_main, name_is_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// C-style union declaration
|
// C-style union declaration
|
||||||
if (self.current.tag == .kw_union) {
|
if (self.current.tag == .kw_union) {
|
||||||
return self.parseUnionDecl(name, start_pos);
|
return self.parseUnionDecl(name, start_pos, name_is_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// UFCS alias: name :: ufcs target;
|
// UFCS alias: name :: ufcs target;
|
||||||
@@ -272,7 +272,7 @@ pub const Parser = struct {
|
|||||||
const target = self.tokenSlice(self.current);
|
const target = self.tokenSlice(self.current);
|
||||||
self.advance();
|
self.advance();
|
||||||
try self.expect(.semicolon);
|
try self.expect(.semicolon);
|
||||||
return try self.createNode(start_pos, .{ .ufcs_alias = .{ .name = name, .target = target } });
|
return try self.createNode(start_pos, .{ .ufcs_alias = .{ .name = name, .target = target, .is_raw = name_is_raw } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function declaration: (params) -> type { body } or () { body }
|
// Function declaration: (params) -> type { body } or () { body }
|
||||||
@@ -332,7 +332,7 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = value, .name_span = name_span, .is_raw = name_is_raw } });
|
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = value, .name_span = name_span, .is_raw = name_is_raw } });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseCImportBlock(self: *Parser, start: u32, name: ?[]const u8) anyerror!*Node {
|
fn parseCImportBlock(self: *Parser, start: u32, name: ?[]const u8, name_is_raw: bool) anyerror!*Node {
|
||||||
try self.expect(.l_brace);
|
try self.expect(.l_brace);
|
||||||
var includes = std.ArrayList([]const u8).empty;
|
var includes = std.ArrayList([]const u8).empty;
|
||||||
var sources = std.ArrayList([]const u8).empty;
|
var sources = std.ArrayList([]const u8).empty;
|
||||||
@@ -381,6 +381,7 @@ pub const Parser = struct {
|
|||||||
.defines = try defines.toOwnedSlice(self.allocator),
|
.defines = try defines.toOwnedSlice(self.allocator),
|
||||||
.flags = try flags.toOwnedSlice(self.allocator),
|
.flags = try flags.toOwnedSlice(self.allocator),
|
||||||
.name = name,
|
.name = name,
|
||||||
|
.is_raw = name_is_raw,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +395,7 @@ pub const Parser = struct {
|
|||||||
self.advance();
|
self.advance();
|
||||||
const value = try self.parseExpr();
|
const value = try self.parseExpr();
|
||||||
try self.expectSemicolonAfter(value);
|
try self.expectSemicolonAfter(value);
|
||||||
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = type_node, .value = value } });
|
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = type_node, .value = value, .name_span = name_span, .is_raw = name_is_raw } });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.current.tag == .equal) {
|
if (self.current.tag == .equal) {
|
||||||
@@ -629,11 +630,16 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self.current.tag.isTypeKeyword() or self.isIdentLike()) {
|
if (self.current.tag.isTypeKeyword() or self.isIdentLike()) {
|
||||||
// A backtick raw identifier (`` `s2 ``) is a VALUE-name escape; it is
|
// A backtick raw identifier (`` `s2 ``) in type position is the
|
||||||
// never a type. Reject it in type position rather than silently
|
// LITERAL name `s2` used as a type reference — never the builtin /
|
||||||
// type-classifying it (issue 0089).
|
// reserved keyword. It is always a plain named-type reference (no
|
||||||
|
// qualified-path, `Closure`, or parameterized continuation), so emit
|
||||||
|
// a raw `type_expr` and return; resolution skips the builtin
|
||||||
|
// classifier and looks up a `` `s2 ``-declared type (issue 0089).
|
||||||
if (self.current.is_raw) {
|
if (self.current.is_raw) {
|
||||||
return self.failFmt("`{s}` is a raw identifier, not a type — the backtick escape names a value, never a type", .{self.tokenSlice(self.current)});
|
const raw_name = self.tokenSlice(self.current);
|
||||||
|
self.advance();
|
||||||
|
return try self.createNode(start, .{ .type_expr = .{ .name = raw_name, .is_raw = true } });
|
||||||
}
|
}
|
||||||
var name = self.tokenSlice(self.current);
|
var name = self.tokenSlice(self.current);
|
||||||
self.advance();
|
self.advance();
|
||||||
@@ -787,20 +793,20 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
// Inline struct type in type position: struct { ... }
|
// Inline struct type in type position: struct { ... }
|
||||||
if (self.current.tag == .kw_struct) {
|
if (self.current.tag == .kw_struct) {
|
||||||
return try self.parseStructDecl("__anon", start);
|
return try self.parseStructDecl("__anon", start, false);
|
||||||
}
|
}
|
||||||
// Inline C-style union in type position: union { ... }
|
// Inline C-style union in type position: union { ... }
|
||||||
if (self.current.tag == .kw_union) {
|
if (self.current.tag == .kw_union) {
|
||||||
return try self.parseUnionDecl("__anon", start);
|
return try self.parseUnionDecl("__anon", start, false);
|
||||||
}
|
}
|
||||||
// Inline enum type in type position: enum { ... }
|
// Inline enum type in type position: enum { ... }
|
||||||
if (self.current.tag == .kw_enum) {
|
if (self.current.tag == .kw_enum) {
|
||||||
return try self.parseEnumDecl("__anon", start);
|
return try self.parseEnumDecl("__anon", start, false);
|
||||||
}
|
}
|
||||||
return self.fail("expected type name");
|
return self.fail("expected type name");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseEnumDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
fn parseEnumDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
|
||||||
self.advance(); // skip 'enum'
|
self.advance(); // skip 'enum'
|
||||||
|
|
||||||
// Check for 'flags' modifier: enum flags { ... }
|
// Check for 'flags' modifier: enum flags { ... }
|
||||||
@@ -874,10 +880,11 @@ pub const Parser = struct {
|
|||||||
.is_flags = is_flags,
|
.is_flags = is_flags,
|
||||||
.variant_values = if (has_any_value) try variant_values.toOwnedSlice(self.allocator) else &.{},
|
.variant_values = if (has_any_value) try variant_values.toOwnedSlice(self.allocator) else &.{},
|
||||||
.backing_type = backing_type,
|
.backing_type = backing_type,
|
||||||
|
.is_raw = name_is_raw,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseErrorSetDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
fn parseErrorSetDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
|
||||||
self.advance(); // skip 'error'
|
self.advance(); // skip 'error'
|
||||||
try self.expect(.l_brace);
|
try self.expect(.l_brace);
|
||||||
var tag_names = std.ArrayList([]const u8).empty;
|
var tag_names = std.ArrayList([]const u8).empty;
|
||||||
@@ -899,10 +906,11 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start_pos, .{ .error_set_decl = .{
|
return try self.createNode(start_pos, .{ .error_set_decl = .{
|
||||||
.name = name,
|
.name = name,
|
||||||
.tag_names = try tag_names.toOwnedSlice(self.allocator),
|
.tag_names = try tag_names.toOwnedSlice(self.allocator),
|
||||||
|
.is_raw = name_is_raw,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseUnionDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
fn parseUnionDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
|
||||||
self.advance(); // skip 'union'
|
self.advance(); // skip 'union'
|
||||||
try self.expect(.l_brace);
|
try self.expect(.l_brace);
|
||||||
var field_names = std.ArrayList([]const u8).empty;
|
var field_names = std.ArrayList([]const u8).empty;
|
||||||
@@ -914,7 +922,7 @@ pub const Parser = struct {
|
|||||||
const anon_field = try std.fmt.allocPrint(self.allocator, "__anon_{d}", .{anon_idx});
|
const anon_field = try std.fmt.allocPrint(self.allocator, "__anon_{d}", .{anon_idx});
|
||||||
anon_idx += 1;
|
anon_idx += 1;
|
||||||
const anon_struct_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ name, anon_field });
|
const anon_struct_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ name, anon_field });
|
||||||
const struct_node = try self.parseStructDecl(anon_struct_name, self.current.loc.start);
|
const struct_node = try self.parseStructDecl(anon_struct_name, self.current.loc.start, false);
|
||||||
try field_names.append(self.allocator, anon_field);
|
try field_names.append(self.allocator, anon_field);
|
||||||
try field_types.append(self.allocator, struct_node);
|
try field_types.append(self.allocator, struct_node);
|
||||||
if (self.current.tag == .semicolon) {
|
if (self.current.tag == .semicolon) {
|
||||||
@@ -942,10 +950,11 @@ pub const Parser = struct {
|
|||||||
.name = name,
|
.name = name,
|
||||||
.field_names = try field_names.toOwnedSlice(self.allocator),
|
.field_names = try field_names.toOwnedSlice(self.allocator),
|
||||||
.field_types = try field_types.toOwnedSlice(self.allocator),
|
.field_types = try field_types.toOwnedSlice(self.allocator),
|
||||||
|
.is_raw = name_is_raw,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseStructDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
fn parseStructDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
|
||||||
self.advance(); // skip 'struct'
|
self.advance(); // skip 'struct'
|
||||||
|
|
||||||
// Optional `#compiler` attribute: all methods inside this struct are
|
// Optional `#compiler` attribute: all methods inside this struct are
|
||||||
@@ -1133,10 +1142,11 @@ pub const Parser = struct {
|
|||||||
.using_entries = try using_entries.toOwnedSlice(self.allocator),
|
.using_entries = try using_entries.toOwnedSlice(self.allocator),
|
||||||
.methods = try methods.toOwnedSlice(self.allocator),
|
.methods = try methods.toOwnedSlice(self.allocator),
|
||||||
.constants = try constants.toOwnedSlice(self.allocator),
|
.constants = try constants.toOwnedSlice(self.allocator),
|
||||||
|
.is_raw = name_is_raw,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseProtocolDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node {
|
fn parseProtocolDecl(self: *Parser, name: []const u8, start_pos: u32, name_is_raw: bool) anyerror!*Node {
|
||||||
self.advance(); // skip 'protocol'
|
self.advance(); // skip 'protocol'
|
||||||
|
|
||||||
// Optional type params: protocol(Target: Type, U: Type) { ... }
|
// Optional type params: protocol(Target: Type, U: Type) { ... }
|
||||||
@@ -1249,6 +1259,7 @@ pub const Parser = struct {
|
|||||||
.methods = try methods.toOwnedSlice(self.allocator),
|
.methods = try methods.toOwnedSlice(self.allocator),
|
||||||
.is_inline = is_inline,
|
.is_inline = is_inline,
|
||||||
.type_params = try type_params.toOwnedSlice(self.allocator),
|
.type_params = try type_params.toOwnedSlice(self.allocator),
|
||||||
|
.is_raw = name_is_raw,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1335,7 +1346,7 @@ pub const Parser = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseForeignClassDecl(self: *Parser, name: []const u8, start_pos: u32, runtime: ast.ForeignRuntime, is_foreign: bool, is_main: bool) anyerror!*Node {
|
fn parseForeignClassDecl(self: *Parser, name: []const u8, start_pos: u32, runtime: ast.ForeignRuntime, is_foreign: bool, is_main: bool, name_is_raw: bool) anyerror!*Node {
|
||||||
self.advance(); // skip directive token
|
self.advance(); // skip directive token
|
||||||
|
|
||||||
try self.expect(.l_paren);
|
try self.expect(.l_paren);
|
||||||
@@ -1576,6 +1587,7 @@ pub const Parser = struct {
|
|||||||
.members = try members.toOwnedSlice(self.allocator),
|
.members = try members.toOwnedSlice(self.allocator),
|
||||||
.is_foreign = is_foreign,
|
.is_foreign = is_foreign,
|
||||||
.is_main = is_main,
|
.is_main = is_main,
|
||||||
|
.is_raw = name_is_raw,
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2820,15 +2832,15 @@ pub const Parser = struct {
|
|||||||
},
|
},
|
||||||
.kw_struct => {
|
.kw_struct => {
|
||||||
// Anonymous struct expression: struct { value: T; count: u32; }
|
// Anonymous struct expression: struct { value: T; count: u32; }
|
||||||
return try self.parseStructDecl("__anon", start);
|
return try self.parseStructDecl("__anon", start, false);
|
||||||
},
|
},
|
||||||
.kw_enum => {
|
.kw_enum => {
|
||||||
// Anonymous enum expression: enum { variant: T; other: u32; }
|
// Anonymous enum expression: enum { variant: T; other: u32; }
|
||||||
return try self.parseEnumDecl("__anon", start);
|
return try self.parseEnumDecl("__anon", start, false);
|
||||||
},
|
},
|
||||||
.kw_union => {
|
.kw_union => {
|
||||||
// Anonymous C-style union expression: union { f: f32; i: s32; }
|
// Anonymous C-style union expression: union { f: f32; i: s32; }
|
||||||
return try self.parseUnionDecl("__anon", start);
|
return try self.parseUnionDecl("__anon", start, false);
|
||||||
},
|
},
|
||||||
.kw_if => {
|
.kw_if => {
|
||||||
return self.parseIfExpr();
|
return self.parseIfExpr();
|
||||||
|
|||||||
Reference in New Issue
Block a user