A block's value is now its last statement ONLY when that statement is a trailing expression with no `;`. A trailing `;` discards the value, leaving the block void. This makes value-vs-statement explicit and lets the compiler reject "this block was supposed to produce a value". Compiler: - Parser records `Block.produces_value` (last stmt is a no-`;` trailing expression) + `Block.discarded_semi` (the `;` that discarded a value), via `expectSemicolonAfter`. A trailing expression before `}` may now omit its `;` (previously a parse error). Match-arm and else-arm bodies are built value-producing regardless of the arm `;` (arms are exempt — the `;` is an arm terminator). - Lowering: `lowerBlockValue` / the block-expr path / `inferExprType` respect `produces_value`. A value-position block that discards its value is a hard error (`lowerValueBody` for function bodies; the value-context `.block` path for if/else branches, `catch` bodies, value bindings, match arms). Pure-failable `-> !` bodies (value rides the error channel) and a value-if whose branches are void are handled without false errors. - `defer`/`onfail` cleanup bodies lower as statements (void), so a trailing `;` there is fine. Migration (behavior-preserving — output unchanged): - stdlib + ~210 examples: dropped the trailing `;` on value-position last expressions. `format` now ends with an explicit `#insert "return result;"` (it relied on `#insert`-as-block-value, which `;` discards). - Two `main :: () -> s32` examples that relied on the old silent default-return got an explicit trailing `0`. - Rejection snapshots 0412 / 1013 regenerated (their quoted source lines lost a `;`); the diagnostics themselves are unchanged. Docs/tests: specs.md "Block values" section; examples 0040 (rules) + 0041 (rejection); 3 parser unit tests. Filed issue 0066 (pre-existing match-arm negated-literal phi-width quirk, surfaced not caused here). Gates: zig build, zig build test, run_examples.sh -> 343 passed, cross_compile.sh -> 7 passed (also refreshed its stale example names).
618 lines
14 KiB
Plaintext
618 lines
14 KiB
Plaintext
#import "modules/std.sx";
|
|
#import "modules/math/math.sx";
|
|
#import "modules/compiler.sx";
|
|
#import "modules/test.sx";
|
|
pkg :: #import "modules/testpkg";
|
|
|
|
Point :: struct { x, y: s32; }
|
|
|
|
Color :: enum { red; green; blue; }
|
|
|
|
Shape :: enum {
|
|
circle: f32;
|
|
rect: struct { w, h: f32; };
|
|
none;
|
|
}
|
|
|
|
Overlay :: union {
|
|
f: f32;
|
|
i: s32;
|
|
}
|
|
|
|
Vec2 :: union {
|
|
data: [2]f32;
|
|
struct { x, y: f32; };
|
|
}
|
|
|
|
Defaults :: struct {
|
|
a: s32;
|
|
b: s32 = 99;
|
|
c: s32 = ---;
|
|
}
|
|
|
|
OptNode :: struct {
|
|
value: s32;
|
|
next: ?s32;
|
|
}
|
|
|
|
OptInner :: struct { val: s32; }
|
|
|
|
OptOuter :: struct { inner: ?OptInner; }
|
|
|
|
MyFloat :: f64;
|
|
|
|
Perms :: enum flags { read; write; execute; }
|
|
|
|
Status :: enum u8 { ok; err; timeout; }
|
|
|
|
WindowFlags :: enum flags u32 { vsync :: 64; resizable :: 4; hidden :: 128; }
|
|
|
|
// --- Top-level functions ---
|
|
|
|
add :: (a: s32, b: s32) -> s32 { a + b }
|
|
|
|
mul :: (a: s32, b: s32) -> s32 { a * b }
|
|
|
|
identity :: (x: $T) -> T { x }
|
|
|
|
pair_add :: (a: $T, b: $U) -> s64 {
|
|
cast(s64) a + cast(s64) b
|
|
}
|
|
|
|
typed_sum :: (..args: []s32) -> s32 {
|
|
result := 0;
|
|
for args: (it) { result = result + it; }
|
|
result
|
|
}
|
|
|
|
apply :: (f: (s32, s32) -> s32, x: s32, y: s32) -> s32 {
|
|
f(x, y)
|
|
}
|
|
|
|
void_return :: () {
|
|
return;
|
|
}
|
|
|
|
implicit_return :: (x: s32) -> s32 {
|
|
x * 2
|
|
}
|
|
|
|
early_return :: (x: s32) -> s32 {
|
|
if x > 10 { return 99; }
|
|
x
|
|
}
|
|
|
|
vec3 :: (x: f32, y: f32, z: f32) -> Vector(3, f32) {
|
|
.[x, y, z]
|
|
}
|
|
|
|
point_sum :: (p: Point) -> s32 { p.x + p.y }
|
|
|
|
// #run compile-time constants
|
|
|
|
// #run compile-time constants
|
|
CT_VAL :: #run add(10, 15);
|
|
|
|
CT_MUL :: #run mul(6, 7);
|
|
|
|
CT_CHAIN :: #run add(CT_VAL, 5);
|
|
|
|
// #run compile-time optional tests
|
|
|
|
// #run compile-time optional tests
|
|
ct_opt_coalesce :: () -> s32 {
|
|
x: ?s32 = 42;
|
|
y: ?s32 = null;
|
|
return (x ?? 0) + (y ?? 99);
|
|
}
|
|
|
|
ct_opt_unwrap :: () -> s32 {
|
|
x: ?s32 = 77;
|
|
return x!;
|
|
}
|
|
|
|
ct_opt_guard :: () -> s32 {
|
|
x: ?s32 = 10;
|
|
if x == null { return -1; }
|
|
return x;
|
|
}
|
|
|
|
CT_OPT_COALESCE :: #run ct_opt_coalesce();
|
|
|
|
CT_OPT_UNWRAP :: #run ct_opt_unwrap();
|
|
|
|
CT_OPT_GUARD :: #run ct_opt_guard();
|
|
|
|
// #insert helpers
|
|
|
|
// #insert helpers
|
|
gen_code :: () -> string {
|
|
return "print(\"insert-ok\\n\");";
|
|
}
|
|
|
|
gen_val :: () -> string {
|
|
return "print(\"insert-gen: {}\\n\", 42);";
|
|
}
|
|
|
|
// --- Error handling (failable functions: sets, raise/try/catch/or/onfail) ---
|
|
|
|
SmokeErr :: error { Empty, BadDigit, Overflow }
|
|
|
|
// value-carrying, named set: raise three tags or succeed
|
|
|
|
// value-carrying, named set: raise three tags or succeed
|
|
sm_parse :: (n: s32) -> (s32, !SmokeErr) {
|
|
if n < 0 { raise error.BadDigit; }
|
|
if n == 0 { raise error.Empty; }
|
|
if n > 99 { raise error.Overflow; }
|
|
return n * 2;
|
|
}
|
|
|
|
// pure failable, inferred set (ad-hoc tag minted into `!`)
|
|
|
|
// pure failable, inferred set (ad-hoc tag minted into `!`)
|
|
sm_check :: (ok: bool) -> ! {
|
|
if !ok { raise error.NotReady; }
|
|
return;
|
|
}
|
|
|
|
// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr
|
|
|
|
// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr
|
|
sm_pair :: (a: s32, b: s32) -> (s32, s32, !) {
|
|
x := try sm_parse(a);
|
|
y := try sm_parse(b);
|
|
return (x, y);
|
|
}
|
|
|
|
// `catch` block that diverges (logs the tag, then returns a fallback)
|
|
|
|
// `catch` block that diverges (logs the tag, then returns a fallback)
|
|
sm_or_default :: (n: s32) -> s32 {
|
|
return sm_parse(n) catch e {
|
|
print(" logged {}\n", e);
|
|
return -1;
|
|
};
|
|
}
|
|
|
|
// `onfail` + `defer` interleave: cleanup runs only on the error path
|
|
|
|
// `onfail` + `defer` interleave: cleanup runs only on the error path
|
|
sm_acquire :: (fail: bool) -> (s32, !) {
|
|
defer print(" smoke defer A\n");
|
|
onfail print(" smoke onfail B\n");
|
|
if fail { raise error.Acquire; }
|
|
return 7;
|
|
}
|
|
|
|
// `or`-chain: try a, fall to try b; propagate if both fail
|
|
|
|
// `or`-chain: try a, fall to try b; propagate if both fail
|
|
sm_first :: (a: s32, b: s32) -> (s32, !) {
|
|
v := try sm_parse(a) or try sm_parse(b);
|
|
return v;
|
|
}
|
|
|
|
// --- Foreign function binding ---
|
|
|
|
// --- Foreign function binding ---
|
|
libc :: #library "c";
|
|
|
|
c_abs :: (n: s32) -> s32 #foreign libc "abs";
|
|
|
|
// --- Protocol declarations (Phase 1: static dispatch only) ---
|
|
|
|
Counter :: protocol {
|
|
inc :: ();
|
|
get :: () -> s32;
|
|
}
|
|
|
|
Summable :: protocol {
|
|
sum :: () -> s32;
|
|
}
|
|
|
|
SimpleCounter :: struct { val: s32; }
|
|
|
|
impl Counter for SimpleCounter {
|
|
inc :: (self: *SimpleCounter) { self.val += 1; }
|
|
get :: (self: *SimpleCounter) -> s32 { self.val }
|
|
}
|
|
|
|
impl Summable for Point {
|
|
sum :: (self: *Point) -> s32 { self.x + self.y }
|
|
}
|
|
|
|
// Phase 2: #inline protocol for dynamic dispatch
|
|
|
|
// Phase 2: #inline protocol for dynamic dispatch
|
|
Adder :: protocol #inline {
|
|
add :: (n: s32);
|
|
value :: () -> s32;
|
|
}
|
|
|
|
Accumulator :: struct {
|
|
total: s32;
|
|
}
|
|
|
|
impl Adder for Accumulator {
|
|
add :: (self: *Accumulator, n: s32) { self.total += n; }
|
|
value :: (self: *Accumulator) -> s32 { self.total }
|
|
}
|
|
|
|
Doubler :: struct { val: s32; }
|
|
|
|
impl Adder for Doubler {
|
|
add :: (self: *Doubler, n: s32) { self.val = self.val + n + n; }
|
|
value :: (self: *Doubler) -> s32 { self.val }
|
|
}
|
|
|
|
// Phase 4: default methods
|
|
|
|
// Phase 4: default methods
|
|
Repeater :: protocol {
|
|
say :: (msg: string);
|
|
say_twice :: (msg: string) {
|
|
self.say(msg);
|
|
self.say(msg);
|
|
}
|
|
}
|
|
|
|
Printer :: struct { count: s32; }
|
|
|
|
impl Repeater for Printer {
|
|
say :: (self: *Printer, msg: string) {
|
|
self.count += 1;
|
|
out(msg);
|
|
}
|
|
}
|
|
|
|
// P4 edge: Chained default→default calls
|
|
|
|
// P4 edge: Chained default→default calls
|
|
Chained :: protocol {
|
|
base :: (msg: string) -> s32;
|
|
wrap :: (msg: string) -> s32 {
|
|
self.base(msg) + 1
|
|
}
|
|
double_wrap :: (msg: string) -> s32 {
|
|
self.wrap(msg) + self.wrap(msg)
|
|
}
|
|
}
|
|
|
|
ChainImpl :: struct { val: s32; }
|
|
impl Chained for ChainImpl {
|
|
base :: (self: *ChainImpl, msg: string) -> s32 {
|
|
self.val += 1;
|
|
msg.len
|
|
}
|
|
}
|
|
|
|
// Phase 5: Self type
|
|
|
|
// Phase 5: Self type
|
|
Eq :: protocol {
|
|
eq :: (other: Self) -> bool;
|
|
}
|
|
|
|
impl Eq for Point {
|
|
eq :: (self: *Point, other: Point) -> bool {
|
|
self.x == other.x and self.y == other.y
|
|
}
|
|
}
|
|
|
|
Cloneable :: protocol {
|
|
clone :: () -> Self;
|
|
}
|
|
|
|
impl Cloneable for Point {
|
|
clone :: (self: *Point) -> Point {
|
|
Point.{ x = self.x, y = self.y }
|
|
}
|
|
}
|
|
|
|
impl Eq for s64 {
|
|
eq :: (self: *s64, other: s64) -> bool {
|
|
self.* == other
|
|
}
|
|
}
|
|
|
|
// Phase 6: Generic constraints
|
|
|
|
// Phase 6: Generic constraints
|
|
are_equal :: ($T: Type/Eq, a: T, b: T) -> bool {
|
|
a.eq(b)
|
|
}
|
|
|
|
Hashable :: protocol {
|
|
hash :: () -> s64;
|
|
}
|
|
|
|
impl Hashable for Point {
|
|
hash :: (self: *Point) -> s64 {
|
|
xx self.x * 31 + xx self.y
|
|
}
|
|
}
|
|
|
|
eq_and_hash :: ($T: Type/Eq/Hashable, a: T, b: T) -> bool {
|
|
if a.hash() != b.hash() { return false; }
|
|
a.eq(b)
|
|
}
|
|
|
|
// P6.4: inline constraint syntax ($T/Protocol)
|
|
|
|
// P6.4: inline constraint syntax ($T/Protocol)
|
|
sum_of_inline :: (a: $T/Summable, b: T) -> s32 {
|
|
a.sum() + b.sum()
|
|
}
|
|
|
|
// Phase 7: Generic struct impls
|
|
|
|
// Phase 7: Generic struct impls
|
|
Pair :: struct ($T: Type) {
|
|
a: T;
|
|
b: T;
|
|
}
|
|
|
|
impl Summable for Pair($T) {
|
|
sum :: (self: *Pair(T)) -> s32 {
|
|
xx self.a + xx self.b
|
|
}
|
|
}
|
|
|
|
// P6.5: Struct type param constraints
|
|
|
|
// P6.5: Struct type param constraints
|
|
SumBox :: struct ($T: Type/Summable) {
|
|
val: T;
|
|
}
|
|
|
|
// ============================================================
|
|
// Struct constants test
|
|
|
|
// ============================================================
|
|
// Struct constants test
|
|
Phys :: struct {
|
|
x, y: f32;
|
|
GRAVITY :f32: 9.81;
|
|
MAX_SPEED :: 100;
|
|
}
|
|
|
|
// Init block test struct
|
|
|
|
// Init block test struct
|
|
Builder :: struct {
|
|
total: s32;
|
|
count: s32;
|
|
|
|
add :: (self: *Builder, val: s32) {
|
|
self.total += val;
|
|
self.count += 1;
|
|
}
|
|
}
|
|
|
|
// Global variable for address-of test
|
|
|
|
// Global variable for address-of test
|
|
g_smoke_val : s32 = 42;
|
|
|
|
write_to_ptr :: (p: *s32) {
|
|
p.* = 99;
|
|
}
|
|
|
|
main :: () {
|
|
|
|
// ========================================================
|
|
// PROTOCOLS (Phase 1: static dispatch)
|
|
// ========================================================
|
|
print("=== Protocols ===\n");
|
|
|
|
// P1.1: Basic protocol + impl, direct call on concrete type
|
|
{
|
|
sc := SimpleCounter.{ val = 0 };
|
|
sc.inc();
|
|
sc.inc();
|
|
sc.inc();
|
|
print("P1.1: {}\n", sc.get());
|
|
}
|
|
|
|
// P1.2: impl in separate scope (retroactive conformance)
|
|
{
|
|
p := Point.{ x = 10, y = 20 };
|
|
print("P1.2: {}\n", p.sum());
|
|
}
|
|
|
|
// P2.1: #inline protocol — xx conversion + dynamic dispatch
|
|
{
|
|
acc := Accumulator.{ total = 0 };
|
|
a : Adder = xx @acc;
|
|
a.add(10);
|
|
a.add(20);
|
|
a.add(12);
|
|
print("P2.1: {}\n", a.value());
|
|
}
|
|
|
|
// P2.2: pass protocol value to function
|
|
{
|
|
use_adder :: (a: Adder, n: s32) -> s32 {
|
|
a.add(n);
|
|
a.value()
|
|
}
|
|
acc := Accumulator.{ total = 100 };
|
|
result := use_adder(xx @acc, 50);
|
|
print("P2.2: {}\n", result);
|
|
}
|
|
|
|
// P2.3: different impls through same protocol type
|
|
{
|
|
acc := Accumulator.{ total = 0 };
|
|
dbl := Doubler.{ val = 0 };
|
|
a1 : Adder = xx @acc;
|
|
a2 : Adder = xx @dbl;
|
|
a1.add(5);
|
|
a2.add(5);
|
|
print("P2.3: {} {}\n", a1.value(), a2.value());
|
|
}
|
|
|
|
// P3.1: vtable-pointer protocol (default, no #inline)
|
|
{
|
|
sc := SimpleCounter.{ val = 0 };
|
|
c : Counter = xx @sc;
|
|
c.inc();
|
|
c.inc();
|
|
c.inc();
|
|
c.inc();
|
|
c.inc();
|
|
print("P3.1: {}\n", c.get());
|
|
}
|
|
|
|
// P3.2: vtable protocol passed to function
|
|
{
|
|
use_counter :: (c: Counter) -> s32 {
|
|
c.inc();
|
|
c.inc();
|
|
c.get()
|
|
}
|
|
sc := SimpleCounter.{ val = 10 };
|
|
result := use_counter(xx @sc);
|
|
print("P3.2: {}\n", result);
|
|
}
|
|
|
|
// P4.1: default method calls required method (static dispatch)
|
|
{
|
|
pr := Printer.{ count = 0 };
|
|
pr.say_twice("hi ");
|
|
print("\nP4.1: {}\n", pr.count);
|
|
}
|
|
|
|
// P4.2: default method via dynamic dispatch (vtable)
|
|
{
|
|
pr := Printer.{ count = 0 };
|
|
r : Repeater = xx @pr;
|
|
r.say_twice("yo ");
|
|
print("\nP4.2: {}\n", pr.count);
|
|
}
|
|
|
|
// P4.3: chained default→default calls via vtable
|
|
{
|
|
ci := ChainImpl.{ val = 0 };
|
|
ch : Chained = xx @ci;
|
|
// double_wrap calls wrap twice, wrap calls base once each
|
|
// base("hi") returns 2 (len), wrap adds 1 → 3, double_wrap = 3 + 3 = 6
|
|
result := ch.double_wrap("hi");
|
|
// base was called 2 times (once per wrap call)
|
|
print("P4.3: {} {}\n", result, ci.val);
|
|
}
|
|
|
|
// P5.1: Self type in protocol — static dispatch
|
|
{
|
|
p1 := Point.{ x = 1, y = 2 };
|
|
p2 := Point.{ x = 1, y = 2 };
|
|
p3 := Point.{ x = 3, y = 4 };
|
|
print("P5.1: {} {}\n", p1.eq(p2), p1.eq(p3));
|
|
}
|
|
|
|
// P5.2: Self in return position
|
|
{
|
|
p := Point.{ x = 10, y = 20 };
|
|
p2 := p.clone();
|
|
print("P5.2: {} {}\n", p2.x, p2.y);
|
|
}
|
|
|
|
// P5.5: impl for primitive type
|
|
{
|
|
x := 42;
|
|
y := 42;
|
|
z := 99;
|
|
r1 := x.eq(y);
|
|
r2 := x.eq(z);
|
|
print("P5.5: {} {}\n", r1, r2);
|
|
}
|
|
|
|
// P5.3: Self with dynamic dispatch (erased to *void)
|
|
{
|
|
p1 := Point.{ x = 1, y = 2 };
|
|
p2 := Point.{ x = 1, y = 2 };
|
|
p3 := Point.{ x = 3, y = 4 };
|
|
e : Eq = xx p1;
|
|
print("P5.3: {} {}\n", e.eq(p2), e.eq(p3));
|
|
}
|
|
|
|
// P6.1: Single constraint — constrained generic function
|
|
{
|
|
p1 := Point.{ x = 1, y = 2 };
|
|
p2 := Point.{ x = 1, y = 2 };
|
|
p3 := Point.{ x = 3, y = 4 };
|
|
print("P6.1: {} {}\n", are_equal(p1, p2), are_equal(p1, p3));
|
|
}
|
|
|
|
// P6.2: Constraint with primitive type
|
|
{
|
|
print("P6.2: {} {}\n", are_equal(42, 42), are_equal(42, 99));
|
|
}
|
|
|
|
// P6.3: Multiple constraints
|
|
{
|
|
p1 := Point.{ x = 1, y = 2 };
|
|
p2 := Point.{ x = 1, y = 2 };
|
|
p3 := Point.{ x = 3, y = 4 };
|
|
print("P6.3: {} {}\n", eq_and_hash(p1, p2), eq_and_hash(p1, p3));
|
|
}
|
|
|
|
// P6.4: inline constraint syntax ($T/Protocol)
|
|
{
|
|
// sum_of_inline uses $T/Summable inline (not $T: Type/Summable)
|
|
p1 := Point.{ x = 10, y = 20 };
|
|
p2 := Point.{ x = 3, y = 7 };
|
|
print("P6.4: {}\n", sum_of_inline(p1, p2));
|
|
}
|
|
|
|
// P6.5: Struct type param constraints ($T: Type/Summable)
|
|
{
|
|
box := SumBox(Point).{ val = Point.{ x = 5, y = 15 } };
|
|
print("P6.5: {}\n", box.val.sum());
|
|
}
|
|
|
|
// P7.1: impl for generic struct
|
|
{
|
|
p := Pair(s32).{ a = 10, b = 20 };
|
|
print("P7.1: {}\n", p.sum());
|
|
}
|
|
|
|
// P7.2: generic struct impl with different type arg
|
|
{
|
|
p1 := Pair(s32).{ a = 3, b = 7 };
|
|
p2 := Pair(s64).{ a = 100, b = 200 };
|
|
print("P7.2: {} {}\n", p1.sum(), p2.sum());
|
|
}
|
|
|
|
// P2.4: xx in function return position (tested in standalone test_return.sx)
|
|
// Covered by: make_adder :: (acc: *Accumulator) -> Adder { xx acc; }
|
|
|
|
// P2.6: protocol values in arrays
|
|
{
|
|
acc := Accumulator.{ total = 0 };
|
|
dbl := Doubler.{ val = 0 };
|
|
adders : [2]Adder = .[xx @acc, xx @dbl];
|
|
i := 0;
|
|
while i < 2 {
|
|
adders[i].add(5);
|
|
i += 1;
|
|
}
|
|
print("P2.6: {} {}\n", acc.total, dbl.val);
|
|
}
|
|
|
|
// P2.7: xx on inline struct literal (no intermediate variable)
|
|
{
|
|
use_adder :: (a: Adder) -> s32 { a.add(10); a.value() }
|
|
result := use_adder(xx Accumulator.{ total = 5 });
|
|
print("P2.7: {}\n", result);
|
|
}
|
|
|
|
// P3.3: xx on inline struct literal with vtable protocol
|
|
{
|
|
use_counter :: (c: Counter) -> s32 { c.inc(); c.inc(); c.get() }
|
|
result := use_counter(xx SimpleCounter.{ val = 100 });
|
|
print("P3.3: {}\n", result);
|
|
}
|
|
}
|