@shd101wyy/yo 0.1.23 → 0.1.25

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.
Files changed (88) hide show
  1. package/.github/skills/yo-async-effects/async-effects-recipes.md +74 -1
  2. package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +115 -0
  3. package/.github/skills/yo-syntax/syntax-cheatsheet.md +626 -6
  4. package/out/cjs/index.cjs +563 -564
  5. package/out/cjs/yo-cli.cjs +722 -715
  6. package/out/cjs/yo-lsp.cjs +601 -602
  7. package/out/esm/index.mjs +506 -507
  8. package/out/types/src/codegen/utils/index.d.ts +1 -0
  9. package/out/types/src/expr.d.ts +1 -0
  10. package/out/types/src/parser.d.ts +1 -0
  11. package/out/types/src/test-runner.d.ts +1 -0
  12. package/out/types/tsconfig.tsbuildinfo +1 -1
  13. package/package.json +1 -1
  14. package/std/build.yo +2 -2
  15. package/std/collections/array_list.yo +26 -0
  16. package/std/imm/map.yo +15 -15
  17. package/std/imm/sorted_map.yo +14 -14
  18. package/std/imm/string.yo +4 -4
  19. package/std/prelude.yo +18 -23
  20. package/std/process/command.yo +8 -8
  21. package/std/string/string.yo +76 -55
  22. package/std/string/unicode.yo +6 -6
  23. package/std/sys/signal.yo +6 -6
  24. package/vendor/mimalloc/.github/workflows/release.yaml +55 -0
  25. package/vendor/mimalloc/.github/workflows/stale.yaml +27 -0
  26. package/vendor/mimalloc/.github/workflows/test.yaml +163 -0
  27. package/vendor/mimalloc/CMakeLists.txt +52 -33
  28. package/vendor/mimalloc/azure-pipelines.yml +4 -3
  29. package/vendor/mimalloc/bin/bundle.bat +74 -0
  30. package/vendor/mimalloc/bin/bundle.sh +232 -0
  31. package/vendor/mimalloc/cmake/mimalloc-config-version.cmake +2 -2
  32. package/vendor/mimalloc/contrib/docker/alpine/Dockerfile +1 -1
  33. package/vendor/mimalloc/contrib/docker/alpine-arm32v7/Dockerfile +2 -2
  34. package/vendor/mimalloc/contrib/docker/alpine-x86/Dockerfile +1 -1
  35. package/vendor/mimalloc/contrib/docker/manylinux-x64/Dockerfile +1 -1
  36. package/vendor/mimalloc/contrib/vcpkg/portfile.cmake +4 -3
  37. package/vendor/mimalloc/contrib/vcpkg/vcpkg.json +1 -1
  38. package/vendor/mimalloc/doc/mimalloc-doc.h +42 -4
  39. package/vendor/mimalloc/doc/release-notes.md +15 -0
  40. package/vendor/mimalloc/ide/vs2022/mimalloc-lib.vcxproj +3 -3
  41. package/vendor/mimalloc/ide/vs2022/mimalloc-override-static-lib.vcxproj +511 -0
  42. package/vendor/mimalloc/ide/vs2022/mimalloc-override-static-lib.vcxproj.filters +117 -0
  43. package/vendor/mimalloc/ide/vs2022/mimalloc-test-dep.vcxproj +360 -0
  44. package/vendor/mimalloc/ide/vs2022/mimalloc-test-override-static.vcxproj +310 -0
  45. package/vendor/mimalloc/ide/vs2022/mimalloc.sln +92 -35
  46. package/vendor/mimalloc/include/mimalloc/atomic.h +178 -182
  47. package/vendor/mimalloc/include/mimalloc/bits.h +8 -10
  48. package/vendor/mimalloc/include/mimalloc/internal.h +76 -32
  49. package/vendor/mimalloc/include/mimalloc/prim.h +25 -18
  50. package/vendor/mimalloc/include/mimalloc/track.h +7 -2
  51. package/vendor/mimalloc/include/mimalloc/types.h +57 -29
  52. package/vendor/mimalloc/include/mimalloc-override.h +10 -10
  53. package/vendor/mimalloc/include/mimalloc-stats.h +18 -6
  54. package/vendor/mimalloc/include/mimalloc.h +22 -12
  55. package/vendor/mimalloc/readme.md +42 -17
  56. package/vendor/mimalloc/src/alloc-aligned.c +13 -11
  57. package/vendor/mimalloc/src/alloc-override.c +97 -17
  58. package/vendor/mimalloc/src/alloc-posix.c +44 -27
  59. package/vendor/mimalloc/src/alloc.c +73 -23
  60. package/vendor/mimalloc/src/arena-meta.c +3 -3
  61. package/vendor/mimalloc/src/arena.c +380 -192
  62. package/vendor/mimalloc/src/bitmap.c +68 -18
  63. package/vendor/mimalloc/src/bitmap.h +8 -4
  64. package/vendor/mimalloc/src/free.c +83 -47
  65. package/vendor/mimalloc/src/heap.c +94 -40
  66. package/vendor/mimalloc/src/init.c +273 -102
  67. package/vendor/mimalloc/src/libc.c +53 -8
  68. package/vendor/mimalloc/src/options.c +43 -40
  69. package/vendor/mimalloc/src/os.c +110 -45
  70. package/vendor/mimalloc/src/page-map.c +14 -8
  71. package/vendor/mimalloc/src/page-queue.c +9 -6
  72. package/vendor/mimalloc/src/page.c +26 -16
  73. package/vendor/mimalloc/src/prim/emscripten/prim.c +10 -1
  74. package/vendor/mimalloc/src/prim/osx/alloc-override-zone.c +35 -16
  75. package/vendor/mimalloc/src/prim/unix/prim.c +26 -22
  76. package/vendor/mimalloc/src/prim/wasi/prim.c +7 -4
  77. package/vendor/mimalloc/src/prim/windows/prim.c +247 -44
  78. package/vendor/mimalloc/src/random.c +8 -3
  79. package/vendor/mimalloc/src/stats.c +59 -48
  80. package/vendor/mimalloc/src/theap.c +85 -44
  81. package/vendor/mimalloc/src/threadlocal.c +102 -41
  82. package/vendor/mimalloc/test/main-override-static.c +31 -2
  83. package/vendor/mimalloc/test/main-override.c +27 -14
  84. package/vendor/mimalloc/test/main-static-dep.cpp +46 -0
  85. package/vendor/mimalloc/test/main-static-dep.h +11 -0
  86. package/vendor/mimalloc/test/test-api-fill.c +2 -2
  87. package/vendor/mimalloc/test/test-stress.c +3 -3
  88. package/vendor/mimalloc/test/test-wrong.c +11 -7
