@shd101wyy/yo 0.1.27 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -144,6 +144,9 @@ counter.* = (counter.* + i32(1));
144
144
  - Use `*(T)` for raw pointers
145
145
  - Model nullable pointers as `Option(*(T))` or `?*(T)`, not sentinel integers
146
146
  - Constructor syntax: `Box(T)(value)` — NOT `Box(T).new(value)`
147
+ - Single-payload objects may use `(*) : T`; access the payload with `value.*`.
148
+ This is a value payload accessor for object values, while pointer dereference
149
+ still applies when the receiver has pointer type.
147
150
  - For self-referential `object` types, use `Box(Self)` to break the recursive cycle:
148
151
 
149
152
  ```rust
@@ -18,6 +18,7 @@ These commands and patterns are aimed at normal Yo projects that use the public
18
18
  | Inspect generated C | `yo compile main.yo --emit-c --skip-c-compiler` |
19
19
  | Run tests in one file | `yo test ./tests/main.test.yo --parallel 1` |
20
20
  | Filter tests by name | `yo test ./tests/main.test.yo --test-name-pattern "Name"` |
21
+ | Tune test batch size | `yo test ./tests/main.test.yo --test-batch-size 100` |
21
22
  | Format Yo source | `yo fmt ./src ./tests` |
22
23
  | Check Yo formatting | `yo fmt --check` |
23
24
  | Generate docs for project | `yo doc ./src` |
@@ -104,6 +105,7 @@ yo test ./tests/main.test.yo --bail --verbose --parallel 1
104
105
 
105
106
  - Use `--parallel 1` for focused, readable single-file runs
106
107
  - Use `--test-name-pattern` when a file contains many tests
108
+ - Use `--test-batch-size N` if a large `.test.yo` file generates C that compiles slowly or looks stuck
107
109
  - Use `yo build test` when the repository's main test workflow is defined in `build.yo`
108
110
 
109
111
  ## Formatting
@@ -79,6 +79,16 @@ if(done, println("done"), println("pending"));
79
79
  - `if(a, b)` and `if(a, b, c)` are macro forms over `cond`
80
80
  - Write `return(value)` or `return()`; `return value` is invalid.
81
81
  - Write `escape(value)` or `escape()`; `escape value` is invalid.
82
+ - If a `match`/`cond` branch returns an enum variant and inference fails, qualify
83
+ the variant with its enum type: `TypeValue.Unit` instead of `.Unit`.
84
+ - Do not match enum payload literals directly, e.g. avoid `.Some(false)` and
85
+ `.Some(true)` as sibling branches. Match `.Some(value)` once, then branch with
86
+ `if(value, ...)` or `cond(...)` inside the arm; otherwise generated C can
87
+ contain duplicate enum `case` labels.
88
+ - In large enum matches, avoid binding a pattern variable with the same name as a
89
+ variant field (for example, prefer `struct_field_types` over `field_types`).
90
+ This can currently produce invalid generated C in some self-hosted codegen
91
+ paths.
82
92
 
83
93
  ## String types
84
94
 
@@ -140,6 +150,8 @@ impl(Counter,
140
150
  ```
141
151
 
142
152
  - No space between a function type and its body: `(fn(...) -> T)(...)`
153
+ - Top-level aliases for function types need parentheses too:
154
+ `Callback :: (fn(x : i32) -> i32);`, not `Callback :: fn(x : i32) -> i32;`
143
155
  - Use `Self` in method signatures and in type definitions for recursive references (the type name is not available during its own definition)
144
156
  - `Self` also works inside generic type constructors — it refers to the current instantiation (e.g., `Tree(T)` inside `Tree`). Use `recur(args)` only when type arguments differ from the current instantiation.
145
157
  - Use `struct(...)` for record and effect-record types. The legacy `module(...)`,
