feat(lang): backtick raw identifier in every binding form + raw-not-a-type + foreign reserved-name fn bare-call [F0.6]
Completes the issue-0089 backtick raw-identifier / `#import c` exemption across all remaining identifier positions and closes three boundary gaps the F0.6 review found. 1. Exhaustive raw-binding coverage. The `is_raw` bit now threads through `ast.Identifier` and EVERY binding/capture form — `IfExpr`/`WhileExpr` optional bindings, `ForExpr` capture + index, `MatchArm` capture, `CatchExpr`/`OnFailStmt` tag bindings, `DestructureDecl` per-name, and the protocol-default-body / foreign-class method param lists — not just `var_decl`/`param`. `UnknownTypeChecker` skips the reserved-name check at each arm when raw, so a backtick works in every identifier position while a bare reserved spelling still errors (issue 0076 preserved). 2. Raw identifier is never a type. `parseTypeExpr`'s atom rejects a raw identifier in type position (`x : `s2 = 1`, `List(`s2)`) with an accurate diagnostic instead of silently type-classifying it. 3. Reserved-name function bare-callable. A bare `s2(4)` parses its callee as a `.type_expr` (reserved spelling); `lowerCall` now rewrites a type_expr callee to an identifier when a function of that name is in scope, so a backtick-declared sx fn and a `#import c` foreign fn whose C name collides with a reserved type spelling both resolve by their bare name. (`TypeName(val)` is not a cast, so there is no ambiguity.) Tests: examples/0152 (every control-flow/capture form + bare ref/call/member access), examples/1054 (catch/onfail tag bindings), examples/1139 (raw in type position rejected), examples/1220 extended (foreign reserved-name function bare-call). 0076 negatives 1119/1121/1122/1123/1124/1125 stay green. Gate: zig build + zig build test + 422 examples pass. specs.md + readme.md updated; issues/0089 RESOLVED banner refreshed.
This commit is contained in:
57
examples/0152-types-backtick-control-flow.sx
Normal file
57
examples/0152-types-backtick-control-flow.sx
Normal file
@@ -0,0 +1,57 @@
|
||||
// Backtick raw identifier across every control-flow / capture / binding form,
|
||||
// plus bare later uses. A reserved type-name spelling (`s2`, `u8`, …) works as a
|
||||
// binding name in a destructure, an `if`/`while` optional binding, a `for`
|
||||
// capture + index, and a match-arm capture; a backtick-named function is
|
||||
// bare-callable; and a backtick struct field is bare- or backtick-accessible.
|
||||
// The escape is needed only at the binding site — a later BARE reference / call
|
||||
// / member access resolves to the binding. A *bare* binding name is still the
|
||||
// reserved type (see examples/1121), so the escape is the only way to spell
|
||||
// these as values.
|
||||
// Regression (issue 0089 — attempt-2 completeness across binding forms).
|
||||
#import "modules/std.sx";
|
||||
|
||||
pair :: () -> (s64, s64) { (1, 2) }
|
||||
maybe :: () -> ?s64 { return 42; }
|
||||
|
||||
// Function named with a reserved spelling — bare-callable (no backtick at call).
|
||||
`s2 :: (n: s64) -> s64 { return n + 1; }
|
||||
|
||||
Quad :: struct { `s1: s32; `s2: s32; }
|
||||
|
||||
main :: () -> s32 {
|
||||
// destructure binding names
|
||||
`u8, rest := pair();
|
||||
print("dstr = {} {}\n", `u8, rest);
|
||||
|
||||
// if optional binding + bare-position reference inside the branch
|
||||
if `s16 := maybe() {
|
||||
print("if = {}\n", `s16);
|
||||
}
|
||||
|
||||
// while optional binding (name only — the while binding isn't body-exposed)
|
||||
while `s32 := maybe() {
|
||||
break;
|
||||
}
|
||||
|
||||
// for capture + index names
|
||||
xs := [3]s64.{ 10, 20, 30 };
|
||||
for xs: (`bool, `u16) {
|
||||
print("for = {} @ {}\n", `bool, `u16);
|
||||
}
|
||||
|
||||
// match-arm capture
|
||||
opt: ?s64 = 5;
|
||||
m := if opt == {
|
||||
case .some: (`string) { `string * 2 }
|
||||
case .none: { 0 }
|
||||
};
|
||||
print("match = {}\n", m);
|
||||
|
||||
// backtick function called BARE and via backtick — both resolve to the fn
|
||||
print("call = {} {}\n", s2(10), `s2(10));
|
||||
|
||||
// struct field named with a reserved spelling: bare + backtick member access
|
||||
q := Quad.{ `s1 = 7, `s2 = 9 };
|
||||
print("field = {} {} | {} {}\n", q.s1, q.s2, q.`s1, q.`s2);
|
||||
return 0;
|
||||
}
|
||||
40
examples/1054-errors-backtick-reserved-binding.sx
Normal file
40
examples/1054-errors-backtick-reserved-binding.sx
Normal file
@@ -0,0 +1,40 @@
|
||||
// Backtick raw identifier as the error-tag binding of `catch` and `onfail`. A
|
||||
// reserved type-name spelling (`s2`, `u8`) is a value name when backticked, so
|
||||
// it is accepted as the tag binding and a later reference resolves to it. A
|
||||
// *bare* reserved spelling in the same position is still rejected (see
|
||||
// examples/1123), so the backtick escape is the only way to spell these tags.
|
||||
// Regression (issue 0089 — attempt-2 catch/onfail coverage).
|
||||
#import "modules/std.sx";
|
||||
|
||||
E :: error { Bad, Empty }
|
||||
|
||||
parse :: (n: s32) -> (s32, !E) {
|
||||
if n < 0 { raise error.Bad; }
|
||||
if n == 0 { raise error.Empty; }
|
||||
return n * 2;
|
||||
}
|
||||
|
||||
// `catch` tag binding spelled `s2`, referenced in the match body.
|
||||
classify :: (n: s32) -> s32 {
|
||||
return parse(n) catch `s2 == {
|
||||
case .Bad: 1;
|
||||
case .Empty: 2;
|
||||
else: 3
|
||||
};
|
||||
}
|
||||
|
||||
// `onfail` tag binding spelled `u8`, referenced in the cleanup body.
|
||||
cleanup :: (n: s32) -> !E {
|
||||
onfail `u8 { if `u8 == error.Bad { print("cleanup: bad\n"); } }
|
||||
if n < 0 { raise error.Bad; }
|
||||
return;
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
print("classify(-1) = {}\n", classify(-1));
|
||||
print("classify(0) = {}\n", classify(0));
|
||||
print("classify(5) = {}\n", classify(5));
|
||||
c := cleanup(-1);
|
||||
print("done\n");
|
||||
return 0;
|
||||
}
|
||||
12
examples/1139-diagnostics-backtick-raw-not-a-type.sx
Normal file
12
examples/1139-diagnostics-backtick-raw-not-a-type.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -7,3 +7,7 @@ int ffi_pick(int s1, int s2, int which) {
|
||||
int ffi_sum(int s1, int s2) {
|
||||
return s1 + s2;
|
||||
}
|
||||
|
||||
int s2(int u8) {
|
||||
return u8 + 100;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* Foreign C declarations whose parameter names (`s1`, `s2`) collide with
|
||||
sx's reserved signed-int type spellings. The `#import c` exemption must
|
||||
accept these generated names unedited (issue 0089). */
|
||||
/* Foreign C declarations whose names collide with sx's reserved type spellings.
|
||||
The `#import c` exemption must accept these generated names unedited, both as
|
||||
parameter names (`s1`, `s2`) and as a FUNCTION name (`s2`) — and a foreign
|
||||
reserved-name function must be bare-callable (issue 0089). */
|
||||
int ffi_pick(int s1, int s2, int which);
|
||||
int ffi_sum(int s1, int s2);
|
||||
int s2(int u8);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// `#import c` foreign-name exemption: a C header's parameter names `s1`/`s2`
|
||||
// collide with sx's reserved signed-int type spellings. Foreign decls are
|
||||
// treated as RAW — their names are never type-classified nor reserved-checked
|
||||
// — so the generated `#foreign` bindings import and call without hand-edits
|
||||
// (no backticks needed). Before issue 0089 this errored with "'s1' is a
|
||||
// reserved type name and cannot be used as an identifier".
|
||||
// `#import c` foreign-name exemption: C names that collide with sx's reserved
|
||||
// type spellings import unedited. Foreign decls are treated as RAW — their names
|
||||
// are never type-classified nor reserved-checked — so the generated `#foreign`
|
||||
// bindings import and call without hand-edits (no backticks needed). This covers
|
||||
// parameter names (`s1`/`s2`), a function whose own NAME is a reserved spelling
|
||||
// (`s2`), and bare-calling that function (its callee spelling parses as a type
|
||||
// but resolves to the foreign fn). Before issue 0089 the params errored with
|
||||
// "'s1' is a reserved type name and cannot be used as an identifier", and the
|
||||
// bare call errored with "unresolved 's2'".
|
||||
// Regression (issue 0089).
|
||||
#import "modules/std.sx";
|
||||
|
||||
@@ -16,5 +19,6 @@ main :: () -> s32 {
|
||||
print("pick(10,20,0) = {}\n", ffi_pick(10, 20, 0));
|
||||
print("pick(10,20,1) = {}\n", ffi_pick(10, 20, 1));
|
||||
print("sum(10,20) = {}\n", ffi_sum(10, 20));
|
||||
print("s2(4) bare = {}\n", s2(4));
|
||||
0
|
||||
}
|
||||
|
||||
1
examples/expected/0152-types-backtick-control-flow.exit
Normal file
1
examples/expected/0152-types-backtick-control-flow.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
dstr = 1 2
|
||||
if = 42
|
||||
for = 10 @ 0
|
||||
for = 20 @ 1
|
||||
for = 30 @ 2
|
||||
match = 10
|
||||
call = 11 11
|
||||
field = 7 9 | 7 9
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
classify(-1) = 1
|
||||
classify(0) = 2
|
||||
classify(5) = 10
|
||||
cleanup: bad
|
||||
done
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
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 @@
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pick(10,20,0) = 10
|
||||
pick(10,20,1) = 20
|
||||
sum(10,20) = 30
|
||||
s2(4) bare = 104
|
||||
|
||||
@@ -4,25 +4,41 @@
|
||||
>
|
||||
> 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
|
||||
> carrying a `Token.is_raw` flag ([src/lexer.zig], [src/token.zig]). A raw
|
||||
> identifier is NEVER type-classified — the parser skips `Type.fromName` for it
|
||||
> in expression position ([src/parser.zig] `parsePrimary`), so it is always a
|
||||
> value identifier. The 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.
|
||||
> value identifier. The `is_raw` flag threads through `ast.Identifier` and EVERY
|
||||
> binding/capture form ([src/ast.zig]): `VarDecl` / `Param` plus `IfExpr` /
|
||||
> `WhileExpr` optional bindings, `ForExpr` capture + index, `MatchArm` capture,
|
||||
> `CatchExpr` / `OnFailStmt` tag bindings, `DestructureDecl` per-name, and the
|
||||
> protocol-default-body / foreign-class method param lists. `UnknownTypeChecker`
|
||||
> skips the reserved-name check at each of those arms when raw
|
||||
> ([src/ir/semantic_diagnostics.zig]). The backtick works in every identifier
|
||||
> position (local, global, param, field, function name, struct member, later
|
||||
> reference, and all the control-flow/capture/binding forms).
|
||||
> 2. **`#import c` foreign-name exemption.** `c_import.zig` synthesizes foreign
|
||||
> `#foreign` decls with `Param.is_raw = true`, so generated C param names that
|
||||
> collide with reserved type names (`s1`, `s2`) import unedited.
|
||||
>
|
||||
> **Boundary rules.** A raw identifier is a value name and is NEVER a type: using
|
||||
> one in type position (`x : `s2 = 1`) is a clean parse error ([src/parser.zig]
|
||||
> `parseTypeExpr` atom). A reserved-spelled FUNCTION (backtick-declared or
|
||||
> `#import c` foreign) is bare-callable: `lowerCall` rewrites a `.type_expr` callee
|
||||
> to an identifier when a function of that name is in scope ([src/ir/lower.zig]),
|
||||
> so `s2(4)` resolves to the function (`TypeName(val)` is not a cast). A later BARE
|
||||
> reference in value position resolves to the binding; a bare `s2` in type position
|
||||
> is still the type.
|
||||
>
|
||||
> A *bare* reserved-name binding in sx still errors (issue 0076 preserved): the
|
||||
> `is_raw`-gated skip only fires for backtick / foreign names. Regression tests:
|
||||
> `examples/0151-types-backtick-raw-identifier.sx` (backtick, 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`.
|
||||
> `examples/0151-types-backtick-raw-identifier.sx` (backtick, decl positions),
|
||||
> `examples/0152-types-backtick-control-flow.sx` (every control-flow/capture form
|
||||
> + bare ref/call/member access), `examples/1054-errors-backtick-reserved-binding.sx`
|
||||
> (`catch`/`onfail` tag bindings), `examples/1220-ffi-c-import-reserved-name-params.{sx,h,c}`
|
||||
> (foreign param + function-name exemption, bare-callable foreign fn),
|
||||
> `examples/1139-diagnostics-backtick-raw-not-a-type.sx` (negative — raw in type
|
||||
> position), `examples/1119`/`1121`/`1123` (negative — bare reserved binding still
|
||||
> rejected across all forms). Backtick lexer unit tests in `src/lexer.zig`.
|
||||
>
|
||||
> The original report is preserved below.
|
||||
|
||||
|
||||
19
readme.md
19
readme.md
@@ -106,17 +106,26 @@ z : s32 = ---; // uninitialized
|
||||
```
|
||||
|
||||
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and can't be used
|
||||
as bare value identifiers. A leading backtick escapes one into a raw identifier — its
|
||||
text drops the backtick and it's never read as a type — so reserved spellings (and
|
||||
keywords) work as ordinary names:
|
||||
as bare value identifiers. A leading backtick at the **binding site** escapes one
|
||||
into a raw identifier — its text drops the backtick and it's never read as a type —
|
||||
so reserved spellings (and keywords) work as ordinary names. The backtick is needed
|
||||
only where the name is declared; a later bare reference in value position resolves
|
||||
to the binding, while a bare `s2` in type position is still the type. It works in
|
||||
every identifier position (local, global, parameter, field, function name, 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
|
||||
`s2 := 2.5; // value identifier "s2", distinct from the s2 type
|
||||
print("{}\n", `s2); // 2.5
|
||||
print("{}\n", `s2); // 2.5 (or bare `s2`)
|
||||
```
|
||||
|
||||
A raw identifier is a value name, never a type — `x : `s2 = 1` is an error.
|
||||
|
||||
Foreign declarations from `#import c { … }` are exempt automatically: C names that
|
||||
collide with reserved type names (e.g. `s1`, `s2`) import unedited.
|
||||
collide with reserved type names (e.g. `s1`, `s2`) import unedited, and a foreign
|
||||
reserved-name function is bare-callable by its C name.
|
||||
|
||||
### Structs
|
||||
|
||||
|
||||
38
specs.md
38
specs.md
@@ -29,32 +29,50 @@ s2 := 2.5; // ERROR: 's2' is a reserved type name and cannot be used as an ide
|
||||
|
||||
A leading backtick makes the following identifier **raw**: its text excludes the
|
||||
backtick and it is never type-classified, so a reserved-type-name spelling can be
|
||||
used as an ordinary value identifier. The backtick is required at every occurrence
|
||||
of that identifier (declaration and each reference); a *bare* `s2` is still the
|
||||
signed-int type.
|
||||
used as an ordinary value identifier. The backtick is required at the **binding
|
||||
site** — the declaration that introduces the name — to escape the reserved-name
|
||||
rule. A later reference is resolved by position: in **value** position a bare `s2`
|
||||
resolves to the binding; in **type** position a bare `s2` is still the signed-int
|
||||
type.
|
||||
|
||||
```sx
|
||||
`s2 := 2.5; // OK — value identifier "s2", distinct from the s2 type
|
||||
print("{}\n", `s2); // 2.5
|
||||
print("{}\n", `s2); // 2.5 (backtick reference)
|
||||
print("{}\n", s2); // 2.5 (bare reference, resolves to the binding)
|
||||
x : s2 = 3; // bare `s2` in TYPE position is still the s2 type
|
||||
```
|
||||
|
||||
The escape works in every identifier position — local, global, parameter, struct
|
||||
field, function name, and a later reference:
|
||||
A raw identifier is a value name and is **never a type**: using one in type
|
||||
position (`x : `s2 = 1`) is a parse error.
|
||||
|
||||
The escape works in **every identifier position** — local, global, parameter,
|
||||
struct field, function name, a later reference, and every control-flow / capture /
|
||||
binding form: a destructure name, an `if` / `while` optional binding, a `for`
|
||||
capture and index, a match-arm capture, and a `catch` / `onfail` tag binding:
|
||||
|
||||
```sx
|
||||
`u8 := 100; // global
|
||||
`s2 :: (`s1: s64) -> s64 { `s1 } // function name + parameter
|
||||
P :: struct { `s2: f64; } // struct field
|
||||
`u8, rest := pair(); // destructure name
|
||||
if `s16 := maybe() { } // optional binding
|
||||
for xs: (`bool, `u16) { } // for capture + index
|
||||
x catch `s2 { } // catch tag binding
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
an identifier with that text.
|
||||
|
||||
**`#import c` exemption.** Foreign declarations synthesized by an `#import c { … }`
|
||||
block are treated as raw automatically: a generated C parameter or name that
|
||||
collides with a reserved type name (e.g. `s1`, `s2`) imports unedited, with no
|
||||
backticks and no reserved-name error. The exemption is scoped to the foreign decls —
|
||||
it does not make a foreign `s2` usable as the sx `s2` type, nor relax the rule for
|
||||
block are treated as raw automatically: a generated C parameter or function name
|
||||
that collides with a reserved type name (e.g. `s1`, `s2`) imports unedited, with no
|
||||
backticks and no reserved-name error, and a foreign reserved-name function is
|
||||
bare-callable by its C name. The exemption is scoped to the foreign decls — it does
|
||||
not make a foreign `s2` usable as the sx `s2` type, nor relax the rule for
|
||||
hand-written sx code.
|
||||
|
||||
### Literals
|
||||
|
||||
37
src/ast.zig
37
src/ast.zig
@@ -188,6 +188,10 @@ pub const StringLiteral = struct {
|
||||
|
||||
pub const Identifier = struct {
|
||||
name: []const u8,
|
||||
/// True when written as a backtick raw identifier (`` `s2 ``). Carried so a
|
||||
/// destructure target (`` `s2, b := … ``) can be recognised as raw and
|
||||
/// exempted from the reserved-type-name binding check (issue 0089).
|
||||
is_raw: bool = false,
|
||||
};
|
||||
|
||||
pub const EnumLiteral = struct {
|
||||
@@ -277,6 +281,9 @@ pub const IfExpr = struct {
|
||||
is_comptime: bool = false, // true for `inline if` — compile-time branch elimination
|
||||
binding_name: ?[]const u8 = null, // for `if val := expr { ... }` optional binding
|
||||
binding_span: ?Span = null, // span of `binding_name` (set iff `binding_name` is)
|
||||
/// True when the optional binding was a backtick raw identifier
|
||||
/// (`` if `s2 := … ``) — exempt from the reserved-type-name check (issue 0089).
|
||||
binding_is_raw: bool = false,
|
||||
};
|
||||
|
||||
pub const MatchExpr = struct {
|
||||
@@ -291,6 +298,9 @@ pub const MatchArm = struct {
|
||||
is_break: bool,
|
||||
capture: ?[]const u8 = null, // payload binding name: case .variant: (name) { ... }
|
||||
capture_span: ?Span = null, // span of `capture` (set iff `capture` is)
|
||||
/// True when the capture was a backtick raw identifier
|
||||
/// (`` case .v: (`s2) ``) — exempt from the reserved-type-name check (issue 0089).
|
||||
capture_is_raw: bool = false,
|
||||
};
|
||||
|
||||
pub const ConstDecl = struct {
|
||||
@@ -341,6 +351,10 @@ pub const MultiAssign = struct {
|
||||
pub const DestructureDecl = struct {
|
||||
names: []const []const u8,
|
||||
name_spans: []const Span, // one per entry in `names`, same order
|
||||
/// One per entry in `names`, same order: true when that target was a
|
||||
/// backtick raw identifier (`` `s2, b := … ``) — exempt from the
|
||||
/// reserved-type-name binding check (issue 0089).
|
||||
name_is_raw: []const bool,
|
||||
value: *Node,
|
||||
};
|
||||
|
||||
@@ -462,6 +476,9 @@ pub const CatchExpr = struct {
|
||||
operand: *Node,
|
||||
binding: ?[]const u8 = null,
|
||||
binding_span: ?Span = null, // span of `binding` (set iff `binding` is)
|
||||
/// True when the binding was a backtick raw identifier
|
||||
/// (`` x catch `s2 { … } ``) — exempt from the reserved-type-name check (issue 0089).
|
||||
binding_is_raw: bool = false,
|
||||
body: *Node,
|
||||
is_match_body: bool = false,
|
||||
};
|
||||
@@ -472,6 +489,9 @@ pub const CatchExpr = struct {
|
||||
pub const OnFailStmt = struct {
|
||||
binding: ?[]const u8 = null,
|
||||
binding_span: ?Span = null, // span of `binding` (set iff `binding` is)
|
||||
/// True when the binding was a backtick raw identifier
|
||||
/// (`` onfail `s2 { … } ``) — exempt from the reserved-type-name check (issue 0089).
|
||||
binding_is_raw: bool = false,
|
||||
body: *Node,
|
||||
};
|
||||
|
||||
@@ -566,6 +586,9 @@ pub const WhileExpr = struct {
|
||||
body: *Node,
|
||||
binding_name: ?[]const u8 = null, // for `while val := expr { ... }` optional binding
|
||||
binding_span: ?Span = null, // span of `binding_name` (set iff `binding_name` is)
|
||||
/// True when the optional binding was a backtick raw identifier
|
||||
/// (`` while `s2 := … ``) — exempt from the reserved-type-name check (issue 0089).
|
||||
binding_is_raw: bool = false,
|
||||
};
|
||||
|
||||
pub const ForExpr = struct {
|
||||
@@ -573,8 +596,14 @@ pub const ForExpr = struct {
|
||||
body: *Node,
|
||||
capture_name: []const u8,
|
||||
capture_span: ?Span = null, // span of `capture_name` (null when omitted, e.g. `for 0..N { }`)
|
||||
/// True when `capture_name` was a backtick raw identifier
|
||||
/// (`` for xs: (`s2) ``) — exempt from the reserved-type-name check (issue 0089).
|
||||
capture_is_raw: bool = false,
|
||||
index_name: ?[]const u8 = null,
|
||||
index_span: ?Span = null, // span of `index_name` (set iff `index_name` is)
|
||||
/// True when `index_name` was a backtick raw identifier
|
||||
/// (`` for xs: (x, `s2) ``) — exempt from the reserved-type-name check (issue 0089).
|
||||
index_is_raw: bool = false,
|
||||
/// Range form `for start..end (i) { }`: `iterable` is the start, `range_end`
|
||||
/// the (exclusive) end. Null for the iterate-a-collection form
|
||||
/// (`for coll : (x) { }`). For the range form `capture_name` is the cursor
|
||||
@@ -663,6 +692,10 @@ pub const ProtocolMethodDecl = struct {
|
||||
params: []const *Node, // type_expr nodes for parameter types (excluding implicit self)
|
||||
param_names: []const []const u8, // parameter names (excluding implicit self)
|
||||
param_name_spans: []const Span = &.{}, // one per `param_names` entry; empty for synthesized methods
|
||||
/// One per `param_names` entry: true when written as a backtick raw
|
||||
/// identifier — exempt from the reserved-type-name check (issue 0089).
|
||||
/// Empty for synthesized methods (treated as all-false).
|
||||
param_name_is_raw: []const bool = &.{},
|
||||
return_type: ?*Node, // null = void return
|
||||
default_body: ?*Node, // null = required method, non-null = default implementation
|
||||
};
|
||||
@@ -689,6 +722,10 @@ pub const ForeignMethodDecl = struct {
|
||||
params: []const *Node, // type_expr nodes — first is `*Self` for instance methods
|
||||
param_names: []const []const u8,
|
||||
param_name_spans: []const Span = &.{}, // one per `param_names` entry; empty for synthesized methods
|
||||
/// One per `param_names` entry: true when written as a backtick raw
|
||||
/// identifier — exempt from the reserved-type-name check (issue 0089).
|
||||
/// Empty for synthesized methods (treated as all-false).
|
||||
param_name_is_raw: []const bool = &.{},
|
||||
return_type: ?*Node, // null = void
|
||||
is_static: bool = false, // true for `static name :: ...`
|
||||
jni_descriptor_override: ?[]const u8 = null, // `#jni_method_descriptor("(Sig)Ret")` — JNI runtime only
|
||||
|
||||
@@ -6621,10 +6621,30 @@ pub const Lowering = struct {
|
||||
// ── Calls ───────────────────────────────────────────────────────
|
||||
|
||||
fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
var c = c_in;
|
||||
// A bare reserved-type-name spelling in call position parses as a
|
||||
// `.type_expr` (e.g. `s2(4)`), but if a function of that name is in
|
||||
// scope — a backtick-declared sx fn or a `#import c` foreign fn whose C
|
||||
// name collides with a reserved type spelling — it is a CALL to that
|
||||
// function. `TypeName(val)` is not a cast (casts are `cast(T, val)`), so
|
||||
// there is no ambiguity. Rewrite the callee to an identifier so the
|
||||
// normal call machinery resolves it, symmetric to the bare-value
|
||||
// reference that already resolves via scope/globals (issue 0089).
|
||||
if (c.callee.data == .type_expr) {
|
||||
const tname = c.callee.data.type_expr.name;
|
||||
const is_fn = self.program_index.fn_ast_map.contains(tname) or
|
||||
(if (self.scope) |scope| scope.lookupFn(tname) != null else false);
|
||||
if (is_fn) {
|
||||
const id_node = self.alloc.create(Node) catch unreachable;
|
||||
id_node.* = .{ .span = c.callee.span, .data = .{ .identifier = .{ .name = tname, .is_raw = true } } };
|
||||
const rewritten = self.alloc.create(ast.Call) catch unreachable;
|
||||
rewritten.* = .{ .callee = id_node, .args = c.args };
|
||||
c = rewritten;
|
||||
}
|
||||
}
|
||||
// Expand default parameter values for bare identifier callees:
|
||||
// when the caller omits trailing positional args, fill them in
|
||||
// from the callee's `param: T = expr` declarations.
|
||||
var c = c_in;
|
||||
if (self.expandCallDefaults(c)) |expanded| c = expanded;
|
||||
// Check reflection builtins first (before lowering args — some args are type names, not values)
|
||||
if (c.callee.data == .identifier) {
|
||||
|
||||
@@ -121,7 +121,9 @@ pub const UnknownTypeChecker = struct {
|
||||
if (vd.value) |v| self.checkBindingNames(v);
|
||||
},
|
||||
.destructure_decl => |dd| {
|
||||
for (dd.names, dd.name_spans) |n, sp| self.checkBindingName(n, sp);
|
||||
for (dd.names, dd.name_spans, dd.name_is_raw) |n, sp, raw| {
|
||||
if (!raw) self.checkBindingName(n, sp);
|
||||
}
|
||||
self.checkBindingNames(dd.value);
|
||||
},
|
||||
.fn_decl => |fd| {
|
||||
@@ -137,19 +139,25 @@ pub const UnknownTypeChecker = struct {
|
||||
if (p.default_expr) |de| self.checkBindingNames(de);
|
||||
},
|
||||
.if_expr => |ie| {
|
||||
if (ie.binding_name) |bn| self.checkBindingName(bn, ie.binding_span);
|
||||
if (ie.binding_name) |bn| {
|
||||
if (!ie.binding_is_raw) self.checkBindingName(bn, ie.binding_span);
|
||||
}
|
||||
self.checkBindingNames(ie.condition);
|
||||
self.checkBindingNames(ie.then_branch);
|
||||
if (ie.else_branch) |e| self.checkBindingNames(e);
|
||||
},
|
||||
.while_expr => |we| {
|
||||
if (we.binding_name) |bn| self.checkBindingName(bn, we.binding_span);
|
||||
if (we.binding_name) |bn| {
|
||||
if (!we.binding_is_raw) self.checkBindingName(bn, we.binding_span);
|
||||
}
|
||||
self.checkBindingNames(we.condition);
|
||||
self.checkBindingNames(we.body);
|
||||
},
|
||||
.for_expr => |fe| {
|
||||
if (fe.capture_name.len != 0) self.checkBindingName(fe.capture_name, fe.capture_span);
|
||||
if (fe.index_name) |idx| self.checkBindingName(idx, fe.index_span);
|
||||
if (fe.capture_name.len != 0 and !fe.capture_is_raw) self.checkBindingName(fe.capture_name, fe.capture_span);
|
||||
if (fe.index_name) |idx| {
|
||||
if (!fe.index_is_raw) self.checkBindingName(idx, fe.index_span);
|
||||
}
|
||||
self.checkBindingNames(fe.iterable);
|
||||
if (fe.range_end) |re| self.checkBindingNames(re);
|
||||
self.checkBindingNames(fe.body);
|
||||
@@ -157,23 +165,31 @@ pub const UnknownTypeChecker = struct {
|
||||
.match_expr => |me| {
|
||||
self.checkBindingNames(me.subject);
|
||||
for (me.arms) |arm| {
|
||||
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span);
|
||||
if (arm.capture) |cap| {
|
||||
if (!arm.capture_is_raw) self.checkBindingName(cap, arm.capture_span);
|
||||
}
|
||||
if (arm.pattern) |p| self.checkBindingNames(p);
|
||||
self.checkBindingNames(arm.body);
|
||||
}
|
||||
},
|
||||
.match_arm => |arm| {
|
||||
if (arm.capture) |cap| self.checkBindingName(cap, arm.capture_span);
|
||||
if (arm.capture) |cap| {
|
||||
if (!arm.capture_is_raw) self.checkBindingName(cap, arm.capture_span);
|
||||
}
|
||||
if (arm.pattern) |p| self.checkBindingNames(p);
|
||||
self.checkBindingNames(arm.body);
|
||||
},
|
||||
.catch_expr => |ce| {
|
||||
if (ce.binding) |b| self.checkBindingName(b, ce.binding_span);
|
||||
if (ce.binding) |b| {
|
||||
if (!ce.binding_is_raw) self.checkBindingName(b, ce.binding_span);
|
||||
}
|
||||
self.checkBindingNames(ce.operand);
|
||||
self.checkBindingNames(ce.body);
|
||||
},
|
||||
.onfail_stmt => |os| {
|
||||
if (os.binding) |b| self.checkBindingName(b, os.binding_span);
|
||||
if (os.binding) |b| {
|
||||
if (!os.binding_is_raw) self.checkBindingName(b, os.binding_span);
|
||||
}
|
||||
self.checkBindingNames(os.body);
|
||||
},
|
||||
// impl / protocol-default / foreign-class method bodies: each
|
||||
@@ -183,13 +199,19 @@ pub const UnknownTypeChecker = struct {
|
||||
.impl_block => |ib| for (ib.methods) |m| self.checkBindingNames(m),
|
||||
.protocol_decl => |pd| for (pd.methods) |m| {
|
||||
if (m.default_body) |body| {
|
||||
for (m.param_names, m.param_name_spans) |pn, sp| self.checkBindingName(pn, sp);
|
||||
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;
|
||||
self.checkBindingName(pn, sp);
|
||||
}
|
||||
self.checkBindingNames(body);
|
||||
}
|
||||
},
|
||||
.foreign_class_decl => |fcd| for (fcd.members) |member| switch (member) {
|
||||
.method => |m| if (m.body) |body| {
|
||||
for (m.param_names, m.param_name_spans) |pn, sp| self.checkBindingName(pn, sp);
|
||||
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;
|
||||
self.checkBindingName(pn, sp);
|
||||
}
|
||||
self.checkBindingNames(body);
|
||||
},
|
||||
.field, .extends, .implements => {},
|
||||
|
||||
@@ -629,6 +629,12 @@ pub const Parser = struct {
|
||||
}
|
||||
|
||||
if (self.current.tag.isTypeKeyword() or self.isIdentLike()) {
|
||||
// A backtick raw identifier (`` `s2 ``) is a VALUE-name escape; it is
|
||||
// never a type. Reject it in type position rather than silently
|
||||
// type-classifying it (issue 0089).
|
||||
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)});
|
||||
}
|
||||
var name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
|
||||
@@ -1186,6 +1192,7 @@ pub const Parser = struct {
|
||||
var param_types = std.ArrayList(*Node).empty;
|
||||
var param_names = std.ArrayList([]const u8).empty;
|
||||
var param_name_spans = std.ArrayList(ast.Span).empty;
|
||||
var param_name_is_raw = std.ArrayList(bool).empty;
|
||||
|
||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||
if (param_types.items.len > 0) {
|
||||
@@ -1198,6 +1205,7 @@ pub const Parser = struct {
|
||||
}
|
||||
const pname = self.tokenSlice(self.current);
|
||||
try param_name_spans.append(self.allocator, .{ .start = self.current.loc.start, .end = self.current.loc.end });
|
||||
try param_name_is_raw.append(self.allocator, self.current.is_raw);
|
||||
self.advance();
|
||||
try self.expect(.colon);
|
||||
const ptype = try self.parseTypeExpr();
|
||||
@@ -1226,6 +1234,7 @@ pub const Parser = struct {
|
||||
.params = try param_types.toOwnedSlice(self.allocator),
|
||||
.param_names = try param_names.toOwnedSlice(self.allocator),
|
||||
.param_name_spans = try param_name_spans.toOwnedSlice(self.allocator),
|
||||
.param_name_is_raw = try param_name_is_raw.toOwnedSlice(self.allocator),
|
||||
.return_type = return_type,
|
||||
.default_body = default_body,
|
||||
});
|
||||
@@ -1454,6 +1463,7 @@ pub const Parser = struct {
|
||||
var param_types = std.ArrayList(*Node).empty;
|
||||
var param_names = std.ArrayList([]const u8).empty;
|
||||
var param_name_spans = std.ArrayList(ast.Span).empty;
|
||||
var param_name_is_raw = std.ArrayList(bool).empty;
|
||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||
if (param_types.items.len > 0) {
|
||||
try self.expect(.comma);
|
||||
@@ -1464,6 +1474,7 @@ pub const Parser = struct {
|
||||
}
|
||||
const pname = self.tokenSlice(self.current);
|
||||
try param_name_spans.append(self.allocator, .{ .start = self.current.loc.start, .end = self.current.loc.end });
|
||||
try param_name_is_raw.append(self.allocator, self.current.is_raw);
|
||||
self.advance();
|
||||
try self.expect(.colon);
|
||||
const ptype = try self.parseTypeExpr();
|
||||
@@ -1546,6 +1557,7 @@ pub const Parser = struct {
|
||||
.params = try param_types.toOwnedSlice(self.allocator),
|
||||
.param_names = try param_names.toOwnedSlice(self.allocator),
|
||||
.param_name_spans = try param_name_spans.toOwnedSlice(self.allocator),
|
||||
.param_name_is_raw = try param_name_is_raw.toOwnedSlice(self.allocator),
|
||||
.return_type = return_type,
|
||||
.is_static = is_static,
|
||||
.jni_descriptor_override = desc_override,
|
||||
@@ -2046,7 +2058,7 @@ pub const Parser = struct {
|
||||
|
||||
// Multi-target assignment: ident, expr, ... = expr, expr, ...;
|
||||
if (self.current.tag == .comma) {
|
||||
const first_target = try self.createNode(start, .{ .identifier = .{ .name = name } });
|
||||
const first_target = try self.createNode(start, .{ .identifier = .{ .name = name, .is_raw = name_is_raw } });
|
||||
return try self.parseMultiAssign(first_target, start);
|
||||
}
|
||||
|
||||
@@ -2056,7 +2068,7 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
const value = try self.parseExpr();
|
||||
try self.expect(.semicolon);
|
||||
const target = try self.createNode(start, .{ .identifier = .{ .name = name } });
|
||||
const target = try self.createNode(start, .{ .identifier = .{ .name = name, .is_raw = name_is_raw } });
|
||||
return try self.createNode(start, .{ .assignment = .{ .target = target, .op = op, .value = value } });
|
||||
}
|
||||
|
||||
@@ -2123,9 +2135,11 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
var binding: ?[]const u8 = null;
|
||||
var binding_span: ?ast.Span = null;
|
||||
var binding_is_raw = false;
|
||||
if (self.current.tag == .identifier and self.peekNext() == .l_brace) {
|
||||
binding = self.tokenSlice(self.current);
|
||||
binding_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
binding_is_raw = self.current.is_raw;
|
||||
self.advance();
|
||||
}
|
||||
const saved_onfail = self.in_onfail_body;
|
||||
@@ -2138,7 +2152,7 @@ pub const Parser = struct {
|
||||
try self.expect(.semicolon);
|
||||
break :blk e;
|
||||
};
|
||||
return try self.createNode(start, .{ .onfail_stmt = .{ .binding = binding, .binding_span = binding_span, .body = body } });
|
||||
return try self.createNode(start, .{ .onfail_stmt = .{ .binding = binding, .binding_span = binding_span, .binding_is_raw = binding_is_raw, .body = body } });
|
||||
}
|
||||
|
||||
// Break statement: break;
|
||||
@@ -2570,9 +2584,11 @@ pub const Parser = struct {
|
||||
self.advance(); // consume 'catch'
|
||||
var binding: ?[]const u8 = null;
|
||||
var binding_span: ?ast.Span = null;
|
||||
var binding_is_raw = false;
|
||||
if (self.current.tag == .identifier) {
|
||||
binding = self.tokenSlice(self.current);
|
||||
binding_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
binding_is_raw = self.current.is_raw;
|
||||
self.advance();
|
||||
}
|
||||
var is_match_body = false;
|
||||
@@ -2582,7 +2598,7 @@ pub const Parser = struct {
|
||||
const m_start = self.current.loc.start;
|
||||
self.advance(); // consume '=='
|
||||
is_match_body = true;
|
||||
const subject = try self.createNode(m_start, .{ .identifier = .{ .name = binding.? } });
|
||||
const subject = try self.createNode(m_start, .{ .identifier = .{ .name = binding.?, .is_raw = binding_is_raw } });
|
||||
break :blk try self.parseMatchBody(subject, m_start);
|
||||
} else if (binding != null)
|
||||
try self.parseExpr()
|
||||
@@ -2592,6 +2608,7 @@ pub const Parser = struct {
|
||||
.operand = expr,
|
||||
.binding = binding,
|
||||
.binding_span = binding_span,
|
||||
.binding_is_raw = binding_is_raw,
|
||||
.body = body,
|
||||
.is_match_body = is_match_body,
|
||||
} });
|
||||
@@ -2690,16 +2707,17 @@ pub const Parser = struct {
|
||||
},
|
||||
.identifier => {
|
||||
const name = self.tokenSlice(self.current);
|
||||
const is_raw = self.current.is_raw;
|
||||
// A backtick raw identifier (`` `s2 ``) is NEVER type-classified —
|
||||
// it is always a value identifier, bypassing the reserved-type-name
|
||||
// rule (issue 0089). Only a bare spelling is checked for a type name
|
||||
// (e.g. s32, u8, s128).
|
||||
if (!self.current.is_raw and Type.fromName(name) != null) {
|
||||
if (!is_raw and Type.fromName(name) != null) {
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .type_expr = .{ .name = name } });
|
||||
}
|
||||
self.advance();
|
||||
return try self.createNode(start, .{ .identifier = .{ .name = name } });
|
||||
return try self.createNode(start, .{ .identifier = .{ .name = name, .is_raw = is_raw } });
|
||||
},
|
||||
.kw_closure, .kw_protocol, .kw_impl, .kw_ufcs => {
|
||||
// Contextual keywords used as identifiers in expressions
|
||||
@@ -2943,6 +2961,7 @@ pub const Parser = struct {
|
||||
if (self.current.tag == .identifier and self.peekNext() == .colon_equal) {
|
||||
const binding_name = self.tokenSlice(self.current);
|
||||
const binding_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
const binding_is_raw = self.current.is_raw;
|
||||
self.advance(); // skip identifier
|
||||
self.advance(); // skip :=
|
||||
const source_expr = try self.parseExpr();
|
||||
@@ -2963,6 +2982,7 @@ pub const Parser = struct {
|
||||
.is_inline = false,
|
||||
.binding_name = binding_name,
|
||||
.binding_span = binding_span,
|
||||
.binding_is_raw = binding_is_raw,
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -3065,6 +3085,7 @@ pub const Parser = struct {
|
||||
if (self.current.tag == .identifier and self.peekNext() == .colon_equal) {
|
||||
const binding_name = self.tokenSlice(self.current);
|
||||
const binding_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
const binding_is_raw = self.current.is_raw;
|
||||
self.advance(); // skip identifier
|
||||
self.advance(); // skip :=
|
||||
const source_expr = try self.parseExpr();
|
||||
@@ -3074,6 +3095,7 @@ pub const Parser = struct {
|
||||
.body = body,
|
||||
.binding_name = binding_name,
|
||||
.binding_span = binding_span,
|
||||
.binding_is_raw = binding_is_raw,
|
||||
} });
|
||||
}
|
||||
|
||||
@@ -3128,8 +3150,10 @@ pub const Parser = struct {
|
||||
|
||||
var capture_name: []const u8 = "";
|
||||
var capture_span: ?ast.Span = null;
|
||||
var capture_is_raw = false;
|
||||
var index_name: ?[]const u8 = null;
|
||||
var index_span: ?ast.Span = null;
|
||||
var index_is_raw = false;
|
||||
var capture_by_ref = false;
|
||||
|
||||
if (range_end != null) {
|
||||
@@ -3142,6 +3166,7 @@ pub const Parser = struct {
|
||||
if (self.current.tag != .identifier) return self.fail("expected cursor variable name");
|
||||
capture_name = self.tokenSlice(self.current);
|
||||
capture_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
capture_is_raw = self.current.is_raw;
|
||||
self.advance();
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
@@ -3157,12 +3182,14 @@ pub const Parser = struct {
|
||||
if (self.current.tag != .identifier) return self.fail("expected capture variable name");
|
||||
capture_name = self.tokenSlice(self.current);
|
||||
capture_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
capture_is_raw = self.current.is_raw;
|
||||
self.advance();
|
||||
if (self.current.tag == .comma) {
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) return self.fail("expected index variable name");
|
||||
index_name = self.tokenSlice(self.current);
|
||||
index_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
index_is_raw = self.current.is_raw;
|
||||
self.advance();
|
||||
}
|
||||
try self.expect(.r_paren);
|
||||
@@ -3175,8 +3202,10 @@ pub const Parser = struct {
|
||||
.body = body,
|
||||
.capture_name = capture_name,
|
||||
.capture_span = capture_span,
|
||||
.capture_is_raw = capture_is_raw,
|
||||
.index_name = index_name,
|
||||
.index_span = index_span,
|
||||
.index_is_raw = index_is_raw,
|
||||
.range_end = range_end,
|
||||
.capture_by_ref = capture_by_ref,
|
||||
} });
|
||||
@@ -3202,10 +3231,12 @@ pub const Parser = struct {
|
||||
// arm body (an expression) and is left for the body parse below.
|
||||
var capture: ?[]const u8 = null;
|
||||
var capture_span: ?ast.Span = null;
|
||||
var capture_is_raw = false;
|
||||
if (self.current.tag == .l_paren and self.isLoneIdentParen()) {
|
||||
self.advance(); // '('
|
||||
capture = self.tokenSlice(self.current);
|
||||
capture_span = .{ .start = self.current.loc.start, .end = self.current.loc.end };
|
||||
capture_is_raw = self.current.is_raw;
|
||||
self.advance(); // ident
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
@@ -3214,7 +3245,7 @@ pub const Parser = struct {
|
||||
self.advance();
|
||||
try self.expect(.semicolon);
|
||||
const body = try self.createNode(arm_start, .{ .block = .{ .stmts = &.{} } });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = true, .capture = capture, .capture_span = capture_span });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = true, .capture = capture, .capture_span = capture_span, .capture_is_raw = capture_is_raw });
|
||||
} else if (self.current.tag == .fat_arrow) {
|
||||
// Short form: (ident) => expr;
|
||||
self.advance();
|
||||
@@ -3224,7 +3255,7 @@ pub const Parser = struct {
|
||||
// `;` is an arm terminator, not a value-discard — match arms are
|
||||
// exempt from the block trailing-`;` rule).
|
||||
const body = try self.createNode(arm_start, .{ .block = .{ .stmts = try self.allocator.dupe(*Node, &.{expr}), .produces_value = true } });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture, .capture_span = capture_span });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture, .capture_span = capture_span, .capture_is_raw = capture_is_raw });
|
||||
} else {
|
||||
const stmts_start = self.current.loc.start;
|
||||
var stmts = std.ArrayList(*Node).empty;
|
||||
@@ -3235,7 +3266,7 @@ pub const Parser = struct {
|
||||
// yields its last statement's value — which, for a braced-block
|
||||
// arm body, still respects that inner block's own flag.
|
||||
const body = try self.createNode(stmts_start, .{ .block = .{ .stmts = try stmts.toOwnedSlice(self.allocator), .produces_value = true } });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture, .capture_span = capture_span });
|
||||
try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false, .capture = capture, .capture_span = capture_span, .capture_is_raw = capture_is_raw });
|
||||
}
|
||||
}
|
||||
// Optional else arm (default)
|
||||
@@ -3597,18 +3628,21 @@ pub const Parser = struct {
|
||||
// All targets must be plain identifiers
|
||||
var names = std.ArrayList([]const u8).empty;
|
||||
var name_spans = std.ArrayList(ast.Span).empty;
|
||||
var name_is_raw = std.ArrayList(bool).empty;
|
||||
for (targets.items) |target| {
|
||||
if (target.data != .identifier) {
|
||||
return self.fail("destructuring targets must be identifiers");
|
||||
}
|
||||
try names.append(self.allocator, target.data.identifier.name);
|
||||
try name_spans.append(self.allocator, target.span);
|
||||
try name_is_raw.append(self.allocator, target.data.identifier.is_raw);
|
||||
}
|
||||
const value = try self.parseExpr();
|
||||
try self.expectSemicolonAfter(value);
|
||||
return try self.createNode(start, .{ .destructure_decl = .{
|
||||
.names = try names.toOwnedSlice(self.allocator),
|
||||
.name_spans = try name_spans.toOwnedSlice(self.allocator),
|
||||
.name_is_raw = try name_is_raw.toOwnedSlice(self.allocator),
|
||||
.value = value,
|
||||
} });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user