@@ -51,7 +51,7 @@ total := {
51
51
  };
52
52
  ```
53
53
 
54
- Remember: `{ expr }` without semicolons is a struct literal, not a block.
54
+ Remember: `{ expr }` without semicolons is a struct literal, not a block. The parser now detects this mistake and emits a clear error if the single expression is not a valid struct field.
55
55
 
56
56
  ## Control flow
57
57
 
@@ -90,6 +90,8 @@ Key rules:
90
90
  - In **runtime** code, `"hello"` is `str`. Mixing literals and variables in `cond`/`match` branches is fine.
91
91
  - In **comptime** functions (return type `comptime(...)`), `"hello"` is `comptime_string` — it does NOT auto-convert to `str`.
92
92
  - For `String` constants, prefer `` `hello` `` over `String.from("hello")`.
93
+ - **`String.from(`` `...` ``)` is WRONG**: `` `...` `` is already `String`; `String.from` takes `str`. Use `` `...` `` directly or `String.from("...")` with double quotes.
94
+ - **`assert` takes `str`, not `String`**: `assert(cond, "message")` — always use `""`. Passing a template string `` `...` `` causes a type mismatch. Use a custom `check_str` helper when you need `String` diagnostics.
93
95
 
94
96
  ## Calls, operators, and whitespace
95
97
 
@@ -102,7 +104,10 @@ masked := ((A | B) | C);
102
104
  - Prefer parenthesized calls: `func(arg1, arg2)`
103
105
  - `func (a, b)` is a different parse shape than `func(a, b)`
104
106
  - Yo has no operator precedence; fully parenthesize binary expressions
105
- - Parenthesize unary operands: `!(ready)`, `-(value)`
107
+ - **All unary operators (`!`, `&`, `-`, `~`) greedily consume everything that follows, including comma-separated args.** `func(&s, a, b)` is parsed as `func(&(s, a, b))` — ONE tuple argument! Preferred fix: parenthesize the operand: `func(&(s), a, b)`. The outer-parens form `func((&s), a, b)` also works. Either is fine; the operand-parens form `&(x)` matches how the parser thinks about it.
108
+ - Parenthesize other unary operands too: `!(ready)`, `-(value)`
109
+ - **`!x && y` is parsed as `!(x && y)`**, not `(!x) && y`. Prefix `!` greedily consumes the full right-hand expression. To get `(!x) && y`, write `((!x) && y)` with explicit inner parens.
110
+ - **Nested `&&` / `||` in a single compound condition causes "Ambiguous operator precedence"** even with explicit parentheses: `((A && B) && (C && D))` on one line triggers the error. Fix: extract sub-conditions into named booleans first: `_c1 := (A && B); _c2 := (C && D); if((_c1 && _c2), ...)`.
106
111
 
107
112
  ## Functions and methods
108
113
 
@@ -126,6 +131,7 @@ impl(Counter,
126
131
  - `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.