@@ -250,15 +262,28 @@ text := match(value,
250
262
  Three destructuring shapes for arms (mix freely across arms):
251
263
 
252
264
  ```rust
253
- Shape :: enum(Circle(radius : i32), Rectangle(width : i32, height : i32));
265
+ Shape :: enum(
266
+ Circle(radius : i32),
267
+ Rectangle(width : i32, height : i32),
268
+ Triangle(base : i32, height : i32, label : str)
269
+ );
254
270
 
255
271
  match(s,
256
- .Circle(r) => (r * r), // positional
257
- .Rectangle(width: w, height: h) => (w * h), // labeled
258
- .Rectangle({width, height: h}) => (width * h) // curly shorthand
272
+ // Preferred curly shorthand names only the fields you use.
273
+ .Triangle({base, height: h}) => (base * h),
274
+
275
+ // Also OK — labeled (label : var) pairs; order-free, partial matches OK.
276
+ .Circle(radius: r) => (r * r),
277
+
278
+ // ⚠️ Avoid for 2+ field variants — positional with `_` is brittle when
279
+ // a field is added and harder to read (each `_` requires counting).
280
+ // OK when the variant has one field, or when every field is named.
281
+ .Rectangle(w, h) => (w * h)
259
282
  )
260
283
  ```
261
284
 
285
+ **Preferred form**: `.Variant({label, label: alias})`. Names only the fields the arm binds, so adding a field to the variant later doesn't silently break every arm. `tests/match_curly.test.yo` is the spec.
286
+
262
287
  Curly `{a, b: c}` is sugar for `(a: a, b: c)` — order-free, supports partial matches (omit fields). Use `{label: _}` to ignore a specific field. Bare `{_}` and empty `{}` are rejected.
263
288
 
264
289
  > **Critical**: Within a single match arm, you must use **either all positional or all named** field patterns. Mixing positional and named fields in the same arm (e.g., `.Foo(x, y: z, w)`) causes C codegen to emit undeclared identifiers for the named fields. This is a parser/codegen limitation — do not mix.
@@ -553,6 +578,23 @@ Variable :: object(name : String, type : TypeValue);
553
578
  Variable :: object(name : String, ty : TypeValue);
554
579
  ```
555
580
 
581
+ ### 1-element array literals require a trailing comma
582
+
583
+ `[expr]` without a trailing comma is **parsed as a Slice type** `Slice(expr)`, not an array literal. To create a 1-element array value, add a trailing comma:
584
+
585
+ ```rust
586
+ // WRONG — parsed as Slice type, not array literal:
587
+ arr := [i32(42)];
588
+
589
+ // CORRECT — trailing comma makes it an array literal:
590
+ arr := [i32(42),];
591
+
592
+ // Multi-element arrays work fine (comma separator detected):
593
+ arr2 := [i32(1), i32(2), i32(3)]; // ✓
594
+ ```
595
+
596
+ This also applies inside source strings in proto-evaluator tests.
597
+
556
598
  ### ArrayList indexing uses call syntax
557
599
 
558
600
  ```rust
@@ -696,6 +738,13 @@ given(exn) := Exception(throw: ((err) -> {
696
738
  }));
697
739
  do_something(using(exn));
698
740
 
741
+ // CORRECT — even after process-exit helpers, satisfy the handler's resume type:
742
+ given(exn2) := Exception(throw: ((err) -> {
743
+ eprintln(err.to_string());
744
+ exit(int(1));
745
+ escape(); // required because exit() returns unit, not the handler ResumeType
746
+ }));
747
+
699
748
  // CORRECT — escape inside a closure passed as argument:
700
749
  result := match(opt, .Some(x) => x, .None => {
701
750
  // This is NOT a nested function — use return:
@@ -1036,3 +1085,102 @@ match(outer_val,
1036
1085
  .None => { fallback() } // ← outer .None arm
1037
1086
  ) // ← closes outer match
1038
1087
  ```
1088
+
1089
+ ### Nested enum patterns in match are NOT supported
1090
+
1091
+ Yo does **not** support nested enum patterns inside a single match arm.
1092
+ You cannot write `.Some(.IntLit(n))` — this is a parser error.
1093
+
1094
+ ```rust
1095
+ // ❌ WRONG — nested enum pattern, parser error:
1096
+ match(v.get(usize(0)),
1097
+ .Some(.IntLit(n)) => assert(n.as_str() == "3", "ok"),
1098
+ _ => assert(false, "err")
1099
+ )
1100
+
1101
+ // ✅ CORRECT — two-level match:
1102
+ match(v.get(usize(0)),
1103
+ .Some(x) => match(x, .IntLit(n) => assert(n.as_str() == "3", "ok"), _ => assert(false, "err")),
1104
+ .None => assert(false, "err")
1105
+ )
1106
+ ```
1107
+
1108
+ This applies to ALL nested enum patterns: `.Some(.BoolVal(b))`, `.Some(.ArrayVal(arr))`, etc. — always use a two-level match.
1109
+
1110
+ ### `get_callee()` returns ExprVal directly, not an Option-wrapped EnumVal
1111
+
1112
+ In the proto-evaluator source strings (`evaluate_module_body`), `ExprVal.get_callee()` on a FnCall returns the callee `ExprVal` directly — NOT wrapped in an `Option` EnumVal. Chaining `.is_some()` fails with SIGABRT because `is_some()` requires an `EnumVal` receiver.
1113
+
1114
+ ```rust
1115
+ // ❌ SIGABRT — get_callee() returns ExprVal, not Option(EnumVal)
1116
+ result := quote(foo(i64(1))).get_callee().is_some();
1117
+
1118
+ // ✅ Chain .is_atom() or .is_fn_call() on the returned ExprVal
1119
+ result := quote(foo(i64(1))).get_callee().is_atom(); // true: callee "foo" is an atom
1120
+ result := quote(foo(i64(1))).get_callee().is_fn_call(); // false: callee "foo" is not a fn call
1121
+ ```
1122
+
1123
+ Similarly, calling `get_callee()` on an Atom causes the overall evaluation to fail — do not test the Atom case via `get_callee()` in source strings.
1124
+
1125
+ ### Source-string evaluation pitfalls (proto-evaluator tests)
1126
+
1127
+ When writing source strings passed to `evaluate_module_body` in proto-evaluator tests:
1128
+
1129
+ **`cond` form**: Always use the `cond(condition => value, true => fallback)` form, NOT `cond(condition, value, fallback)`. The 3-arg form does NOT work inside lambdas or recursive functions in source strings.
1130
+
1131
+ ```
1132
+ // ❌ WRONG — crashes inside lambdas and recursive functions
1133
+ cond((n <= i32(1)), i32(1), (n * recur((n - i32(1)))))
1134
+
1135
+ // ✅ CORRECT
1136
+ cond((n <= i32(1)) => i32(1), true => (n * recur((n - i32(1)))))
1137
+ ```
1138
+
1139
+ **Recursive functions**: Use `recur(...)` for self-recursion inside named `::` functions. Never call the function by name from inside its own body.
1140
+
1141
+ **Chaining function calls with operators**: `f(a) + f(b) + f(c)` throws an exception. Use fold over an array instead:
1142
+
1143
+ ```
1144
+ // ❌ WRONG — exception in source strings
1145
+ result := abs_val(i32(3)) + abs_val(i32(1)) + abs_val(i32(4));
1146
+
1147
+ // ✅ CORRECT
1148
+ arr := [i32(3), i32(1), i32(4)];
1149
+ result := arr.fold(i32(0), (fn(acc : i32, x : i32) -> i32)((acc + abs_val(x))));
1150
+ ```
1151
+
1152
+ **Empty array `[]` in cond branches**: `cond(condition => [x], true => [])` crashes because the empty array type is unknown. Avoid empty array literals in conditional branches inside `flat_map` lambdas.
1153
+
1154
+ **Option types**: Must use `Option(T).Some(val)` not `Option.Some(val)`. `Option(T).None` with a type annotation crashes — use `r := Option(i32).None` without annotation. `.is_none()` is not supported; use `!(r.is_some())`. `and_then(f)` returns the raw value (not wrapped in Option), so calling `.unwrap_or()` on the result crashes.
1155
+
1156
+ **Number literals**: `i32(-3)` crashes — use `(i32(0) - i32(3))`. `i32.as_usize()` / `usize.as_i32()` not supported.
1157
+
1158
+ **Fibonacci without tmp variable**: `b = (a + b); a = (b - a)` computes fib correctly without a temp variable. After N iterations, `a` holds fib(N) and `b` holds fib(N+1).
1159
+
1160
+ **3-term multiplication in source strings**: `(x * x * x)` causes an exception in evaluated source strings. Break it into a block:
1161
+
1162
+ ```
1163
+ // ❌ WRONG — causes exception
1164
+ cubes := arr.map((fn(x : i32) -> i32)((x * x * x)));
1165
+
1166
+ // ✅ CORRECT — use a block with a local binding
1167
+ cubes := arr.map((fn(x : i32) -> i32)({
1168
+ sq := (x * x);
1169
+ (sq * x)
1170
+ }));
1171
+ ```
1172
+
1173
+ **3-term sum in fold on tuples**: `(acc + p.0 + p.1)` inside a fold lambda on tuple pairs crashes. Always map pairs to scalars first, then fold:
1174
+
1175
+ ```
1176
+ // ❌ WRONG — crashes in fold on (i32, i32) tuples
1177
+ total := pairs.fold(i32(0), (fn(acc : i32, p : (i32, i32)) -> i32)((acc + p.0 + p.1)));
1178
+
1179
+ // ✅ CORRECT — map to scalars first, then fold
1180
+ sums := pairs.map((fn(p : (i32, i32)) -> i32)((p.0 + p.1)));
1181
+ total := sums.fold(i32(0), (fn(acc : i32, x : i32) -> i32)((acc + x)));
1182
+ ```
1183
+
1184
+ **`&&` in `cond` conditions inside `while` body**: Crashes. Avoid by restructuring (e.g., start loop at 1 instead of 0 to eliminate the `&& (i > 0)` guard).
1185
+
1186
+ **Test API format**: Use `evaluate_module_body(exprs, &(env))` (reference syntax, returns `Option`). Match with function-style `match(result, .None => ..., .Some(m) => ...)`. Do NOT use block-style `match(result) { ... }` — it causes a parse error ("Paren-less function and operator calls are not supported").