Files
sx/examples/0019-basic-dot-shorthand.sx
agra bdd0e96d78 feat(lang): block value requires no trailing ; (Rust-style)
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).
2026-06-02 09:23:50 +03:00

296 lines
8.3 KiB
Plaintext

#import "modules/std.sx";
// ============================================================
// Dot-shorthand tests: .identifier(args) unification
// Tests both tagged enum backward compat and struct static methods
// ============================================================
// --- Type declarations ---
Color :: enum { red; green; blue; }
Shape :: enum {
circle: f32;
rect: struct { w, h: f32; };
none;
}
Vec2 :: struct {
x: f32;
y: f32;
create :: (x: f32, y: f32) -> Vec2 { Vec2.{ x = x, y = y } }
zero :: () -> Vec2 { Vec2.{ x = 0.0, y = 0.0 } }
unit_x :: () -> Vec2 { Vec2.{ x = 1.0, y = 0.0 } }
add :: (a: Vec2, b: Vec2) -> Vec2 { Vec2.{ x = a.x + b.x, y = a.y + b.y } }
scale :: (v: Vec2, s: f32) -> Vec2 { Vec2.{ x = v.x * s, y = v.y * s } }
len :: (v: Vec2) -> s32 { xx (v.x + v.y) }
}
EdgeInsets :: struct {
top: f32;
right: f32;
bottom: f32;
left: f32;
all :: (v: f32) -> EdgeInsets { EdgeInsets.{ top = v, right = v, bottom = v, left = v } }
symmetric :: (h: f32, v: f32) -> EdgeInsets { EdgeInsets.{ top = v, right = h, bottom = v, left = h } }
horizontal :: (h: f32) -> EdgeInsets { EdgeInsets.{ top = 0.0, right = h, bottom = 0.0, left = h } }
}
Trio :: struct {
a: s32;
b: s32;
c: s32;
make :: (a: s32, b: s32, c: s32) -> Trio { Trio.{ a = a, b = b, c = c } }
sum :: (t: Trio) -> s32 { t.a + t.b + t.c }
}
Result :: enum {
ok: s32;
err: string;
}
main :: () {
// ============================================================
// SECTION 1: Tagged enum backward compatibility
// ============================================================
print("--- tagged enum compat ---\n");
// T1: .variant(payload) in typed variable declaration
{
sh : Shape = .circle(3.14);
print("T1: {}\n", sh.circle);
}
// T2: Bare .variant (no payload) in typed variable
{
sh : Shape = .none;
ms := if sh == {
case .circle: 1;
case .rect: 2;
case .none: 3;
};
print("T2: {}\n", ms);
}
// T3: .variant with struct payload
{
sh : Shape = .rect(.{ 5.0, 3.0 });
print("T3: {} {}\n", sh.rect.w, sh.rect.h);
}
// T4: Qualified Type.variant(payload) still works
{
sh := Shape.circle(2.71);
print("T4: {}\n", sh.circle);
}
// T5: Match with payload capture
{
sh : Shape = .circle(9.5);
if sh == {
case .circle: (r) { print("T5: {}\n", r); }
case .rect: (sz) { print("T5: rect\n"); }
case .none: print("T5: none\n");
}
}
// T6: Return .variant(payload) from function
{
make_shape :: (r: f32) -> Shape { .circle(r) }
sh := make_shape(4.2);
print("T6: {}\n", sh.circle);
}
// T7: Reassignment with .variant(payload) and bare .variant
{
sh : Shape = .circle(1.0);
print("T7a: {}\n", sh.circle);
sh = .rect(.{ 2.0, 3.0 });
print("T7b: {} {}\n", sh.rect.w, sh.rect.h);
sh = .none;
ms := if sh == {
case .circle: 1;
case .rect: 2;
case .none: 3;
};
print("T7c: {}\n", ms);
}
// T8: .variant(payload) as function argument (match-as-expression)
{
describe :: (sh: Shape) -> s32 {
if sh == {
case .circle: 10;
case .rect: 20;
case .none: 30;
}
}
print("T8a: {}\n", describe(.circle(7.0)));
print("T8b: {}\n", describe(.rect(.{ 3.0, 4.0 })));
print("T8c: {}\n", describe(.none));
}
// T9: Tagged enum with string payload
{
r : Result = .ok(42);
ms := if r == {
case .ok: (v) { v }
case .err: (e) { -1 }
};
print("T9: {}\n", ms);
}
// T10: Match as expression returning tagged enum
{
select :: (n: s32) -> Shape {
if n == {
case 0: .none;
case 1: .circle(1.0);
else: .rect(.{ 9.0, 9.0 });
}
}
print("T10a: {}\n", if select(0) == { case .none: 1; else: 0; });
print("T10b: {}\n", select(1).circle);
print("T10c: {}\n", select(2).rect.w);
}
// ============================================================
// SECTION 2: Struct static method shorthand
// ============================================================
print("--- struct static shorthand ---\n");
// S1: .method(args) as function argument (the motivating use case)
{
print_vec :: (v: Vec2) { print("S1: {} {}\n", v.x, v.y); }
print_vec(.create(3.0, 4.0));
}
// S2: .method(args) in typed variable declaration
{
v : Vec2 = .create(5.0, 6.0);
print("S2: {} {}\n", v.x, v.y);
}
// S3: Return .method(args) from function with return type
{
make_vec :: () -> Vec2 { .create(7.0, 8.0) }
v := make_vec();
print("S3: {} {}\n", v.x, v.y);
}
// S4: Zero-arg static method (factory)
{
print_vec :: (v: Vec2) { print("S4: {} {}\n", v.x, v.y); }
print_vec(.zero());
print_vec(.unit_x());
}
// S5: Three-arg static method (proves multi-arg works)
{
print_trio :: (t: Trio) { print("S5: {}\n", t.a + t.b + t.c); }
print_trio(.make(10, 20, 30));
}
// S6: Two-arg shorthand matching the EdgeInsets use case
{
apply_insets :: (ei: EdgeInsets) { print("S6: {} {} {} {}\n", ei.top, ei.right, ei.bottom, ei.left); }
apply_insets(.all(8.0));
apply_insets(.symmetric(16.0, 8.0));
apply_insets(.horizontal(12.0));
}
// S7: Result of .method() used in further computation
{
v : Vec2 = .create(3.0, 4.0);
print("S7: {}\n", Vec2.len(v));
}
// S8: Chained qualified + shorthand — ensure both work together
{
v1 := Vec2.create(1.0, 2.0);
print_vec :: (v: Vec2) { print("S8: {} {}\n", v.x, v.y); }
print_vec(.create(3.0, 4.0));
print("S8q: {} {}\n", v1.x, v1.y);
}
// S9: Static method taking struct of same type as args
{
v : Vec2 = .add(.create(1.0, 2.0), .create(3.0, 4.0));
print("S9: {} {}\n", v.x, v.y);
}
// S10: Static method + piped result
{
v := Vec2.create(2.0, 3.0) |> Vec2.scale(2.0);
print("S10: {} {}\n", v.x, v.y);
}
// ============================================================
// SECTION 3: Edge cases mixing both
// ============================================================
print("--- edge cases ---\n");
// E1: Both tagged enum and struct shorthand in same scope
{
sh : Shape = .circle(5.0);
v : Vec2 = .create(1.0, 2.0);
print("E1: {} {} {}\n", sh.circle, v.x, v.y);
}
// E2: Function taking both types — each resolves correctly
{
use_both :: (sh: Shape, v: Vec2) {
ms : s32 = 0;
if sh == { case .circle: (r) { ms = xx r; } else: {} }
print("E2: {} {} {}\n", ms, v.x, v.y);
}
use_both(.circle(9.0), .create(1.0, 2.0));
}
// E3: Bare .variant (no parens) as function arg
{
check_none :: (sh: Shape) -> s32 {
if sh == { case .none: 1; else: 0; }
}
print("E3: {}\n", check_none(.none));
}
// E4: Nested shorthand — .method takes a struct param created with shorthand
// (inner .create must resolve via the method's parameter type, not the outer type)
{
v : Vec2 = .add(Vec2.create(1.0, 2.0), Vec2.create(3.0, 4.0));
print("E4: {} {}\n", v.x, v.y);
}
// E5: Tagged enum .variant(payload) in match-as-expression
{
sh : Shape = .circle(42.0);
r : s32 = 0;
if sh == {
case .circle: (v) { r = xx v; }
case .rect: (sz) { r = xx sz.w; }
case .none: r = xx -1;
}
print("E5: {}\n", r);
}
// E6: Color enum (plain, not tagged) still works with bare .variant
{
c : Color = .green;
ci : s32 = xx c;
print("E6: {}\n", ci);
}
// E7: Struct shorthand in typed variable, then pass to function
{
ei : EdgeInsets = .symmetric(10.0, 20.0);
show :: (e: EdgeInsets) { print("E7: {} {}\n", e.top, e.left); }
show(ei);
}
print("=== DONE ===\n");
}