docs: tuple syntax cutover — Tuple(...) type, .(...) value, channel-outside-Tuple failables
Rewrite specs.md tuple/failable/pack/UFCS/grammar sections to the new syntax, update readme.md, and refresh stale tuple references in example header comments. Also fixes two pre-existing doc inaccuracies surfaced in review: drop the value-discarding `;` in the tuple-return examples, and correct the §13 function-type grammar production (optional param list + optional trailing `!` channel). Optional semantics unchanged. current/CHECKPOINT-LANG.md logs the cutover.
This commit is contained in:
51
current/CHECKPOINT-LANG.md
Normal file
51
current/CHECKPOINT-LANG.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# CHECKPOINT-LANG — user-facing language features
|
||||||
|
|
||||||
|
Companion to [PLAN-LANG.md](PLAN-LANG.md). Update after every step (one step at
|
||||||
|
a time, per the cadence rule).
|
||||||
|
|
||||||
|
## Last completed step
|
||||||
|
**Tuple syntax cutover — `Tuple(...)` type + `.(...)` value (commit 989e18b7).**
|
||||||
|
The bare-paren tuple grammar was replaced with explicit, position-unambiguous
|
||||||
|
forms that mirror how structs work:
|
||||||
|
|
||||||
|
- type `(A, B)` → `Tuple(A, B)` (named keeps `:` — `Tuple(x: A, y: B)`)
|
||||||
|
- value `(a, b)` → `.(a, b)` (named uses `=` — `.(x = a, y = b)`)
|
||||||
|
- typed (new) → `Tuple(A, B).(a, b)` (like `Point.{...}`)
|
||||||
|
- failable `-> (T, !)` → `-> T !`
|
||||||
|
`-> (T1, T2, !)` → `-> Tuple(T1, T2) !` (error channel OUTSIDE the Tuple)
|
||||||
|
|
||||||
|
Bare `(...)` is now grouping ONLY, everywhere; a comma in bare parens is a hard
|
||||||
|
error with a migration hint. Grouping, function types `(A, B) -> R`, param lists,
|
||||||
|
lambdas, match bindings, and `?(?T)` grouping are unaffected. `Tuple(...)` is
|
||||||
|
strictly a TYPE in every position (incl. `size_of` / `type_info` args); a tuple
|
||||||
|
VALUE comes only from `.(...)` or `Tuple(...).(...)`. A bare `Tuple(1, 2)`
|
||||||
|
(non-type elements) is rejected. Field access is unchanged (`.0`/`.1` positional,
|
||||||
|
`.x` named). Optional semantics are untouched — `??T ≡ ?T` was NOT done; nested
|
||||||
|
optionals (`?(?i64)`) stay genuine.
|
||||||
|
|
||||||
|
The ~110 tuple-bearing corpus files were migrated by a one-shot AST-aware
|
||||||
|
migrator; new examples landed (0130 new syntax, 0131 typed construction, 1060
|
||||||
|
named-tuple failable return). Issue **0189** filed (non-type expression in type
|
||||||
|
position silently fabricates an empty struct — surfaced while validating the
|
||||||
|
`Tuple(i32, g.a)` rejection path).
|
||||||
|
|
||||||
|
Docs updated to the new syntax: `specs.md` (Tuple Types section, function
|
||||||
|
multi-return note, all error-channel sections, Variadic Heterogeneous Type Packs,
|
||||||
|
Tuple UFCS Splatting, and the normative Grammar block) and `readme.md` (inline-asm
|
||||||
|
named-tuple return + the `N → a tuple` rule). Stale old-syntax mentions in example
|
||||||
|
header comments were corrected (comments only — no code touched). Suite green
|
||||||
|
(810 ran, 0 failed).
|
||||||
|
|
||||||
|
## Current state
|
||||||
|
Tuple syntax cutover shipped and documented. `Tuple(...)` / `.(...)` are the only
|
||||||
|
tuple spellings across the corpus, specs, and readme.
|
||||||
|
|
||||||
|
## Next step
|
||||||
|
Pick up the next incomplete LANG step from [PLAN-LANG.md](PLAN-LANG.md).
|
||||||
|
|
||||||
|
## Log
|
||||||
|
- **Tuple syntax cutover** (commit 989e18b7): `(A,B)`/`(a,b)` tuples replaced by
|
||||||
|
`Tuple(A,B)` type + `.(a,b)` value; failable `!` moved outside the Tuple
|
||||||
|
(`-> T !` / `-> Tuple(...) !`); bare parens are grouping-only. Docs (specs.md +
|
||||||
|
readme.md) and stale example-comment mentions migrated to the new syntax. Issue
|
||||||
|
0189 filed. Suite green (810 ran, 0 failed).
|
||||||
@@ -35,7 +35,7 @@ main :: () {
|
|||||||
print("{}\n", a); // 2
|
print("{}\n", a); // 2
|
||||||
print("{}\n", b); // 1
|
print("{}\n", b); // 1
|
||||||
|
|
||||||
wrap :: (x: i64) -> Tuple(i64) { .(x) } // 1-tuple needs trailing comma; (i64) groups
|
wrap :: (x: i64) -> Tuple(i64) { .(x) } // 1-tuple type `Tuple(i64)`; bare `(i64)` groups
|
||||||
t := wrap(99);
|
t := wrap(99);
|
||||||
print("{}\n", t.0); // 99
|
print("{}\n", t.0); // 99
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// tuple here). Tuples are POSITIONAL, so `TupleInfo` is just a `[]Type` (no field
|
// tuple here). Tuples are POSITIONAL, so `TupleInfo` is just a `[]Type` (no field
|
||||||
// names). Two paths:
|
// names). Two paths:
|
||||||
// 1. Programmatic build: `define(declare("Pair"), .tuple(.{ elements = … }))`.
|
// 1. Programmatic build: `define(declare("Pair"), .tuple(.{ elements = … }))`.
|
||||||
// 2. Round-trip: `define(declare("TripleCopy"), type_info((i64, bool, f64)))`
|
// 2. Round-trip: `define(declare("TripleCopy"), type_info(Tuple(i64, bool, f64)))`
|
||||||
// reflects a source tuple type INTO a `.tuple(TupleInfo)` value and
|
// reflects a source tuple type INTO a `.tuple(TupleInfo)` value and
|
||||||
// reconstructs it — no literal element list.
|
// reconstructs it — no literal element list.
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// A tuple literal used in a type position (`(i32, i32)` reinterpreted as a tuple
|
// A tuple type (`Tuple(i32, i32)` at a type-demanding site like `size_of`) must
|
||||||
// type at a type-demanding site like `size_of`) must list only types. A non-type
|
// list only types. A non-type
|
||||||
// element — here the `1` in `(i32, 1)` — is rejected with a user-facing
|
// element — here the `1` in `Tuple(i32, 1)` — is rejected with a user-facing
|
||||||
// diagnostic instead of silently fabricating an `i64` field for that slot.
|
// diagnostic instead of silently fabricating an `i64` field for that slot.
|
||||||
// Regression (issue 0067).
|
// Regression (issue 0067).
|
||||||
// Expected: a clean "tuple type element is not a type" error at the `1`; exit 1.
|
// Expected: a clean "tuple type element is not a type" error at the `1`; exit 1.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Feature 1 — materialize a tuple from a pack via `(..xs.method)` (Decision 2:
|
// Feature 1 — materialize a tuple from a pack via `.(..xs.method)` (Decision 2:
|
||||||
// a pack is stored by materializing a tuple). `(..xs.get)` projects `get` over
|
// a pack is stored by materializing a tuple). `.(..xs.get)` projects `get` over
|
||||||
// the pack and collects the results into a real tuple value, which can then be
|
// the pack and collects the results into a real tuple value, which can then be
|
||||||
// stored, indexed, and (for `Box(T)`) is heterogeneous per position.
|
// stored, indexed, and (for `Box(T)`) is heterogeneous per position.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Feature 1 — TYPE-position pack projection `xs.T`. The per-element protocol
|
// Feature 1 — TYPE-position pack projection `xs.T`. The per-element protocol
|
||||||
// type-arg `T` projects into a Pack of types, usable in type/signature
|
// type-arg `T` projects into a Pack of types, usable in type/signature
|
||||||
// positions: a tuple type `(..xs.T)` and a closure signature
|
// positions: a tuple type `Tuple(..xs.T)` and a closure signature
|
||||||
// `Closure(..xs.T) -> R`. (`T` of each element comes from its
|
// `Closure(..xs.T) -> R`. (`T` of each element comes from its
|
||||||
// `impl Box(T) for <elem>`.)
|
// `impl Box(T) for <elem>`.)
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ impl Box(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
|
|||||||
impl Box(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
|
impl Box(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
|
||||||
impl Box(i64) for Dbl { get :: (self: *Dbl) -> i64 => self.n * 2; }
|
impl Box(i64) for Dbl { get :: (self: *Dbl) -> i64 => self.n * 2; }
|
||||||
|
|
||||||
// Tuple type `(..xs.T)` — heterogeneous (i64, string), matched by the
|
// Tuple type `Tuple(..xs.T)` — heterogeneous (i64, string), matched by the
|
||||||
// value-projection `(..xs.get)`.
|
// value-projection `.(..xs.get)`.
|
||||||
snap :: (..xs: Box) -> void {
|
snap :: (..xs: Box) -> void {
|
||||||
t : Tuple(..xs.T) = .(..xs.get);
|
t : Tuple(..xs.T) = .(..xs.get);
|
||||||
print("0={} 1={}\n", t.0, t.1);
|
print("0={} 1={}\n", t.0, t.1);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Phase 4.2 — the canonical `Combined` struct's storage layer: a generic
|
// Phase 4.2 — the canonical `Combined` struct's storage layer: a generic
|
||||||
// struct whose field is a pack of PARAMETERIZED-protocol values,
|
// struct whose field is a pack of PARAMETERIZED-protocol values,
|
||||||
// `sources: (..VL(Ts))` → `(VL(T0), VL(T1), …)`. Each `VL(Ti)` is a real
|
// `sources: Tuple(..VL(Ts))` → `Tuple(VL(T0), VL(T1), …)`. Each `VL(Ti)` is a real
|
||||||
// 16-byte protocol value (issue: parameterized-protocol value types), and
|
// 16-byte protocol value (issue: parameterized-protocol value types), and
|
||||||
// `(..VL(Ts))` applies `VL` per pack element. Instantiate + whole-tuple store
|
// `Tuple(..VL(Ts))` applies `VL` per pack element. Instantiate + whole-tuple store
|
||||||
// of `xx`-erased values + per-element method dispatch all work.
|
// of `xx`-erased values + per-element method dispatch all work.
|
||||||
|
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Phase 6 — `c.sources = (..sources)`: materialize a pack into a
|
// Phase 6 — `c.sources = .(..sources)`: materialize a pack into a
|
||||||
// protocol-typed tuple field, erasing each concrete pack element to the field's
|
// protocol-typed tuple field, erasing each concrete pack element to the field's
|
||||||
// protocol slot. The pack `..sources: VL` holds concrete cells; `(..sources)`
|
// protocol slot. The pack `..sources: VL` holds concrete cells; `.(..sources)`
|
||||||
// into a `(..VL(Ts))` field `xx`-erases each to its `VL(Ti)` value.
|
// into a `Tuple(..VL(Ts))` field `xx`-erases each to its `VL(Ti)` value.
|
||||||
|
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
// - `$R` is inferred at the call site from the lowered mapper's closure ret,
|
// - `$R` is inferred at the call site from the lowered mapper's closure ret,
|
||||||
// bound into the mono (`-> VL($R)` ⇒ `VL(i64)`, `Combined($R, ..)` ⇒
|
// bound into the mono (`-> VL($R)` ⇒ `VL(i64)`, `Combined($R, ..)` ⇒
|
||||||
// `Combined(i64, ..)`), and folded into the mangle.
|
// `Combined(i64, ..)`), and folded into the mangle.
|
||||||
// - `(..sources)` materializes the pack into the `(..VL(Ts))` field (per-element
|
// - `.(..sources)` materializes the pack into the `Tuple(..VL(Ts))` field (per-element
|
||||||
// erase) and `mapper(..sources.get)` projects+spreads; `xx c` erases the
|
// erase) and `mapper(..sources.get)` projects+spreads; `xx c` erases the
|
||||||
// generic-struct instance to `VL(i64)` via the generic impl's monomorphized
|
// generic-struct instance to `VL(i64)` via the generic impl's monomorphized
|
||||||
// thunk.
|
// thunk.
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
// arrive in Phase 2 — do NOT expect this to compile/run yet. The authoritative
|
// arrive in Phase 2 — do NOT expect this to compile/run yet. The authoritative
|
||||||
// checks are the parser unit tests in src/parser.zig ("parse pack expansion: …").
|
// checks are the parser unit tests in src/parser.zig ("parse pack expansion: …").
|
||||||
|
|
||||||
// 1. Tuple value position — `(..pack)` / `(..pack.field)`:
|
// 1. Tuple value position — `.(..pack)` / `.(..pack.field)`:
|
||||||
tv1 :: () => .(..xs);
|
tv1 :: () => .(..xs);
|
||||||
tv2 :: () => .(..xs.value);
|
tv2 :: () => .(..xs.value);
|
||||||
tv3 :: () => .(a, ..xs, b); // mixed positional + spread
|
tv3 :: () => .(a, ..xs, b); // mixed positional + spread
|
||||||
|
|
||||||
// 2. Tuple type position — `(..F(Ts))` / `(..F(Ts.Arg))`:
|
// 2. Tuple type position — `Tuple(..F(Ts))` / `Tuple(..F(Ts.Arg))`:
|
||||||
tt1 :: (x: Tuple(..ValueListenable(Ts))) => x;
|
tt1 :: (x: Tuple(..ValueListenable(Ts))) => x;
|
||||||
tt2 :: (x: Tuple(..ValueListenable(Ts.Arg))) => x;
|
tt2 :: (x: Tuple(..ValueListenable(Ts.Arg))) => x;
|
||||||
|
|
||||||
|
|||||||
@@ -46,17 +46,17 @@ main :: () -> i32 {
|
|||||||
// ── GAPS (Feature 1 work — intentionally NOT exercised above) ──────
|
// ── GAPS (Feature 1 work — intentionally NOT exercised above) ──────
|
||||||
//
|
//
|
||||||
// G1. Tuple field projection across elements:
|
// G1. Tuple field projection across elements:
|
||||||
// t := (Listenable.{value=1}, Listenable.{value=2});
|
// t := .(Listenable.{value=1}, Listenable.{value=2});
|
||||||
// v := t.value; // expected: (1, 2) — Decision 3 "tuple.field"
|
// v := t.value; // expected: .(1, 2) — Decision 3 "tuple.field"
|
||||||
// Today: `error: field 'value' not found on type 'tuple'`.
|
// Today: `error: field 'value' not found on type 'tuple'`.
|
||||||
// Needed by canonical `self.sources.value`.
|
// Needed by canonical `self.sources.value`.
|
||||||
//
|
//
|
||||||
// G2. Tuple spread into call args:
|
// G2. Tuple spread into call args:
|
||||||
// p := (10, 20);
|
// p := .(10, 20);
|
||||||
// add(..p); // expected: add(10, 20) — Decision 3 "..tuple"
|
// add(..p); // expected: add(10, 20) — Decision 3 "..tuple"
|
||||||
// Today: lowers to one `undef` arg → LLVM arity verification failure.
|
// Today: lowers to one `undef` arg → LLVM arity verification failure.
|
||||||
// Needed by canonical `mapper(..sources.value)` and `(..sources)`.
|
// Needed by canonical `mapper(..sources.value)` and `.(..sources)`.
|
||||||
//
|
//
|
||||||
// Both are already scheduled: parsing in Phase 1.2 (PackExpansion node covers
|
// Both are already scheduled: parsing in Phase 1.2 (PackExpansion node covers
|
||||||
// `(..pack)` / `..pack.field`), sema in Phase 2.3 ("tuple-spread parallels").
|
// `.(..pack)` / `..pack.field`), sema in Phase 2.3 ("tuple-spread parallels").
|
||||||
// No separate Feature 1.5 needed — see Step 0.4 triage in CHECKPOINT-LANG.md.
|
// No separate Feature 1.5 needed — see Step 0.4 triage in CHECKPOINT-LANG.md.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
// - wrapper-alias element `BoxPtr :: *Box` (engine wrapper aliasing)
|
// - wrapper-alias element `BoxPtr :: *Box` (engine wrapper aliasing)
|
||||||
// - union body-builder child `WrapU :: union { b: Box }` (C1, registerUnionDecl)
|
// - union body-builder child `WrapU :: union { b: Box }` (C1, registerUnionDecl)
|
||||||
// - enum body-builder child `WrapE :: enum { V: Box }` (C1, registerEnumDecl)
|
// - enum body-builder child `WrapE :: enum { V: Box }` (C1, registerEnumDecl)
|
||||||
// - tuple-literal element `size_of((Box, i32))` (O1/K5)
|
// - tuple-type element `size_of(Tuple(Box, i32))` (O1/K5)
|
||||||
// - inline-anonymous body child `x : union { b: Box }` (inline-anon engine arm)
|
// - inline-anonymous body child `x : union { b: Box }` (inline-anon engine arm)
|
||||||
//
|
//
|
||||||
// Fail-before (pre-E6b-R): each of these resolved `Box` through the stateless
|
// Fail-before (pre-E6b-R): each of these resolved `Box` through the stateless
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Compound type literals in expression position — `size_of` /
|
// Compound type literals in expression position — `size_of` /
|
||||||
// `align_of` accept pointer (`*T`), optional (`?T`), array (`[N]T`),
|
// `align_of` accept pointer (`*T`), optional (`?T`), array (`[N]T`),
|
||||||
// function (`(A) -> B`), and tuple (`(A, B)`) types directly. Also
|
// function (`(A) -> B`), and tuple (`Tuple(A, B)`) types directly. Also
|
||||||
// const-decl RHS aliases through the same forms (`Ptr :: *u8;` etc).
|
// const-decl RHS aliases through the same forms (`Ptr :: *u8;` etc).
|
||||||
// Same shape as the existing `size_of(i32)` baseline path.
|
// Same shape as the existing `size_of(i32)` baseline path.
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ main :: () -> i32 {
|
|||||||
// Function-type literal in expression position.
|
// Function-type literal in expression position.
|
||||||
print("size_of((i32)->i32) = {}\n", size_of((i32) -> i32));
|
print("size_of((i32)->i32) = {}\n", size_of((i32) -> i32));
|
||||||
|
|
||||||
// Tuple literal reinterpreted as tuple type at the type-demanding site.
|
// Tuple type at the type-demanding site.
|
||||||
print("size_of((i32, i32)) = {}\n", size_of(Tuple(i32, i32)));
|
print("size_of((i32, i32)) = {}\n", size_of(Tuple(i32, i32)));
|
||||||
|
|
||||||
// Aliases.
|
// Aliases.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// - `t.0 = v` writes one element in place (was a known gap: the lvalue path
|
// - `t.0 = v` writes one element in place (was a known gap: the lvalue path
|
||||||
// looked the element up by name via getStructFields and left the pointee
|
// looked the element up by name via getStructFields and left the pointee
|
||||||
// `.unresolved`; now it indexes the tuple positionally like the read path).
|
// `.unresolved`; now it indexes the tuple positionally like the read path).
|
||||||
// - Named tuples `(x: T, y: U)` keep their field names through parsing and
|
// - Named tuples `Tuple(x: T, y: U)` keep their field names through parsing and
|
||||||
// type resolution, so `t.x` reads/writes by name (and `.0` by position).
|
// type resolution, so `t.x` reads/writes by name (and `.0` by position).
|
||||||
|
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// Parenthesized type grouping: in type position `(T)` (single element, no
|
// Parenthesized type grouping: bare parens `(T)` are GROUPING ONLY (in every
|
||||||
// trailing comma) is a GROUPING that resolves to the inner type — mirroring
|
// position) and resolve to the inner type. A tuple type is `Tuple(...)`:
|
||||||
// value position where `(expr)` groups and `(expr,)` is a 1-tuple. A 1-tuple
|
// `Tuple(T)` is a 1-tuple, `Tuple(A, B)` a 2-tuple. Bare parens never form a
|
||||||
// type requires the trailing comma `(T,)`; `(A, B)` is a 2-tuple.
|
// tuple.
|
||||||
//
|
//
|
||||||
// This lets a closure/optional type be parenthesized for readability:
|
// This lets a closure/optional type be parenthesized for readability:
|
||||||
// [1](Closure(i64,i64) -> i64) // array of closures (grouped element type)
|
// [1](Closure(i64,i64) -> i64) // array of closures (grouped element type)
|
||||||
// ?(?i64) // nested optional
|
// ?(?i64) // nested optional
|
||||||
// without the parens silently turning it into a 1-tuple.
|
// without ambiguity — grouping is decoupled from the tuple grammar.
|
||||||
|
|
||||||
#import "modules/std.sx";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ main :: () {
|
|||||||
fns : [1](Closure(i64, i64) -> i64) = .[ add ];
|
fns : [1](Closure(i64, i64) -> i64) = .[ add ];
|
||||||
print("{}\n", fns[0](3, 4)); // 7
|
print("{}\n", fns[0](3, 4)); // 7
|
||||||
|
|
||||||
// A 1-tuple type still requires the trailing comma.
|
// A 1-tuple type is `Tuple(i64)`.
|
||||||
one : Tuple(i64) = .(9);
|
one : Tuple(i64) = .(9);
|
||||||
print("{}\n", one.0); // 9
|
print("{}\n", one.0); // 9
|
||||||
|
|
||||||
|
|||||||
@@ -498,11 +498,11 @@ operands or to give a value a name distinct from its register. A label that just
|
|||||||
echoes its register (`[rax] "={rax}"`) is rejected.
|
echoes its register (`[rax] "={rax}"`) is rejected.
|
||||||
|
|
||||||
Outputs decide the result: **0** → `void` (and the asm must be `volatile`);
|
Outputs decide the result: **0** → `void` (and the asm must be `volatile`);
|
||||||
**1** → that type; **N** → a tuple, named by each operand's name.
|
**1** → that type; **N** → a `Tuple`, named by each operand's name.
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
// multiple value outputs → a destructurable tuple
|
// multiple value outputs → a destructurable tuple
|
||||||
split :: (x: u64) -> (lo: u64, hi: u64) {
|
split :: (x: u64) -> Tuple(lo: u64, hi: u64) {
|
||||||
return asm {
|
return asm {
|
||||||
#string ASM
|
#string ASM
|
||||||
and %[l], %[x], #0xff
|
and %[l], %[x], #0xff
|
||||||
|
|||||||
158
specs.md
158
specs.md
@@ -827,35 +827,54 @@ impl Into(MyBuf) for []u8 { ... }
|
|||||||
the convert into itself.
|
the convert into itself.
|
||||||
|
|
||||||
### Tuple Types
|
### Tuple Types
|
||||||
Anonymous product types with optional field names. Tuples are first-class values — they can be stored in variables, passed to functions, and returned. Tuples also support **spread** (`..tuple` / `(..tuple)`) and **field projection** (`tuple.field` across all elements) — see "Variadic Heterogeneous Type Packs".
|
Anonymous product types with optional field names. Tuples are first-class values — they can be stored in variables, passed to functions, and returned. A **named tuple** `Tuple(x: A, y: B)` is sx's anonymous *structural* record — it carries field names but has no nominal identity (distinct from a `struct`, which is nominal). Tuples also support **spread** (`..tuple` / `.(..tuple)`) and **field projection** (`tuple.field` across all elements) — see "Variadic Heterogeneous Type Packs".
|
||||||
|
|
||||||
|
The tuple TYPE is always written `Tuple(...)`; a tuple VALUE is always written
|
||||||
|
`.(...)` (mirroring how a struct type `Point` pairs with a value literal
|
||||||
|
`Point.{...}` / `.{...}`). Bare parentheses `(...)` are **grouping only,
|
||||||
|
everywhere** — a comma inside bare parens is a hard error with a migration hint.
|
||||||
|
|
||||||
#### Construction
|
#### Construction
|
||||||
```sx
|
```sx
|
||||||
pair := (40, 2); // positional tuple: (i64, i64)
|
pair := .(40, 2); // positional tuple value: Tuple(i64, i64)
|
||||||
named := (x: 10, y: 20); // named tuple: (x: i64, y: i64)
|
named := .(x = 10, y = 20); // named tuple value: Tuple(x: i64, y: i64)
|
||||||
single := (42,); // 1-tuple (trailing comma in value position)
|
single := .(42); // 1-tuple value
|
||||||
zeroed : (i32, i32) = ---; // zero-initialized tuple
|
empty := .(); // empty tuple value
|
||||||
|
zeroed : Tuple(i32, i32) = ---; // zero-initialized tuple
|
||||||
|
|
||||||
|
// Explicitly typed value (like `Point.{...}`):
|
||||||
|
p := Tuple(i64, i64).(40, 2);
|
||||||
|
n := Tuple(x: i64, y: i64).(x = 10, y = 20);
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: In value position, `(expr)` without a comma is a grouping expression, not a tuple. Use `(expr,)` for a 1-tuple value.
|
A named tuple value uses `=` for its fields (`.(x = a, y = b)`); a named tuple
|
||||||
|
type keeps `:` (`Tuple(x: A, y: B)`).
|
||||||
|
|
||||||
#### Type Syntax
|
#### Type Syntax
|
||||||
In type position, parentheses mirror value position: `(T)` (a single element, no
|
The tuple type is `Tuple(...)`:
|
||||||
trailing comma) is a **grouping** that resolves to the inner type `T`, while
|
|
||||||
`(T,)` (trailing comma) is a 1-tuple. `(A, B)` is a 2-tuple. The `->` arrow
|
|
||||||
disambiguates function types from grouped/tuple types:
|
|
||||||
```sx
|
```sx
|
||||||
(i64) // grouping: resolves to i64 (NOT a tuple)
|
Tuple(i64) // 1-tuple type
|
||||||
(i64,) // tuple type with one field
|
Tuple(i64, i64) // 2-tuple type
|
||||||
(i64, i64) // tuple type with two fields
|
Tuple(x: i64, y: i64) // named tuple type
|
||||||
|
Tuple() // empty tuple type
|
||||||
|
Tuple(..F(Ts)) // pack-spread tuple type (see Variadic Heterogeneous Type Packs)
|
||||||
|
```
|
||||||
|
`Tuple(...)` is strictly a **type** in every position — including `size_of(Tuple(...))`
|
||||||
|
and `type_info(...)` arguments. A tuple **value** comes only from `.(...)` (anonymous)
|
||||||
|
or `Tuple(...).(...)` (explicitly typed); a bare `Tuple(1, 2)` (non-type elements) is
|
||||||
|
rejected as a tuple type with non-type elements.
|
||||||
|
|
||||||
|
Bare parentheses are grouping and never a tuple:
|
||||||
|
```sx
|
||||||
|
(i64) // grouping: resolves to i64
|
||||||
(i64) -> i64 // function type: takes i64, returns i64
|
(i64) -> i64 // function type: takes i64, returns i64
|
||||||
(i64, i64) -> i64 // function type: takes two i64, returns i64
|
(i64, i64) -> i64 // function type: takes two i64, returns i64
|
||||||
?(?i64) // grouping → a genuine nested optional
|
?(?i64) // grouping → a genuine nested optional
|
||||||
[1](Closure(i64,i64) -> i64) // grouping → array of one closure
|
[1](Closure(i64,i64) -> i64) // grouping → array of one closure
|
||||||
```
|
```
|
||||||
Grouping lets a closure/optional/function type be parenthesized for readability
|
Grouping lets a closure/optional/function type be parenthesized for readability.
|
||||||
without silently becoming a 1-tuple. A named single element `(x: T)` stays a
|
Function types `(A, B) -> R`, parameter lists, lambdas, and `match` bindings keep
|
||||||
(named) tuple.
|
using bare parens — they are unaffected by the tuple grammar.
|
||||||
|
|
||||||
#### Field Access
|
#### Field Access
|
||||||
```sx
|
```sx
|
||||||
@@ -867,49 +886,49 @@ named.0; // 10 — numeric index also works on named tuples
|
|||||||
|
|
||||||
#### As Return Type
|
#### As Return Type
|
||||||
```sx
|
```sx
|
||||||
swap :: (a: i64, b: i64) -> (i64, i64) { (b, a); }
|
swap :: (a: i64, b: i64) -> Tuple(i64, i64) { .(b, a) }
|
||||||
wrap :: (x: i64) -> (i64,) { (x,); } // 1-tuple return needs the trailing comma
|
wrap :: (x: i64) -> Tuple(i64) { .(x) } // 1-tuple return
|
||||||
|
|
||||||
s := swap(1, 2); // s.0 = 2, s.1 = 1
|
s := swap(1, 2); // s.0 = 2, s.1 = 1
|
||||||
t := wrap(42); // t.0 = 42
|
t := wrap(42); // t.0 = 42
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Representation
|
#### Representation
|
||||||
Tuples are represented as anonymous LLVM struct types (same layout as named structs). A tuple `(i64, i64)` has LLVM type `{ i64, i64 }`.
|
Tuples are represented as anonymous LLVM struct types (same layout as named structs). A tuple `Tuple(i64, i64)` has LLVM type `{ i64, i64 }`.
|
||||||
|
|
||||||
#### Tuple Operators
|
#### Tuple Operators
|
||||||
|
|
||||||
**Equality and inequality** — element-wise comparison, both sides must have the same field count:
|
**Equality and inequality** — element-wise comparison, both sides must have the same field count:
|
||||||
```sx
|
```sx
|
||||||
(1, 2) == (1, 2) // true
|
.(1, 2) == .(1, 2) // true
|
||||||
(1, 2) != (1, 3) // true
|
.(1, 2) != .(1, 3) // true
|
||||||
```
|
```
|
||||||
|
|
||||||
**Concatenation** (`+`) — creates a new tuple with fields from both sides:
|
**Concatenation** (`+`) — creates a new tuple with fields from both sides:
|
||||||
```sx
|
```sx
|
||||||
c := (1, 2) + (3, 4); // c : (i64, i64, i64, i64)
|
c := .(1, 2) + .(3, 4); // c : Tuple(i64, i64, i64, i64)
|
||||||
c.0; // 1
|
c.0; // 1
|
||||||
c.3; // 4
|
c.3; // 4
|
||||||
```
|
```
|
||||||
|
|
||||||
**Repetition** (`*`) — repeats a tuple N times (N must be a compile-time integer literal):
|
**Repetition** (`*`) — repeats a tuple N times (N must be a compile-time integer literal):
|
||||||
```sx
|
```sx
|
||||||
r := (1, 2) * 3; // r : (i64, i64, i64, i64, i64, i64)
|
r := .(1, 2) * 3; // r : Tuple(i64, i64, i64, i64, i64, i64)
|
||||||
r.0; // 1
|
r.0; // 1
|
||||||
r.5; // 2
|
r.5; // 2
|
||||||
```
|
```
|
||||||
|
|
||||||
**Lexicographic comparison** (`<`, `<=`, `>`, `>=`) — compares element-by-element left to right:
|
**Lexicographic comparison** (`<`, `<=`, `>`, `>=`) — compares element-by-element left to right:
|
||||||
```sx
|
```sx
|
||||||
(1, 2) < (1, 3) // true (first fields equal, 2 < 3)
|
.(1, 2) < .(1, 3) // true (first fields equal, 2 < 3)
|
||||||
(2, 0) > (1, 9) // true (2 > 1, rest ignored)
|
.(2, 0) > .(1, 9) // true (2 > 1, rest ignored)
|
||||||
(1, 2) <= (1, 2) // true (all equal, <= allows tie)
|
.(1, 2) <= .(1, 2) // true (all equal, <= allows tie)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Membership** (`in`) — checks if a value exists in a tuple:
|
**Membership** (`in`) — checks if a value exists in a tuple:
|
||||||
```sx
|
```sx
|
||||||
3 in (1, 2, 3) // true
|
3 in .(1, 2, 3) // true
|
||||||
5 in (1, 2, 3) // false
|
5 in .(1, 2, 3) // false
|
||||||
```
|
```
|
||||||
|
|
||||||
### Array Types
|
### Array Types
|
||||||
@@ -1470,8 +1489,8 @@ may do, regardless of the concrete arg types at any particular call site.
|
|||||||
| Comptime unroll (element + index) | `inline for xs, 0.. (x, i) { ... }` | multi-iterable parity with the runtime `for`: position 0 drives the count, a trailing open range pairs the cursor |
|
| Comptime unroll (element + index) | `inline for xs, 0.. (x, i) { ... }` | multi-iterable parity with the runtime `for`: position 0 drives the count, a trailing open range pairs the cursor |
|
||||||
| Projection | `xs.field` | see "Pack projection" |
|
| Projection | `xs.field` | see "Pack projection" |
|
||||||
| Spread → call args | `..xs` / `..xs.field` | expands to N positional args |
|
| Spread → call args | `..xs` / `..xs.field` | expands to N positional args |
|
||||||
| Spread → tuple value | `(..xs)` / `(..xs.field)` | materializes a tuple |
|
| Spread → tuple value | `.(..xs)` / `.(..xs.field)` | materializes a tuple |
|
||||||
| Spread → tuple type | `(..F(Ts))` / `(..F(Ts.Arg))` | tuple type with per-element type application |
|
| Spread → tuple type | `Tuple(..F(Ts))` / `Tuple(..F(Ts.Arg))` | tuple type with per-element type application |
|
||||||
| Spread → callable sig | `Closure(..Ts) -> R` / `Closure(..Ts.Arg) -> R` | positional params of the callable |
|
| Spread → callable sig | `Closure(..Ts) -> R` / `Closure(..Ts.Arg) -> R` | positional params of the callable |
|
||||||
|
|
||||||
#### Pack projection
|
#### Pack projection
|
||||||
@@ -1484,7 +1503,7 @@ Resolution is **position-driven** (no cross-namespace shadowing):
|
|||||||
type-arg `T`, so `..xs.T` is the pack of element value-types.
|
type-arg `T`, so `..xs.T` is the pack of element value-types.
|
||||||
- In **value** position, `xs.field` looks `field` up in the constraint's
|
- In **value** position, `xs.field` looks `field` up in the constraint's
|
||||||
**runtime-field** namespace and yields a *tuple* of the projected values
|
**runtime-field** namespace and yields a *tuple* of the projected values
|
||||||
(e.g. `xs.value` → `(xs[0].value, xs[1].value, ...)`).
|
(e.g. `xs.value` → `.(xs[0].value, xs[1].value, ...)`).
|
||||||
|
|
||||||
A protocol that declares a type-arg and a runtime field with the **same name**
|
A protocol that declares a type-arg and a runtime field with the **same name**
|
||||||
compiles, but emits a soft warning at the protocol declaration (the human is
|
compiles, but emits a soft warning at the protocol declaration (the human is
|
||||||
@@ -1499,13 +1518,13 @@ tuple rather than a pack:
|
|||||||
- `tuple.field` projects `field` out of every element (when all elements have a
|
- `tuple.field` projects `field` out of every element (when all elements have a
|
||||||
same-named field), returning a tuple of the projected values.
|
same-named field), returning a tuple of the projected values.
|
||||||
|
|
||||||
This lets a pack be materialized once (`stored := (..xs)`) and later re-spread
|
This lets a pack be materialized once (`stored := .(..xs)`) and later re-spread
|
||||||
(`f(..stored)`) or re-projected (`stored.value`).
|
(`f(..stored)`) or re-projected (`stored.value`).
|
||||||
|
|
||||||
#### Pack of zero (N = 0)
|
#### Pack of zero (N = 0)
|
||||||
|
|
||||||
`xs.len == 0` is valid: `inline for` over an empty range doesn't execute, spreads
|
`xs.len == 0` is valid: `inline for` over an empty range doesn't execute, spreads
|
||||||
are no-ops, and `(..xs)` is the empty tuple. A library built on packs (e.g.
|
are no-ops, and `.(..xs)` is the empty tuple. A library built on packs (e.g.
|
||||||
`map`) must handle N=0 — typically by producing a constant result that never
|
`map`) must handle N=0 — typically by producing a constant result that never
|
||||||
changes.
|
changes.
|
||||||
|
|
||||||
@@ -1515,10 +1534,10 @@ Because a pack has no runtime representation, using the **bare pack name** where
|
|||||||
a runtime value is required is a compile error with a context-tailored
|
a runtime value is required is a compile error with a context-tailored
|
||||||
suggestion:
|
suggestion:
|
||||||
|
|
||||||
- storing/binding it (`x := xs;`, `self.f = xs;`) → materialize a tuple `(..xs)`;
|
- storing/binding it (`x := xs;`, `self.f = xs;`) → materialize a tuple `.(..xs)`;
|
||||||
- passing it to a runtime call (`f(xs)`) → declare the parameter as a *slice*
|
- passing it to a runtime call (`f(xs)`) → declare the parameter as a *slice*
|
||||||
variadic `..xs: []P` (a runtime slice) instead of a pack `..xs: P`;
|
variadic `..xs: []P` (a runtime slice) instead of a pack `..xs: P`;
|
||||||
- returning it (`return xs;`) → return a tuple `(..xs)` (and make the return
|
- returning it (`return xs;`) → return a tuple `.(..xs)` (and make the return
|
||||||
type that tuple);
|
type that tuple);
|
||||||
- iterating it (`for xs (x)`, `xs[runtime_i]`) → `inline for xs (x)` (or
|
- iterating it (`for xs (x)`, `xs[runtime_i]`) → `inline for xs (x)` (or
|
||||||
`inline for 0..xs.len (i)` for the index) for a comptime unroll, or take
|
`inline for 0..xs.len (i)` for the index) for a comptime unroll, or take
|
||||||
@@ -1533,8 +1552,8 @@ place.
|
|||||||
#### Storage and protocol conformance
|
#### Storage and protocol conformance
|
||||||
|
|
||||||
To **store** a pack, materialize a tuple: a pack-shaped struct field is
|
To **store** a pack, materialize a tuple: a pack-shaped struct field is
|
||||||
tuple-typed, `sources: (..ValueListenable(Ts))`, assigned `self.sources =
|
tuple-typed, `sources: Tuple(..ValueListenable(Ts))`, assigned `self.sources =
|
||||||
(..sources)`. To **return** a struct as a protocol value, `xx` requires an
|
.(..sources)`. To **return** a struct as a protocol value, `xx` requires an
|
||||||
explicit impl (protocol erasure is impl-driven, not structural) — e.g.
|
explicit impl (protocol erasure is impl-driven, not structural) — e.g.
|
||||||
`impl ValueListenable($R) for Combined($R, ..$Ts) { ... }`.
|
`impl ValueListenable($R) for Combined($R, ..$Ts) { ... }`.
|
||||||
|
|
||||||
@@ -1542,7 +1561,7 @@ explicit impl (protocol erasure is impl-driven, not structural) — e.g.
|
|||||||
|
|
||||||
```sx
|
```sx
|
||||||
Combined :: struct($R: Type, ..$Ts: []Type) {
|
Combined :: struct($R: Type, ..$Ts: []Type) {
|
||||||
sources: (..ValueListenable(Ts)); // pack-spread in tuple type position
|
sources: Tuple(..ValueListenable(Ts)); // pack-spread in tuple type position
|
||||||
mapper: Closure(..Ts) -> $R; // pack-spread in callable sig
|
mapper: Closure(..Ts) -> $R; // pack-spread in callable sig
|
||||||
value: $R;
|
value: $R;
|
||||||
own_allocator: Allocator;
|
own_allocator: Allocator;
|
||||||
@@ -1559,7 +1578,7 @@ map :: (mapper: Closure(..sources.T) -> $R, ..sources: ValueListenable)
|
|||||||
c := context.allocator.alloc(Combined($R, ..sources.T));
|
c := context.allocator.alloc(Combined($R, ..sources.T));
|
||||||
c.own_allocator = context.allocator;
|
c.own_allocator = context.allocator;
|
||||||
c.mapper = mapper;
|
c.mapper = mapper;
|
||||||
c.sources = (..sources); // pack-to-tuple materialization
|
c.sources = .(..sources); // pack-to-tuple materialization
|
||||||
inline for 0..sources.len (i) { // comptime unroll over the pack
|
inline for 0..sources.len (i) { // comptime unroll over the pack
|
||||||
sources[i].addListener((_) => c.recompute());
|
sources[i].addListener((_) => c.recompute());
|
||||||
}
|
}
|
||||||
@@ -1827,15 +1846,16 @@ name :: (params) -> return_type {
|
|||||||
```
|
```
|
||||||
|
|
||||||
- Parameters: `name: type` separated by commas
|
- Parameters: `name: type` separated by commas
|
||||||
- Return type: `-> type` (omit for void). A multi-value return is a tuple: `-> (T1, T2)`.
|
- Return type: `-> type` (omit for void). A multi-value return is a tuple: `-> Tuple(T1, T2)`.
|
||||||
- Body: a block whose **value** is its last statement when that statement is a
|
- Body: a block whose **value** is its last statement when that statement is a
|
||||||
trailing expression with **no** `;` (see [Block values](#block-values)). That
|
trailing expression with **no** `;` (see [Block values](#block-values)). That
|
||||||
value is the implicit return; an explicit `return` works too.
|
value is the implicit return; an explicit `return` works too.
|
||||||
|
|
||||||
A trailing `!` in the return type marks the function **failable** — it adds a
|
A trailing `!` in the return type marks the function **failable** — it adds a
|
||||||
separate error channel alongside the normal returns (`-> (T, !)`, `-> !`,
|
separate error channel alongside the normal returns. The `!` sits **outside**
|
||||||
`-> (T1, T2, !)`). The `!` is not a wrapper around the value; it is one more
|
the tuple: `-> T !` (one value), `-> Tuple(T1, T2) !` (multi value), `-> !`
|
||||||
return slot. See [§12 Error Handling](#12-error-handling).
|
(void). The `!` is not a wrapper around the value; it is one more return slot.
|
||||||
|
See [§12 Error Handling](#12-error-handling).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```sx
|
```sx
|
||||||
@@ -2462,8 +2482,8 @@ When a tuple is used as the receiver of a UFCS call, its elements are unpacked a
|
|||||||
num_add :: (a: i64, b: i64) -> i64 { a + b; }
|
num_add :: (a: i64, b: i64) -> i64 { a + b; }
|
||||||
add :: ufcs num_add;
|
add :: ufcs num_add;
|
||||||
|
|
||||||
(40, 2).add(); // splats to num_add(40, 2) → 42
|
.(40, 2).add(); // splats to num_add(40, 2) → 42
|
||||||
(40,).add(2); // partial: num_add(40, 2) → 42
|
.(40).add(2); // partial: num_add(40, 2) → 42
|
||||||
40.add(2); // normal UFCS: num_add(40, 2) → 42
|
40.add(2); // normal UFCS: num_add(40, 2) → 42
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -2472,8 +2492,8 @@ With more arguments:
|
|||||||
compute :: (a: i64, b: i64, c: i64, d: i64) -> i64 { a + b * c - d; }
|
compute :: (a: i64, b: i64, c: i64, d: i64) -> i64 { a + b * c - d; }
|
||||||
calc :: ufcs compute;
|
calc :: ufcs compute;
|
||||||
|
|
||||||
(1, 2, 3, 4).calc(); // full splat → compute(1, 2, 3, 4)
|
.(1, 2, 3, 4).calc(); // full splat → compute(1, 2, 3, 4)
|
||||||
(1, 2).calc(3, 4); // partial splat → compute(1, 2, 3, 4)
|
.(1, 2).calc(3, 4); // partial splat → compute(1, 2, 3, 4)
|
||||||
1.calc(2, 3, 4); // normal UFCS → compute(1, 2, 3, 4)
|
1.calc(2, 3, 4); // normal UFCS → compute(1, 2, 3, 4)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -3101,7 +3121,7 @@ main :: () {
|
|||||||
|
|
||||||
`main` takes no arguments. Its return type may be any of: void (`()`,
|
`main` takes no arguments. Its return type may be any of: void (`()`,
|
||||||
`-> ()`, `-> void`, or no annotation), an integer type (POSIX exit code),
|
`-> ()`, `-> void`, or no annotation), an integer type (POSIX exit code),
|
||||||
`-> !` (pure failable), or `-> (int_type, !)` (value-carrying failable).
|
`-> !` (pure failable), or `-> int_type !` (value-carrying failable).
|
||||||
The exit code is `0` for void / `-> !` success, the integer return
|
The exit code is `0` for void / `-> !` success, the integer return
|
||||||
truncated to `u8` otherwise. An error that escapes a failable `main`
|
truncated to `u8` otherwise. An error that escapes a failable `main`
|
||||||
prints the unhandled-error header + return trace to stderr and exits `1`.
|
prints the unhandled-error header + return trace to stderr and exits `1`.
|
||||||
@@ -3114,8 +3134,9 @@ See [§12 Error Handling](#12-error-handling).
|
|||||||
sx models recoverable errors as a **separate return channel**, not a wrapped
|
sx models recoverable errors as a **separate return channel**, not a wrapped
|
||||||
result type. A trailing `!` in a function's return type adds one extra return
|
result type. A trailing `!` in a function's return type adds one extra return
|
||||||
slot — a `u32` error tag — alongside the normal value slots. This keeps sx's
|
slot — a `u32` error tag — alongside the normal value slots. This keeps sx's
|
||||||
native multi-return ergonomics: `-> (i32, i64, !)` is a function returning two
|
native multi-return ergonomics: `-> Tuple(i32, i64) !` is a function returning
|
||||||
values *and* an error, with no tuple-in-a-wrapper.
|
two values *and* an error, with no tuple-in-a-wrapper. The `!` sits **outside**
|
||||||
|
the `Tuple`.
|
||||||
|
|
||||||
This section is the canonical surface reference. The design rationale,
|
This section is the canonical surface reference. The design rationale,
|
||||||
trade-offs, and implementation breakdown live in `current/PLAN-ERR.md`.
|
trade-offs, and implementation breakdown live in `current/PLAN-ERR.md`.
|
||||||
@@ -3123,10 +3144,10 @@ trade-offs, and implementation breakdown live in `current/PLAN-ERR.md`.
|
|||||||
### Failable signatures
|
### Failable signatures
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
parse_digit :: (s: string) -> (i32, !) { ... } // one value + error
|
parse_digit :: (s: string) -> i32 ! { ... } // one value + error
|
||||||
parse :: (s: string) -> (i32, i64, !) { ... } // multi-value + error
|
parse :: (s: string) -> Tuple(i32, i64) ! { ... } // multi-value + error
|
||||||
must_init :: () -> ! { ... } // pure failable, no value
|
must_init :: () -> ! { ... } // pure failable, no value
|
||||||
divide :: (a: i32, b: i32) -> (i32, !MathErr) { ... } // named set
|
divide :: (a: i32, b: i32) -> i32 !MathErr { ... } // named set
|
||||||
```
|
```
|
||||||
|
|
||||||
The `!` is always the **last** slot. `0` in the error slot means "no error";
|
The `!` is always the **last** slot. `0` in the error slot means "no error";
|
||||||
@@ -3141,7 +3162,7 @@ Two forms of error set:
|
|||||||
ParseErr :: error { BadDigit, Overflow, Empty };
|
ParseErr :: error { BadDigit, Overflow, Empty };
|
||||||
|
|
||||||
// Inferred set — bare `!` collects whatever tags the body raises.
|
// Inferred set — bare `!` collects whatever tags the body raises.
|
||||||
quick :: () -> (i32, !) {
|
quick :: () -> i32 ! {
|
||||||
if cond raise error.SomeAdHocTag; // mints into the inferred set
|
if cond raise error.SomeAdHocTag; // mints into the inferred set
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -3228,7 +3249,7 @@ v := parse_digit(s) catch (e) compute_fallback(e); // value-producing body
|
|||||||
|
|
||||||
v, n := parse(s) catch (e) {
|
v, n := parse(s) catch (e) {
|
||||||
log.warn("parse failed: {}", e);
|
log.warn("parse failed: {}", e);
|
||||||
(0, 0) // tuple body for a multi-value failable
|
.(0, 0) // tuple body for a multi-value failable
|
||||||
};
|
};
|
||||||
|
|
||||||
v := parse(s) catch (e) == { // match-body form
|
v := parse(s) catch (e) == { // match-body form
|
||||||
@@ -3264,7 +3285,7 @@ the RHS shape decides the result:
|
|||||||
v := parse_digit(s) or 0; // value terminator → non-failable
|
v := parse_digit(s) or 0; // value terminator → non-failable
|
||||||
v := try foo() or try boo(); // chain, propagate if both fail
|
v := try foo() or try boo(); // chain, propagate if both fail
|
||||||
v := foo() or boo() or 0; // bare operands, 0 absorbs all
|
v := foo() or boo() or 0; // bare operands, 0 absorbs all
|
||||||
v, n := parse_pair(s) or (0, 0); // tuple terminator (multi-value)
|
v, n := parse_pair(s) or .(0, 0); // tuple terminator (multi-value)
|
||||||
```
|
```
|
||||||
|
|
||||||
A **void** failable (`-> !`) rejects a plain-value RHS (no success type to
|
A **void** failable (`-> !`) rejects a plain-value RHS (no success type to
|
||||||
@@ -3339,14 +3360,14 @@ On success exit (fall-through, `return`, `break` / `continue` without an error)
|
|||||||
it is skipped — only `defer` runs.
|
it is skipped — only `defer` runs.
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
make_handle :: () -> (Handle, !) {
|
make_handle :: () -> Handle ! {
|
||||||
h := try open();
|
h := try open();
|
||||||
onfail close(h); // close ONLY on a subsequent failure
|
onfail close(h); // close ONLY on a subsequent failure
|
||||||
try configure(h); // fails → onfail runs → close(h)
|
try configure(h); // fails → onfail runs → close(h)
|
||||||
return h; // success → onfail skipped; caller owns h
|
return h; // success → onfail skipped; caller owns h
|
||||||
}
|
}
|
||||||
|
|
||||||
open :: (path: string) -> (Handle, !) {
|
open :: (path: string) -> Handle ! {
|
||||||
h := try sys_open(path);
|
h := try sys_open(path);
|
||||||
onfail (e) { log.warn("init failed for {}: {}", path, e); sys_close(h); }
|
onfail (e) { log.warn("init failed for {}: {}", path, e); sys_close(h); }
|
||||||
...
|
...
|
||||||
@@ -3367,9 +3388,9 @@ function, or at top level, is rejected.
|
|||||||
|
|
||||||
- **Explicit annotation required.** A closure literal's value type is inferred
|
- **Explicit annotation required.** A closure literal's value type is inferred
|
||||||
as today, but if its body raises or `try`-escapes, the `!` channel is **not**
|
as today, but if its body raises or `try`-escapes, the `!` channel is **not**
|
||||||
inferred — declare it (`closure((x: i32) -> (i32, !) { ... })`). This keeps
|
inferred — declare it (`closure((x: i32) -> i32 ! { ... })`). This keeps
|
||||||
adding a `raise` from silently changing a lambda's type.
|
adding a `raise` from silently changing a lambda's type.
|
||||||
- **Program-wide union per shape.** All `Closure(<sig>) -> (T, !)` occurrences
|
- **Program-wide union per shape.** All `Closure(<sig>) -> T !` occurrences
|
||||||
with the same signature share one inferred-set node; the SCC pass unions
|
with the same signature share one inferred-set node; the SCC pass unions
|
||||||
every closure flowing into any matching slot.
|
every closure flowing into any matching slot.
|
||||||
- **FFI boundary.** A failable closure cannot be assigned to a non-failable
|
- **FFI boundary.** A failable closure cannot be assigned to a non-failable
|
||||||
@@ -3427,8 +3448,9 @@ const_decl = IDENT '::' expr ';'
|
|||||||
var_decl = IDENT ':=' expr ';'
|
var_decl = IDENT ':=' expr ';'
|
||||||
| IDENT ':' type '=' expr ';'
|
| IDENT ':' type '=' expr ';'
|
||||||
| IDENT ':' type ';'
|
| IDENT ':' type ';'
|
||||||
fn_decl = IDENT '::' '(' params? ')' ('->' type)? block
|
fn_decl = IDENT '::' '(' params? ')' ('->' ret_type)? block
|
||||||
| IDENT '::' block
|
| IDENT '::' block
|
||||||
|
ret_type = type ('!' IDENT?)? // trailing `!` = failable; channel outside any Tuple
|
||||||
enum_decl = IDENT '::' 'enum' '{' (IDENT ';')* '}'
|
enum_decl = IDENT '::' 'enum' '{' (IDENT ';')* '}'
|
||||||
struct_decl = IDENT '::' 'struct' '{' struct_member* '}'
|
struct_decl = IDENT '::' 'struct' '{' struct_member* '}'
|
||||||
struct_member = field_group | '#using' IDENT ';'
|
struct_member = field_group | '#using' IDENT ';'
|
||||||
@@ -3459,12 +3481,16 @@ binary = catch_expr (binop catch_expr)* // binop includes `or` (fall
|
|||||||
catch_expr = unary ('catch' ('(' IDENT ')')? (block | '==' '{' case_arm* else_arm? '}' | unary))?
|
catch_expr = unary ('catch' ('(' IDENT ')')? (block | '==' '{' case_arm* else_arm? '}' | unary))?
|
||||||
unary = ('-' | '!' | 'xx' | 'try' | 'cast' '(' type ')') postfix
|
unary = ('-' | '!' | 'xx' | 'try' | 'cast' '(' type ')') postfix
|
||||||
| postfix
|
| postfix
|
||||||
postfix = primary ('(' args? ')' | '.' IDENT | '.{' field_init_list '}')*
|
postfix = primary ('(' args? ')' | '.' IDENT | '.{' field_init_list '}'
|
||||||
|
| '.(' tuple_elem_list? ')')*
|
||||||
primary = INT | HEX_INT | BIN_INT | FLOAT | STRING | BOOL | IDENT | '---'
|
primary = INT | HEX_INT | BIN_INT | FLOAT | STRING | BOOL | IDENT | '---'
|
||||||
| '.' IDENT | '.' '{' field_init_list '}'
|
| '.' IDENT | '.' '{' field_init_list '}'
|
||||||
| '(' expr ')' | block | '#run' expr
|
| '.(' tuple_elem_list? ')' // tuple value (anonymous): .(a, b) / .(x = a) / .() / .(..xs)
|
||||||
|
| '(' expr ')' | block | '#run' expr // bare parens = grouping ONLY
|
||||||
field_init_list = field_init (',' field_init)* ','?
|
field_init_list = field_init (',' field_init)* ','?
|
||||||
field_init = IDENT '=' expr | IDENT | expr
|
field_init = IDENT '=' expr | IDENT | expr
|
||||||
|
tuple_elem_list = tuple_elem (',' tuple_elem)* ','?
|
||||||
|
tuple_elem = IDENT '=' expr | '..' expr | expr
|
||||||
if_expr = 'if' expr 'then' expr ('else' expr)?
|
if_expr = 'if' expr 'then' expr ('else' expr)?
|
||||||
| 'if' expr block ('else' block)?
|
| 'if' expr block ('else' block)?
|
||||||
match_expr = 'if' expr '==' '{' case_arm* else_arm? '}'
|
match_expr = 'if' expr '==' '{' case_arm* else_arm? '}'
|
||||||
@@ -3475,8 +3501,12 @@ lambda = '(' params? ')' ('->' type)? '=>' expr
|
|||||||
args = expr (',' expr)* ','?
|
args = expr (',' expr)* ','?
|
||||||
type = '$' IDENT | 'i32' | 'f32' | 'f64' | 'bool' | 'string'
|
type = '$' IDENT | 'i32' | 'f32' | 'f64' | 'bool' | 'string'
|
||||||
| 'Any' | 'Type' | '..' type | '[' expr ']' type | IDENT
|
| 'Any' | 'Type' | '..' type | '[' expr ']' type | IDENT
|
||||||
| '(' type (',' type)* ',' '!' IDENT? ')' // value-carrying failable
|
| 'Tuple' '(' tuple_type_list? ')' // tuple type: Tuple(A, B) / Tuple(x: A) / Tuple() / Tuple(..F(Ts))
|
||||||
|
| '(' type ')' // grouping (bare parens never form a tuple)
|
||||||
|
| '(' (type (',' type)*)? ')' '->' type ('!' IDENT?)? // function type (params optional; optional error channel)
|
||||||
| '!' IDENT? // pure failable (`!` / `!Named`)
|
| '!' IDENT? // pure failable (`!` / `!Named`)
|
||||||
|
tuple_type_list = tuple_type_elem (',' tuple_type_elem)* ','?
|
||||||
|
tuple_type_elem = IDENT ':' type | '..' type | type
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user