127
132
  - Wrap `fn` types in parentheses when they appear after `:`
128
133
  - **Forward references between methods in the same `impl` block are supported.** A method defined later in the block can be called by a method defined earlier. Both `self.method()` and `Self.method(...)` dispatch work. Only the canonical `name : (fn(...) -> R)(body)` method shape participates; bare lambdas do not get forward-ref shells.
134
+ - **Module-level `::` function definitions are processed in order.** A function body that calls another function declared later in the same file will fail with "Variable not found". Always define leaf helpers first (bottom-up order): `eval_identifier` → `eval_atom` → `evaluate`.
129
135
 
130
136
  ### Named arguments and default values
131
137
 
@@ -223,6 +229,22 @@ text := match(value,
223
229
  - Construction and match branches use the leading `.`
224
230
  - Nested destructuring is not supported; match one layer at a time
225
231
 
232
+ Three destructuring shapes for arms (mix freely across arms):
233
+
234
+ ```rust
235
+ Shape :: enum(Circle(radius : i32), Rectangle(width : i32, height : i32));
236
+
237
+ match(s,
238
+ .Circle(r) => (r * r), // positional
239
+ .Rectangle(width: w, height: h) => (w * h), // labeled
240
+ .Rectangle({width, height: h}) => (width * h) // curly shorthand
241
+ )
242
+ ```
243
+
244
+ 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.
245
+
246
+ > **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.
247
+
226
248
  ## Generics and compile-time
227
249
 
228
250
  ```rust
@@ -294,14 +316,36 @@ factorial :: (fn(n : i32) -> i32)(
294
316
  )
295
317
  );
296
318
 
297
- while runtime(true), {
319
+ // Runtime infinite loop — `while cond` is ALWAYS runtime
320
+ while true, {
298
321
  work();
299
322
  };
323
+
324
+ // Compile-time loop unrolling — requires comptime() modifier
325
+ while comptime(i < 10), {
326
+ // body evaluated/unrolled at compile time
327
+ };
328
+
329
+ // for loop — 2-arg prelude macro; first arg MUST be an iterator (has .next()):
330
+ for(list.iter(), x => { // ArrayList, array → call .iter() first
331
+ process(x);
332
+ });
333
+
334
+ for(map.into_iter(), bucket => {
335
+ println(bucket.key);
336
+ });
337
+
338
+ for(list.iter(), (ptr) => { // ptr is a mutable reference to each element
339
+ ptr.* = transform(ptr.*);
340
+ });
300
341
  ```
301
342
 
302
343
  - Use `recur(...)` for self-recursion
303
- - `while true` runs at compile time
304
- - Use `while runtime(true), { ... }` for open-ended runtime loops
344
+ - `while cond` is **always a runtime loop** — use this for open-ended loops (e.g., server accept loops, event loops)
345
+ - `while comptime(cond)` explicitly unrolls at compile time — `cond` must be a compile-time-known value
346
+ - Using a comptime-only (`::`) variable in a bare `while` condition without `comptime()` is a **compile error** (would be an infinite loop at runtime)
347
+ - **`for(arr, item => { body })`** — correct 2-arg prelude macro form. The `item => { body }` is an anonymous closure.
348
+ - **Do NOT use `for(x, arr, { body })`** — this older 3-arg form is an evaluator-internal representation, not valid top-level Yo syntax. (The self-hosted evaluator currently only understands the 3-arg form in its internal for-loop handler; track issue: `issues/eval-for-loop-3arg-vs-2arg.md`)
305
349
 
306
350
  ## Return and branch safety
307
351
 
@@ -333,9 +377,27 @@ get_value :: (fn(opt : Option(i32)) -> i32)(
333
377
 
334
378
  - `return expr1, expr2` parses as a single function call: `return(expr1, expr2)`
335
379
  - In `cond` or `match` branches, **always use begin blocks** when you need `return`
380
+ - `return` must be the **last expression** in a begin block — dead code after `return` is rejected. Do NOT write `{ return x; fallback_val }`. Write `{ return x; }` only.
336
381
  - If the whole function is one expression, prefer expression-bodied style and skip `return` entirely
337
382
  - The same trap applies to any function call without parens in match branches
338
383
 
384
+ ## String concatenation pitfall
385
+
386
+ ```rust
387
+ // WRONG — str + str causes "comptime_string vs str" type unification error:
388
+ content := String.from("line1\n" + "line2\n");
389
+
390
+ // CORRECT — use .concat() on String objects:
391
+ content := String.from("line1\n").concat(String.from("line2\n"));
392
+
393
+ // Also CORRECT — single long string literal:
394
+ content := String.from("line1\nline2\n");
395
+ ```
396
+
397
+ - `"hello" + "world"` at runtime uses `+` on `str` values, which can cause type mismatches
398
+ - The `str + str` operator can produce a `comptime_string` in some contexts, which is not always compatible with `str`
399
+ - Prefer `.concat()` method on `String` objects when building multi-part strings at runtime
400
+
339
401
  ## Iterator and for loop
340
402
 
341
403
  ```rust
@@ -381,7 +443,311 @@ test "Async test", {
381
443
  - `comptime_assert(condition)` — compile-time assertion
382
444
  - `comptime_expect_error(expr)` — verify code produces a compile error
383
445
 
384
- ## Advanced features (reference)
446
+ ## Common pitfalls
447
+
448
+ ### `&&` short-circuit with `match`/`cond` on RHS causes C codegen scope bug
449
+
450
+ Using `&&` where the right-hand side is a `match` or `cond` expression causes
451
+ a C codegen bug: the temp variable for the RHS is declared inside the short-circuit
452
+ `if` block but the cleanup drop is emitted outside it. This produces a C compile
453
+ error ("use of undeclared identifier").
454
+
455
+ ```rust
456
+ // WRONG — triggers codegen scope bug:
457
+ is_ok := (av.is_compile_time_only && match(av.value,
458
+ .Some(v) => compute(v),
459
+ .None => false
460
+ ));
461
+
462
+ // CORRECT — use an explicit if block to scope the match:
463
+ (is_ok : bool) = false;
464
+ if(av.is_compile_time_only, {
465
+ is_ok = match(av.value,
466
+ .Some(v) => compute(v),
467
+ .None => false
468
+ );
469
+ });
470
+ ```
471
+
472
+ This only affects `&&`/`||` where the **right-hand side contains a `match`,
473
+ `cond`, or other expression that allocates heap-managed temporaries** (e.g.,
474
+ `String`, `ArrayList`, `Option(HeapType)`). Pure boolean expressions on both
475
+ sides are fine.
476
+
477
+ ### `impl(...)` requires a trailing semicolon
478
+
479
+ ```rust
480
+ // WRONG — "Invalid function call on type" at runtime:
481
+ impl(MyType,
482
+ get : (fn(self : Self) -> i32)(self.x)
483
+ )
484
+
485
+ // CORRECT:
486
+ impl(MyType,
487
+ get : (fn(self : Self) -> i32)(self.x)
488
+ );
489
+ ```
490
+
491
+ ### `___` discard variable cannot appear twice in the same scope
492
+
493
+ ```rust
494
+ // WRONG — shadowing of ___ is not allowed:
495
+ ___ := foo();
496
+ ___ := bar();
497
+
498
+ // CORRECT — use unique names or bare calls:
499
+ _a := foo();
500
+ _b := bar();
501
+ // or simply:
502
+ foo();
503
+ bar();
504
+ ```
505
+
506
+ ### Enum pattern matching does NOT support literal values
507
+
508
+ Match patterns on enum variants only support **variable binding**, not literal comparison.
509
+ `.BoolVal(true)` binds the inner value to a variable named `true` — it does NOT check
510
+ if the value is `true`. The arm always matches any `BoolVal`.
511
+
512
+ ```rust
513
+ // ❌ WRONG — always matches (true is a variable binding, not a comparison)
514
+ match(val,
515
+ .BoolVal(true) => handle_true(),
516
+ _ => ()
517
+ );
518
+
519
+ // ✅ CORRECT — bind to variable, then check with cond
520
+ match(val,
521
+ .BoolVal(b) => cond(b => handle_true(), true => ()),
522
+ _ => ()
523
+ );
524
+ ```
525
+
526
+ Same applies to `.IntLit(42)`, `.StrLit("hello")`, etc.
527
+
528
+ ### `type` is a reserved keyword — avoid as field/param name
529
+
530
+ ```rust
531
+ // WRONG:
532
+ Variable :: object(name : String, type : TypeValue);
533
+
534
+ // CORRECT:
535
+ Variable :: object(name : String, ty : TypeValue);
536
+ ```
537
+
538
+ ### ArrayList indexing uses call syntax
539
+
540
+ ```rust
541
+ list := ArrayList(i32).new();
542
+ list.push(i32(42));
543
+
544
+ val := list(usize(0)); // → i32 (value copy via Index trait)
545
+ list(usize(0)) = i32(99); // mutate in place directly
546
+
547
+ // When you need the pointer explicitly:
548
+ ptr := &(list(usize(0))); // → *(i32)
549
+ ptr.* = i32(99); // also works
550
+
551
+ // Safe access (returns Option(T)):
552
+ match(list.get(usize(0)),
553
+ .Some(v) => println(`${v}`),
554
+ .None => ()
555
+ );
556
+ ```
557
+
558
+ - `list(i)` returns the value `T` (not a pointer)
559
+ - `list(i) = val` mutates in place directly (preferred)
560
+ - `&(list(i))` returns `*(T)` if you need the pointer explicitly
561
+ - `list.get(i)` returns `Option(T)` for safe bounds-checked access
562
+
563
+ ### Named fields required for `struct`/`object` constructors
564
+
565
+ ```rust
566
+ Point :: struct(x : i32, y : i32);
567
+
568
+ // CORRECT:
569
+ p := Point(x: i32(1), y: i32(2));
570
+
571
+ // WRONG — positional not supported for struct/object:
572
+ p := Point(i32(1), i32(2));
573
+ ```
574
+
575
+ Enum variant construction is positional (no field names needed).
576
+
577
+ ### Object types (RC) are passed by value
578
+
579
+ `HashMap`, `ArrayList`, and other `object(...)` types are reference-counted. Passing them by value shares the underlying data — mutations are visible to all holders.
580
+
581
+ ```rust
582
+ // DO NOT use pointer params for RC objects:
583
+ // WRONG: fn(m : *(HashMap(String, V))) — will cause greedy & issues at call site
584
+ // CORRECT: fn(m : HashMap(String, V)) — pass by value, mutations propagate via RC
585
+
586
+ process_map :: (fn(m : HashMap(String, i32)) -> unit)({
587
+ m.set(String.from("key"), i32(42)); // mutation visible to caller
588
+ });
589
+
590
+ counts := HashMap(String, i32).new();
591
+ process_map(counts);
592
+ // counts now has "key" => 42
593
+ ```
594
+
595
+ ### Forward references are NOT allowed
596
+
597
+ Top-level bindings are evaluated strictly in order. A function must be defined BEFORE it is called (even inside closures that are called later).
598
+
599
+ ```rust
600
+ // WRONG — forward reference:
601
+ caller :: (fn() -> unit)({ helper(); });
602
+ helper :: (fn() -> unit)({ println("hi"); });
603
+
604
+ // CORRECT — helper before caller:
605
+ helper :: (fn() -> unit)({ println("hi"); });
606
+ caller :: (fn() -> unit)({ helper(); });
607
+ ```
608
+
609
+ This applies to ALL callee-before-caller relationships:
610
+
611
+ - `_walk_dag` before `build_dag`
612
+ - `compile_artifact`, `run_executable`, `run_test_suite` before `execute_node`
613
+ - `execute_node` before `execute_dag`
614
+ - `_print_summary_node` before `print_build_summary`
615
+ - `print_build_summary` before `execute_step`
616
+ - Exports section must come AFTER all definitions
617
+
618
+ ### Named tuple fields in type syntax are not allowed
619
+
620
+ Yo does not support named tuple field types in the syntax `(name : Type, ...)`. Use an `object` struct instead:
621
+
622
+ ```rust
623
+ // WRONG — "Labelled field is not allowed in tuple value":
624
+ get_range :: (fn(ty : TypeValue) -> Option((min : i64, max : u64)))(
625
+ .Some((min: i64(-128), max: u64(127)))
626
+ );
627
+
628
+ // CORRECT — define a named struct:
629
+ Range :: object(min : i64, max : u64);
630
+ get_range :: (fn(ty : TypeValue) -> Option(Range))(
631
+ .Some({min: i64(-128), max: u64(127)})
632
+ );
633
+ ```
634
+
635
+ Named fields work in struct/object constructors `{min: ..., max: ...}`, just not in the type syntax for tuples.
636
+
637
+ ### `Option` equality comparison limitations
638
+
639
+ Comparing `Option(T)` with `== .None` or `== .Some(...)` fails when the inner type `T` lacks a derived `Eq` implementation or when `.None` is type-ambiguous. Always use the method API:
640
+
641
+ ```rust
642
+ // WRONG — "No matching call found with arguments: r == .None":
643
+ assert((r == .None), "should be None");
644
+
645
+ // CORRECT — use .is_none() / .is_some() / .unwrap():
646
+ assert(r.is_none(), "should be None");
647
+ assert(r.is_some(), "should be Some");
648
+ assert((r.unwrap() == expected_value), "value check");
649
+
650
+ // Also fine in match:
651
+ match(r,
652
+ .Some(v) => assert((v == expected_value), "value check"),
653
+ .None => assert(false, "unexpected None")
654
+ );
655
+ ```
656
+
657
+ The `!` operator is greedy and consumes all following args including the block. Always parenthesize:
658
+
659
+ ```rust
660
+ // WRONG — ! consumes "cond, block" as one arg:
661
+ if(!cond, { do_thing(); });
662
+
663
+ // CORRECT — ! only consumes (cond):
664
+ if((!cond), { do_thing(); });
665
+ ```
666
+
667
+ ### `escape` requires a nested-function context
668
+
669
+ `escape value` exits the **enclosing function** — the nearest `fn(...)` that
670
+ wraps the current code. It requires that the code is inside a nested function
671
+ (e.g., a closure or `given` handler lambda), NOT at the top level of a
672
+ standalone function definition.
673
+
674
+ ```rust
675
+ // CORRECT — escape inside a given handler lambda (lambda has enclosing fn):
676
+ given(exn) := Exception(throw: ((err) -> {
677
+ escape result // exits the outer function that contains this given()
678
+ }));
679
+ do_something(using(exn));
680
+
681
+ // CORRECT — escape inside a closure passed as argument:
682
+ result := match(opt, .Some(x) => x, .None => {
683
+ // This is NOT a nested function — use return:
684
+ // WRONG: escape default_val // ERROR: no enclosing fn
685
+ return default_val // CORRECT: return exits the enclosing fn directly
686
+ });
687
+
688
+ // WRONG — escape inside a match arm (not a nested fn):
689
+ match(opt,
690
+ .Some(x) => { escape x } // ERROR: "can only be used inside a function that has an enclosing function"
691
+ );
692
+ ```
693
+
694
+ **Rule of thumb**: Use `escape` only inside lambdas passed to `given()` handlers.
695
+ In all other contexts (match arms, if blocks, begin blocks, top-level fn bodies),
696
+ use `return` for early exit.
697
+
698
+ `return` inside a lambda exits that lambda only; `escape` exits the OUTER function
699
+ (the one that installed the `given` handler).
700
+
701
+ ### Parameter reassignment
702
+
703
+ Function parameters are **NOT reassignable**. To reassign, declare a mutable local:
704
+
705
+ ```rust
706
+ // WRONG — cannot reassign parameter 'env':
707
+ my_fn :: (fn(env : Environment) -> Environment)({
708
+ env = other_env; // ERROR: "cannot reassign itself"
709
+ env
710
+ });
711
+
712
+ // CORRECT — create a mutable local copy:
713
+ my_fn :: (fn(init_env : Environment) -> Environment)({
714
+ (env : Environment) = init_env;
715
+ env = other_env; // OK — reassigning local variable
716
+ env
717
+ });
718
+ ```
719
+
720
+ This also applies to `object` types: you can mutate fields (`env.field = val`)
721
+ but cannot rebind the variable (`env = other_env`).
722
+
723
+ ### String cloning
724
+
725
+ Calling `.clone()` on a `String` field from a struct/method chain requires a
726
+ reference — take `&` first:
727
+
728
+ ```rust
729
+ // WRONG — .clone() requires *(Self) but gets Self value from field access:
730
+ name := token.value.clone(); // ERROR
731
+
732
+ // CORRECT — take reference first:
733
+ tok := some_fn_call();
734
+ name := (&tok.value).clone(); // OK
735
+
736
+ // ALSO CORRECT — use String.from on the str slice:
737
+ name := String.from(token.value.as_str());
738
+ ```
739
+
740
+ ### Template strings produce `String`, literals are `str`
741
+
742
+ ```rust
743
+ // Template string `` `...` `` → String
744
+ // String literal "..." → str
745
+
746
+ // If a function takes `str`, call .as_str() on a template string:
747
+ fn_taking_str((`prefix_${value}`).as_str());
748
+
749
+ // Or change the function to take String
750
+ ```
385
751
 
386
752
  These features are powerful but less commonly used. Consult the linked docs for full details.
387
753
 
@@ -398,3 +764,257 @@ These features are powerful but less commonly used. Consult the linked docs for
398
764
  | Isolated types | `Iso(T)` for data-race-free parallelism | [ISOLATED.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/ISOLATED.md) |
399
765
  | Arc (atomic ref count) | `arc(value)`, `shared.(*)` for cross-thread sharing | [ARC.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/ARC.md) |
400
766
  | Parallelism | Thread pool, `io.spawn` for parallel work | [PARALLELISM.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/PARALLELISM.md) |
767
+
768
+ ---
769
+
770
+ ## Self-hosted evaluator (`yo-self/`) — known limitations and pitfalls
771
+
772
+ These constraints apply **only** to the self-hosted Yo evaluator (code inside `yo-self/`). The TypeScript-compiled Yo compiler does not have these restrictions.
773
+
774
+ ### Match patterns: no bare identifier catch-all
775
+
776
+ The self-hosted parser only accepts three match arm forms:
777
+
778
+ - `.VariantName` — unit variant
779
+ - `.VariantName(p1, p2, ...)` — tuple variant
780
+ - `_` — wildcard
781
+
782
+ **Bare identifier catch-all `t => ...` is NOT supported.** Use `_` and access the outer binding directly:
783
+
784
+ ```rust
785
+ // ❌ NOT supported in yo-self/
786
+ match(val, {
787
+ .SomeVariant(x) => x,
788
+ t => t, // ERROR: bare identifier not a valid pattern
789
+ })
790
+
791
+ // ✅ Correct — use outer binding
792
+ match(val, {
793
+ .SomeVariant(x) => x,
794
+ _ => val, // refer to outer binding
795
+ })
796
+ ```
797
+
798
+ ### String concatenation: no `String + str` operator
799
+
800
+ The `+` operator does not accept mixed `String`/`str` operands.
801
+ **Always use template strings** for concatenation:
802
+
803
+ ```rust
804
+ // ❌ Type error
805
+ result := (parts + ", ");
806
+ result := (parts + item.as_str());
807
+
808
+ // ✅ Template strings
809
+ result := `${parts}, `;
810
+ result := `${parts}${item}`;
811
+ ```
812
+
813
+ ### `clone()` on extracted String fields is ambiguous
814
+
815
+ When a `String` field is bound in a match arm, calling `.clone()` triggers an ambiguity error (two impls: `fn(self: String)` and `fn(self: *(String))`).
816
+
817
+ ```rust
818
+ // ❌ Ambiguous
819
+ .StructVal(name, fields) => name.clone()
820
+
821
+ // ✅ Use from + as_str
822
+ .StructVal(name, fields) => String.from(name.as_str())
823
+ ```
824
+
825
+ ### `box(val)` is a move — cannot box the same value twice
826
+
827
+ ```rust
828
+ // ❌ Move error: target is moved by first box(target)
829
+ p1 := PtrVal(box(target), usize(0));
830
+ p2 := PtrVal(box(target), usize(0)); // ERROR: target already moved
831
+
832
+ // ✅ Create separate instances
833
+ p1 := PtrVal(box(EvalValue.IntLit(String.from("42"))), usize(0));
834
+ p2 := PtrVal(box(EvalValue.IntLit(String.from("42"))), usize(0));
835
+ ```
836
+
837
+ ### `recur(...)` for self-recursive lambdas
838
+
839
+ Lambdas defined as `name :: (fn(args) -> T)(body)` cannot call `name` inside `body`.
840
+ Use `recur(...)` instead:
841
+
842
+ ```rust
843
+ // ❌ Would not find `my_fn` inside its own body
844
+ my_fn :: (fn(x : i32) -> i32)({
845
+ my_fn(x - 1) // ERROR: `my_fn` not in scope yet
846
+ });
847
+
848
+ // ✅ Use recur
849
+ my_fn :: (fn(x : i32) -> i32)({
850
+ recur(x - 1)
851
+ });
852
+ ```
853
+
854
+ ### `{ expr }` without semicolons is a struct literal, not a block
855
+
856
+ ```rust
857
+ // ❌ Parsed as struct literal `{ match(...) }`
858
+ fn :: (fn() -> T)({ match(x, arms) })
859
+
860
+ // ✅ Remove braces or add semicolon
861
+ fn :: (fn() -> T)(match(x, arms))
862
+ fn :: (fn() -> T)({ match(x, arms); })
863
+ ```
864
+
865
+ ### Template strings cannot be nested inside `${...}` interpolations
866
+
867
+ A template string literal (`` ` `` ... `` ` ``) inside a `${...}` interpolation of another template string closes the outer string. The compiler gives confusing parse errors.
868
+
869
+ ```rust
870
+ // ❌ Inner backtick closes the outer template string — parse error
871
+ lines.push(`**Implements:** ${`, `.join(names)}`);
872
+
873
+ // ✅ Assign the separator to a variable first
874
+ sep := `, `;
875
+ lines.push(`**Implements:** ${sep.join(names)}`);
876
+ ```
877
+
878
+ ### Pushing RC struct fields into ArrayList does not need `.clone()`
879
+
880
+ String (and other RC object) fields of structs can be passed directly to `ArrayList.push()`. Calling `.clone()` triggers an ambiguity error between `fn(self: String)` and `fn(self: *(String))` overloads.
881
+
882
+ ```rust
883
+ // ❌ Ambiguous clone call
884
+ names.push(param.name.clone());
885
+
886
+ // ✅ Push directly — RC bump happens automatically
887
+ names.push(param.name);
888
+ ```
889
+
890
+ If explicit clone is needed elsewhere, use `(&field).clone()` to select the pointer overload.
891
+
892
+ ### `.Some(expr)` in expression position is parsed as a 2-arg property access
893
+
894
+ Using `.Some(x)` as an expression (not inside a match pattern) is parsed by the Yo parser
895
+ as a 2-arg dot property access: `obj.(prop, arg)`. This means `evaluate_property_access` is
896
+ invoked on it at compile time, causing confusing errors like "Failed to infer enum variant type".
897
+
898
+ ```rust
899
+ // ❌ Parsed as 2-arg property access — NOT an Option::Some constructor call
900
+ val := .Some(oi.ty);
901
+
902
+ // ✅ Use the explicit fully-qualified form
903
+ val := Option(TypeValue).Some(oi.ty);
904
+ ```
905
+
906
+ ### `||` chaining requires explicit parentheses for 3+ operands
907
+
908
+ Chaining three or more `||` terms in a single expression is rejected with a precedence error.
909
+ Always add explicit parentheses around each pair:
910
+
911
+ ```rust
912
+ // ❌ Rejected — ambiguous precedence
913
+ if ((is_tuple_type(ty) || is_struct_type(ty) || is_union_type(ty)), ...)
914
+
915
+ // ✅ Parenthesise each pair
916
+ if (((is_tuple_type(ty) || is_struct_type(ty)) || is_union_type(ty)), ...)
917
+ ```
918
+
919
+ ### Duplicate imports from the same path must be merged
920
+
921
+ Having two `:: import "path"` lines importing from the same file causes a compile error.
922
+ Always merge them into a single destructuring import:
923
+
924
+ ```rust
925
+ // ❌ Two imports from the same path
926
+ { Foo } :: import "../../mod.yo";
927
+ { Bar } :: import "../../mod.yo";
928
+
929
+ // ✅ Merged
930
+ { Foo, Bar } :: import "../../mod.yo";
931
+ ```
932
+
933
+ ### Implicit (`using`) parameters cannot be used with `:=` assignment
934
+
935
+ Implicit effect parameters introduced via `using(name : Type)` cannot be bound
936
+ to a discarded variable with `:= name`. They can only be passed via `using()`.
937
+ To suppress "unused parameter" warnings for an implicit param, simply omit the
938
+ discard assignment — implicit params never trigger unused-variable errors.
939
+
940
+ ```rust
941
+ // WRONG — implicit variable cannot be used in assignment:
942
+ foo :: (fn(using(exn : Exception)) -> unit)({
943
+ _ := exn; // ERROR: Cannot use implicit variable "exn" in assignment
944
+ ()
945
+ });
946
+
947
+ // CORRECT — just omit the discard line:
948
+ foo :: (fn(using(exn : Exception)) -> unit)({
949
+ ()
950
+ });
951
+ ```
952
+
953
+ ### Nested `Option` patterns require staging
954
+
955
+ `match` does not support nested destructuring patterns like `.Some(.TypeVal(x))`.
956
+ Split into two separate `match` expressions.
957
+
958
+ ```rust
959
+ // WRONG — nested option pattern:
960
+ match(opt_value,
961
+ .Some(.TypeVal(box)) => { ... }, // ERROR
962
+ _ => { ... }
963
+ );
964
+
965
+ // CORRECT — match in two stages:
966
+ match(opt_value,
967
+ .Some(v) => match(v,
968
+ .TypeVal(box) => { ... },
969
+ _ => { ... }
970
+ ),
971
+ .None => { ... }
972
+ );
973
+ ```
974
+
975
+ ### Outer match on `Option` must have `.None` arm
976
+
977
+ When using `match(opt, .Some(x) => match(x, ...), ...)`, the outer match
978
+ needs its own `.None` arm. The inner match's `_ =>` wildcard does NOT cover
979
+ the outer match's `.None` variant.
980
+
981
+ ```rust
982
+ // WRONG — outer match missing .None:
983
+ match(opt_callee_value,
984
+ .Some(cv) => match(cv,
985
+ .Foo(x) => { ... },
986
+ _ => { throw_phase3() } // inner wildcard, does NOT cover outer .None
987
+ ) // outer match closes here — .None uncovered!
988
+ );
989
+
990
+ // CORRECT — add explicit .None arm to outer match:
991
+ match(opt_callee_value,
992
+ .Some(cv) => match(cv,
993
+ .Foo(x) => { ... },
994
+ _ => { throw_phase3() }
995
+ ),
996
+ .None => { throw_phase3_none() }
997
+ );
998
+ ```
999
+
1000
+ ### Parenthesis balance in deeply nested matches
1001
+
1002
+ When using `match(outer, .Some(x) => match(inner, ...), .None => ...)`,
1003
+ count parentheses carefully:
1004
+
1005
+ - The inner `match(inner, ...)` closes with its own `)`
1006
+ - AFTER that `)`, add a `,` then the outer `.None =>` arm
1007
+ - The outer match closes with its own `)`
1008
+ - Only then does `});` close the function body
1009
+
1010
+ ```rust
1011
+ // Correct structure:
1012
+ match(outer_val,
1013
+ .Some(x) => match(x,
1014
+ arm1,
1015
+ arm2,
1016
+ _ => { fallback() } // last inner arm, no trailing comma
1017
+ ), // ← closes inner match; `,` continues outer
1018
+ .None => { fallback() } // ← outer .None arm
1019
+ ) // ← closes outer match
1020
+ ```