@shd101wyy/yo 0.1.32 → 0.1.34

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 (48) hide show
  1. package/.github/skills/yo-async-effects/async-effects-recipes.md +2 -2
  2. package/.github/skills/yo-core-patterns/SKILL.md +1 -1
  3. package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +76 -22
  4. package/.github/skills/yo-syntax/SKILL.md +1 -1
  5. package/.github/skills/yo-syntax/syntax-cheatsheet.md +59 -67
  6. package/out/cjs/index.cjs +662 -574
  7. package/out/cjs/yo-cli.cjs +803 -715
  8. package/out/cjs/yo-lsp.cjs +771 -683
  9. package/out/esm/index.mjs +555 -467
  10. package/out/types/src/codegen/exprs/comptime-value.d.ts +2 -1
  11. package/out/types/src/codegen/functions/context.d.ts +1 -0
  12. package/out/types/src/codegen/types/generation.d.ts +1 -1
  13. package/out/types/src/codegen/utils/index.d.ts +2 -4
  14. package/out/types/src/evaluator/exprs/_expr.d.ts +1 -0
  15. package/out/types/src/evaluator/types/flowability.d.ts +22 -0
  16. package/out/types/src/expr.d.ts +4 -13
  17. package/out/types/src/types/creators.d.ts +2 -3
  18. package/out/types/src/types/definitions.d.ts +2 -3
  19. package/out/types/src/types/guards.d.ts +2 -2
  20. package/out/types/src/types/tags.d.ts +2 -2
  21. package/out/types/src/value-tag.d.ts +0 -1
  22. package/out/types/src/value.d.ts +2 -11
  23. package/out/types/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +1 -1
  25. package/std/alg/hash.yo +5 -3
  26. package/std/build.yo +46 -46
  27. package/std/collections/array_list.yo +73 -66
  28. package/std/collections/hash_map.yo +53 -95
  29. package/std/collections/list_view.yo +77 -0
  30. package/std/crypto/random.yo +5 -5
  31. package/std/encoding/base64.yo +12 -13
  32. package/std/encoding/hex.yo +6 -6
  33. package/std/encoding/json.yo +6 -5
  34. package/std/encoding/utf16.yo +9 -9
  35. package/std/env.yo +8 -8
  36. package/std/fmt/to_string.yo +12 -12
  37. package/std/http/client.yo +1 -1
  38. package/std/imm/list.yo +4 -3
  39. package/std/imm/map.yo +3 -2
  40. package/std/imm/set.yo +4 -3
  41. package/std/imm/sorted_map.yo +3 -2
  42. package/std/imm/sorted_set.yo +4 -3
  43. package/std/imm/string.yo +12 -5
  44. package/std/imm/vec.yo +8 -3
  45. package/std/prelude.yo +178 -401
  46. package/std/string/string.yo +198 -71
  47. package/std/url/index.yo +26 -26
  48. package/out/types/src/evaluator/types/slice.d.ts +0 -8
@@ -160,13 +160,13 @@ process_dir :: (fn(root: Path, ctx : WalkCtx) -> Impl(Future(unit, WalkCtx)))(
160
160
  stack := ArrayList(Path).new();
161
161
  { stack.push(root); };
162
162
 
163
- while(runtime((stack.len() > usize(0))), {
163
+ while(stack.len() > usize(0), {
164
164
  cur := match(stack.pop(), .Some(p) => p, .None => return());
165
165
  entries := ctx.io.await(read_dir(cur, ctx.io), ctx.io);
166
166
  // process `entries`, push subdirectories to `stack`
167
167
  n := entries.len();
168
168
  i := usize(0);
169
- while(runtime((i < n)), {
169
+ while(i < n, {
170
170
  match(entries.get(i),
171
171
  .None => (),
172
172
  .Some(e) => {
@@ -29,7 +29,7 @@ Use this skill when you need to:
29
29
 
30
30
  ## High-signal rules
31
31
 
32
- - `"` creates `str` in runtime code; template strings create `String`. In `comptime` functions, `"hello"` is `comptime_string` (distinct from `str`).
32
+ - `"` creates `str` in runtime code; template strings create `String`. In `comptime` functions, `"hello"` is `comptime_str` (distinct from `str`).
33
33
  - Prefer template strings for constant `String` values.
34
34
  - Prefer `print`/`println` from `std/fmt` over `printf`.
35
35
  - `Option(T)` and `Result(T, E)` are the default nullable/error carriers.
@@ -24,14 +24,14 @@ println("plain str is also fine");
24
24
 
25
25
  | Type | When you see it | Key behavior |
26
26
  | ----------------- | -------------------------------------------- | -------------------------------------- |
27
- | `str` | `"hello"` in runtime contexts | Slice of bytes, no ownership |
27
+ | `str` | `"hello"` in runtime contexts | View of STATIC bytes, no constraints |
28
28
  | `String` | Template strings `` `hello` `` | Owned UTF-8, reference-counted |
29
- | `comptime_string` | `"hello"` inside `comptime` functions/macros | Compile-time only, distinct from `str` |
29
+ | `comptime_str` | `"hello"` inside `comptime` functions/macros | Compile-time only, distinct from `str` |
30
30
 
31
31
  Key rules:
32
32
 
33
33
  - In **runtime** code, `"hello"` is always `str`. Mixing literal and variable branches in `cond`/`match` works fine.
34
- - In **comptime** functions (return type `comptime(...)`), `"hello"` is `comptime_string`. It does NOT auto-convert to `str`. Use `str.from_raw_parts(*(u8)("..."), usize(N))` if a comptime function needs to return `str`.
34
+ - In **comptime** functions (return type `comptime(...)`), `"hello"` is `comptime_str`. It does NOT auto-convert to `str`. A comptime function returning `str` materializes its `comptime_str` result automatically.
35
35
  - For `String` constants, prefer `` `hello` `` over `String.from("hello")`.
36
36
  - **PITFALL:** Never write `String.from(`hello`)` — backtick strings are already `String`, not `str`. `String.from` takes `str`, so wrapping a backtick in `String.from` causes a type error ("Cannot unify String and str"). Only use `String.from(str_expr)` for actual `str` values.
37
37
 
@@ -78,6 +78,30 @@ text := match(parsed,
78
78
  - Prefer combinators for straight-line transforms: `map`, `and_then`, `map_err`, `or_else`
79
79
  - Switch to `match(...)` when branches need different logic or side effects
80
80
 
81
+ ### Match destructuring: prefer curly `{field}`, avoid positional `_` padding
82
+
83
+ For a variant with **2+ fields**, destructure by **name** with curly braces —
84
+ name only the fields the arm uses. Do NOT count positions and pad with `_`.
85
+
86
+ ```rust
87
+ // ✅ Curly — names only what you need; order-free; partial matches OK.
88
+ // Robust: adding a field to the variant later doesn't shift anything.
89
+ match(v,
90
+ .FuncVal({ func_id }) => use(func_id), // bind field `func_id`
91
+ .Struct({ id, name: n }) => use2(id, n), // rename via `field: alias`
92
+ .EnumT({ id }) => use3(id), // ignore the other 6 fields
93
+ _ => ()
94
+ )
95
+
96
+ // ❌ Avoid — positional with many `_`; brittle and unreadable:
97
+ // .FuncVal(_, _, _, _, _, _, _, _, func_id) => … // count the 8 _'s!
98
+ ```
99
+
100
+ `{a}` = bind field `a`; `{a: x}` = rename to `x`; `{a: _}` = assert-exists-ignore.
101
+ Empty `{}` and bare `{_}` are rejected. Spec: `tests/match_curly.test.yo`.
102
+ (Full rules: `.github/instructions/yo-syntax.instructions.md` § Match
103
+ destructuring forms.)
104
+
81
105
  ## Collections
82
106
 
83
107
  ```rust
@@ -237,6 +261,29 @@ impl(forall(T), where(T <: ToString), Box(T),
237
261
  - `forall(T)` + `where(T <: Trait)` for generic impls
238
262
  - Trait impls: `impl(MyType, MyTrait(args), : trait_field_bindings...)`
239
263
 
264
+ ### Method overloading: inherent NO, trait YES
265
+
266
+ Inherent methods cannot be overloaded — a second same-name inherent method is
267
+ rejected ("Method already defined" across impl blocks, "variable shadowing"
268
+ within one). But **trait-provided methods may share a name** with an inherent
269
+ method and with same-name methods from other traits; dispatch picks by
270
+ argument types. This is how std gives `String` both `contains(String)`
271
+ (inherent) and `contains(str)` (via the `StrPattern` trait), and both
272
+ `Eq(String)` and `Eq(str)` `(==)` overloads:
273
+
274
+ ```rust
275
+ PickStr :: trait(pick : (fn(self : Self, x : str) -> i32));
276
+ impl(V, pick : (fn(self : Self, x : V) -> i32)(i32(1))); // inherent
277
+ impl(V, PickStr(pick : (fn(self : Self, x : str) -> i32)(i32(2))));
278
+ v.pick(v); // 1 — inherent overload
279
+ v.pick("s"); // 2 — trait overload, chosen by argument type
280
+ ```
281
+
282
+ - Heterogeneous parametric-trait impls work: `impl(String, Eq(str)(...))`
283
+ beside `impl(String, Eq(String)(...))`; `x == "lit"` dispatches by RHS type.
284
+ - Provide only `(==)`; `(!=)` comes from the `Eq` trait's `?=` default and
285
+ resolves to the right overload by argument types.
286
+
240
287
  ## Partial application
241
288
 
242
289
  ```rust
@@ -303,7 +350,7 @@ node_eq :: (fn(a : Node, b : Node) -> bool)(
303
350
  true => {
304
351
  (i : usize) = usize(0);
305
352
  (ok : bool) = true;
306
- while(runtime(((i < acs.len()) && ok)), {
353
+ while(((i < acs.len()) && ok), {
307
354
  match(acs.get(i),
308
355
  .Some(ac) => match(bcs.get(i),
309
356
  .Some(bc) => { ok = recur(ac, bc); },
@@ -376,8 +423,10 @@ safe_div :: (fn(a : i32, b : i32) -> Result(i32, DivError))(
376
423
  result := inc(i32(5));
377
424
 
378
425
  transform :: (fn(values : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
379
- for(values, ref(x) => {
380
- x = f(x);
426
+ i := usize(0);
427
+ while(i < values.len(), {
428
+ values(i) = f(values(i));
429
+ i = (i + usize(1));
381
430
  });
382
431
  });
383
432
  ```
@@ -396,27 +445,28 @@ list := ArrayList(i32).new();
396
445
  list.push(i32(1));
397
446
  list.push(i32(2));
398
447
 
399
- // Value form — implicit .into_iter().
448
+ // Value form — implicit .into_iter(). The only form.
400
449
  for(list, (value) => {
401
450
  println(value);
402
451
  });
403
452
 
404
- // Borrow form implicit .iter() + .project(pos). `x` is a writable
405
- // binding into the collection; assignments propagate back.
406
- for(list, ref(x) => {
407
- x = (x + i32(10));
453
+ // In-place element mutation: index writes.
454
+ i := usize(0);
455
+ while(i < list.len(), {
456
+ list(i) = (list(i) + i32(10));
457
+ i = (i + usize(1));
408
458
  });
409
459
  ```
410
460
 
411
- | Form | Expansion | When to use |
412
- | ----------------------------- | ------------------------------------------ | --------------------------------------------- |
413
- | `for(coll, (x) => …)` | `coll.into_iter()`, yields `T` by value | Read-only iteration; combinator chains |
414
- | `for(coll, ref(x) => …)` | `coll.iter()` + `coll.project(pos)` borrow | Mutation in place; writes propagate to `coll` |
415
- | `for(chain.map(f), (x) => …)` | Treats chain as the iterator (value form) | Computed values; chains support only value |
461
+ | Form | Expansion | When to use |
462
+ | ----------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------- |
463
+ | `for(coll, (x) => …)` | `coll.into_iter()`, yields `T` by value | All iteration; object elements are handles and mutate in place |
464
+ | index loop + `coll(i) = v` | Index trait read/write | In-place struct/scalar element mutation |
465
+ | `for(chain.map(f), (x) => …)` | Treats chain as the iterator (value form) | Computed values |
416
466
 
467
+ - The borrow form `for(coll, ref(x) => …)` was REMOVED (v4, plans/BORROW_EXCLUSIVITY.md — no interior refs); it emits a teaching compile error.
417
468
  - `Iterator` trait — defines `next() -> Option(Item)`. Custom iterables impl this.
418
469
  - `IntoIterator` trait — defines `into_iter() -> IntoIter`. Collections impl this so `for(coll, ...)` works.
419
- - `Indexable(Position)` trait — defines `project(pos) -> ref(Element)`. Collections impl this to support the borrow form.
420
470
 
421
471
  ## Module-level mutable variables
422
472
 
@@ -450,13 +500,17 @@ result := my_module.helper(i32(5));
450
500
 
451
501
  Several `yo-self/` APIs take `String` (not `str`) parameters even when the argument is conceptually a name:
452
502
 
453
- - `get_variables_from_env(env, name: String)` — pass the `String` directly, do NOT call `.as_str()` first
503
+ - `get_variables_from_env(env, name: String)` — pass the `String` directly
454
504
  - Most other env/value/type lookup functions follow the same convention
505
+ - (`as_str()` no longer exists — heap Strings can never become `str`.)
455
506
 
456
507
  ```rust
457
- // Wrong .as_str() converts String → str but the param is String
458
- vars := get_variables_from_env(env, prop_name_su.as_str());
459
-
460
- // ✅ Correct — pass the String directly
508
+ // Pass the String directly
461
509
  vars := get_variables_from_env(env, prop_name_su);
462
510
  ```
511
+
512
+ String/str comparisons never need `as_str()` either (slice-rework step 2
513
+ swept all of them): `token.value == "fn"`, `name != other_string`, and
514
+ `"lit" == x` all dispatch directly via the heterogeneous `Eq(str)`/
515
+ `Eq(String)` impls. `as_str()` itself is slated for deletion
516
+ (plans/SLICE_REWORK.md) — do not introduce new calls to it.
@@ -50,7 +50,7 @@ Use this skill when you need to:
50
50
  - Unary operators need parenthesized operands: `!(ready)`, `&(value)`.
51
51
  - Use `while(true, { ... })` for infinite runtime loops; use `while(comptime(cond), { ... })` only for compile-time unrolling.
52
52
  - A single-expression lambda body should not be wrapped in `{ ... }` unless semicolons make it a begin block.
53
- - `"hello"` is `comptime_string` inside `comptime` functions, not `str`. In runtime code, `"hello"` is always `str`.
53
+ - `"hello"` is `comptime_str` inside `comptime` functions, not `str`. In runtime code, `"hello"` is always `str`.
54
54
  - Calls in match/cond branches must use immediate `(...)`; this avoids trailing-comma ambiguity.
55
55
 
56
56
  ## Resource
@@ -95,7 +95,7 @@ if(done, println("done"), println("pending"));
95
95
  | Syntax | Type | Context |
96
96
  | ------------------ | ----------------- | -------------------------------- |
97
97
  | `"hello"` | `str` | Runtime contexts (most code) |
98
- | `"hello"` | `comptime_string` | Inside `comptime` functions |
98
+ | `"hello"` | `comptime_str` | Inside `comptime` functions |
99
99
  | `` `hello ${x}` `` | `String` | Always (template string) |
100
100
  | `` `hello` `` | `String` | Always (template without interp) |
101
101
  | `*(u8)("hello")` | `*(u8)` | Pointer cast for C interop |
@@ -103,7 +103,7 @@ if(done, println("done"), println("pending"));
103
103
  Key rules:
104
104
 
105
105
  - In **runtime** code, `"hello"` is `str`. Mixing literals and variables in `cond`/`match` branches is fine.
106
- - In **comptime** functions (return type `comptime(...)`), `"hello"` is `comptime_string` — it does NOT auto-convert to `str`.
106
+ - In **comptime** functions (return type `comptime(...)`), `"hello"` is `comptime_str` — it does NOT auto-convert to `str`.
107
107
  - For `String` constants, prefer `` `hello` `` over `String.from("hello")`.
108
108
  - **`String.from(`` `...` ``)` is WRONG**: `` `...` `` is already `String`; `String.from` takes `str`. Use `` `...` `` directly or `String.from("...")` with double quotes.
109
109
  - **`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.
@@ -130,10 +130,11 @@ masked := ((A | B) | C);
130
130
  - **Pointer deref (`p.*`), arithmetic (`&+`, `&-`, `&/`), and `consume(p.* = v)` require `unsafe(...)`, AND the file must declare `pragma(Pragma.AllowUnsafe);` at the top before `unsafe(...)` is usable.** Pointer comparison (`&==`, `&<`, etc.) and pointer-type casts (`*(u8)(p)`) stay safe. `unsafe(expr)` is a one-arg builtin call: `v := unsafe(p.*);`, `unsafe(p.* = i32(5));`, `unsafe(p &+ usize(1))`. Every file in `std/`, `yo-self/`, and `tests/` declares the pragma explicitly. User code (default) does not, so attempts to use `unsafe(...)` are rejected with a hint to add the pragma. See `plans/MEMORY_SAFETY.md`.
131
131
  - **In-place mutation without raw pointers:** use the `ref(name) : T` parameter modifier (parallel to `own(name)`). `swap :: (fn(ref(a) : i32, ref(b) : i32) -> unit)({ tmp := a; a = b; b = tmp; });` — caller writes `swap(x, y)` with no `&()` syntax. The compiler lowers `ref(name) : T` to `T*` in C and inserts `&(arg)` at the call site automatically. Cannot combine with `own(...)` or with `forall`/`using` (those are erased at runtime — no binding to mutate). CAN combine with `comptime` as `comptime(ref(name)) : T` — the parameter is erased at runtime and mutations propagate via the evaluator's compile-time binding update path (used by prelude `ComptimeIndex`). See `plans/MEMORY_SAFETY.md` Phase B.
132
132
  - **Object-type params:** use plain `name : Type`, NOT `*(Type)` or `ref(name) : Type`. Object types (`Environment`, `EvalContext`, `CodegenContext`, `Emitter`, `HashMap`, `ArrayList`, …) carry reference semantics — passing by name already shares the underlying RC state, so mutations through the param propagate to the caller. `*(Type)` requires `pragma(Pragma.AllowUnsafe);` for the `.* ` derefs and clutters the API; `ref(name) : Type` is redundant since object semantics already share state. Use the plain form: `foo :: (fn(ctx : EvalContext) -> unit)(ctx.method());`. The same applies at call sites — don't wrap object arguments with `&(obj)`; just pass `obj`. For receivers on object methods, plain `self : Self` is the idiom (`yo-self/env.yo`, `yo-self/codegen/context.yo`, `yo-self/emitter.yo` all follow this). `ref(self) : Self` is reserved for receivers on value-type methods (the form used by `Hash`, `Clone`, `ToString`, `Index`, `ComptimeIndex`, `Writer`, `Reader`).
133
- - **Byte-buffer params:** prefer `Slice(u8)` over `*(u8) + usize` for public signatures (e.g. `random_bytes`, `fnv1a_hash_bytes`). `Slice` carries the length, eliminating the (`ptr`, `wrong-size`) footgun. Convert at the FFI seam with `slice.ptr()` and `slice.len()`; construct from existing storage with `Slice(u8).from_raw_parts(&(buf(0)), len)`. The `_cstr` family is the explicit raw-pointer variant — those names signal raw-pointer use by contract.
133
+ - **Byte-buffer params:** for SAFE public signatures use owned collections (`ArrayList(u8)`/`String`). For pragma'd internals/FFI, `RawSlice(u8)` carries ptr+len (construct with `RawSlice(u8)(ptr : &(buf(0)), len : n)`; read `.ptr`/`.len` fields). The `_cstr` family is the explicit raw-pointer variant — those names signal raw-pointer use by contract.
134
134
  - **Audit public stdlib safety with `./yo-cli public-safe-report [path]`.** Flags every top-level public `fn(...)` whose params or return type expose `*(T)` outside an `extern(...)` block. Skips FFI-by-construction directories (`libc/`, `linux/`, `darwin/`, `cuda/`, `sys/`, `sync/`) and names that signal raw-pointer use by contract (`*_cstr`, `*_ptr`, `*_raw`, `raw_*`, `from_raw_parts`, `as_ptr`, `argv`, `argc`). Currently reports 0 findings on `./std` and `./yo-self`; keep it that way when adding new APIs.
135
135
  - **Extern "c" call sites require `unsafe(...)` even in pragma'd files.** `unsafe(memcpy(dst, src, n))`, `unsafe(strlen(s))`, etc. The pragma authorizes DECLARING the FFI symbol via `extern(...)` / `c_include(...)`; the wrap is the per-call audit marker so `yo unsafe-report` lines up with UB-capable lines. `asm(...)` and `extern(...)` / `c_include(...)` declarations themselves do NOT need a wrap (the keyword / declaration syntax is its own marker). See `plans/EXTERN_UNSAFE_WRAP.md`.
136
- - **Slice-flowability rule:** a function returning a slice-bearing type (`Slice(T)`, `str`, a struct wrapping a Slice, ...) must root the returned value in caller-owned storage (a `ref`-bound parameter, any non-`ref` parameter, a `comptime`/literal source, or a flowable projection chain). `(fn() -> Option(Slice(i32)))({ arr := ArrayList(i32).new(); arr.as_slice() })` is rejected; `(fn(ref(arr) : ArrayList(i32)) -> Option(Slice(i32)))(arr.as_slice())` is accepted. See `plans/SLICE_FLOWABILITY.md`.
136
+ - **Static-str model (post slice-rework):** builtin `Slice(T)`, `as_str()`, `as_slice()` are DELETED. `str` = static string view (no flow constraints); ranges COPY (`arr(a..b)` → ArrayList, String range → String, str range str window); safe windows = `ListView(T)`; pragma'd ptr+len = `RawSlice(T)` (naming any raw-ptr-carrying type in an annotation requires the pragma). See `docs/en-US/FLOWABILITY.md`.
137
+ - **`ref` is PARAMETER-ONLY (v4.1, plans/BORROW_EXCLUSIVITY.md).** `-> ref(T)`, `-> (ref(name) : T)`, `-> (name : ref(T))` AND the local binding form `ref(r) := lvalue` are all rejected (both compilers, teaching errors). Refs exist ONLY as `ref(name) : T` parameters. Migrations: return the value (object values are handles that mutate in place; struct values copy); read/write fields directly (`h.s = v`); bind the handle (`b := a.b`) to keep an object alive; or take a callback parameter receiving `ref(v) : T` (`Mutex.with_lock` pattern). A ref ARGUMENT is a simple lvalue place: a variable, or `var.field` rooted at a local/param — intermediate-OBJECT hops and module-level field roots are rejected (bind to a local first). `comptime` return modifiers go on the LABEL when labeled: `-> comptime(T)` / `-> (comptime(name) : T)` valid; `-> (name : comptime(T))` rejected. See `tests/ref_return_ban.test.yo`, `tests/ref_local_binding.test.yo`, `tests/ref_field_borrow.test.yo`.
137
138
  - **Signed-integer overflow is defined (wrap-around).** Yo passes `-fwrapv` to clang/gcc/zig by default so `x + i32(1)` on `i32(MAX)` wraps to `i32(MIN)` instead of UB. Opt-out: `--cflags='-fno-wrapv'`.
138
139
  - **`// SAFETY:` comment convention.** Every non-obvious `unsafe(...)` site in stdlib should have a `// SAFETY:` comment in the previous ~8 lines explaining the contract. `yo unsafe-report` picks them up and shows them inline under each finding.
139
140
  - **User-facing memory-safety guide:** `docs/en-US/MEMORY_SAFETY.md` (English) and `docs/zh-CN/MEMORY_SAFETY.md` (Chinese). Refer users there instead of `plans/MEMORY_SAFETY.md` (which is the design document — not shipped via npm).
@@ -226,8 +227,10 @@ caller :: (fn() -> i32)({
226
227
  result := closure(i32(5));
227
228
 
228
229
  transform :: (fn(list : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
229
- for(list, ref(x) => {
230
- x = f(x);
230
+ i := usize(0);
231
+ while(i < list.len(), {
232
+ list(i) = f(list(i));
233
+ i = (i + usize(1));
231
234
  });
232
235
  });
233
236
  ```
@@ -381,29 +384,34 @@ while(comptime((i < 10)), {
381
384
  // body evaluated/unrolled at compile time
382
385
  });
383
386
 
384
- // for loop — 2-arg prelude macro. First arg is the collection
385
- // directly; the macro dispatches on the body's binding shape to
386
- // pick value-form vs borrow-form iteration:
387
- for(list, (x) => { // value form: implicit .into_iter()
387
+ // for loop — 2-arg prelude macro iterating BY VALUE (implicit
388
+ // .into_iter()). Object elements are handles: mutating them in the
389
+ // body mutates the element in place.
390
+ for(list, (x) => {
388
391
  process(x);
389
392
  });
393
+ for(names, (s) => {
394
+ s.push_str("!"); // String element mutated in place
395
+ });
390
396
 
391
- for(list, ref(x) => { // borrow form: iter() + project(pos)
392
- x = transform(x); // writes propagate back into list
397
+ // In-place struct/scalar element mutation: index loop + index writes.
398
+ i := usize(0);
399
+ while(i < list.len(), {
400
+ list(i) = transform(list(i));
401
+ i = (i + usize(1));
393
402
  });
394
403
 
395
404
  // Combinator chains (.map / .filter / .into_iter / etc.) yield
396
- // computed values; pass them as the first arg in the value form:
397
- for(list.iter().map((x) => (x + i32(1))), (y) => println(y));
405
+ // computed values; pass them as the first arg:
406
+ for(list.into_iter().map((x) => (x + i32(1))), (y) => println(y));
398
407
  ```
399
408
 
400
409
  - Use `recur(...)` for self-recursion
401
410
  - `while(cond, body)` is **always a runtime loop** — use this for open-ended loops (e.g., server accept loops, event loops)
402
411
  - `while(comptime(cond), body)` explicitly unrolls at compile time — `cond` must be a compile-time-known value
403
412
  - Using a comptime-only (`::`) variable in a bare `while` condition without `comptime()` is a **compile error** (would be an infinite loop at runtime)
404
- - **`for(coll, (x) => body)`** — value form; macro expands to `coll.into_iter()` then iterates by value (`x : T`).
405
- - **`for(coll, ref(x) => body)`** borrow form; macro expands to `coll.iter()` (position iterator) + `coll.project(pos)` (`Indexable.project` impl) so `x` is a writable binding. Writes propagate back into the collection.
406
- - **Do NOT write `for(coll.iter(), (x) => …)` for the value form** — `.iter()` yields positions (usize), not the collection's elements. Use the bare collection or `.into_iter()`.
413
+ - **`for(coll, (x) => body)`** — the only form; macro expands to `coll.into_iter()` then iterates by value (`x : T`; a handle for object element types).
414
+ - **The borrow form `for(coll, ref(x) => body)` was REMOVED** (v4, plans/BORROW_EXCLUSIVITY.md no interior refs); it emits a teaching compile error with the migration recipe.
407
415
  - **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`)
408
416
 
409
417
  ## Return and branch safety
@@ -443,7 +451,7 @@ get_value :: (fn(opt : Option(i32)) -> i32)(
443
451
  ## String concatenation pitfall
444
452
 
445
453
  ```rust
446
- // WRONG — str + str causes "comptime_string vs str" type unification error:
454
+ // WRONG — str + str causes "comptime_str vs str" type unification error:
447
455
  content := String.from("line1\n" + "line2\n");
448
456
 
449
457
  // CORRECT — use .concat() on String objects:
@@ -454,7 +462,7 @@ content := String.from("line1\nline2\n");
454
462
  ```
455
463
 
456
464
  - `"hello" + "world"` at runtime uses `+` on `str` values, which can cause type mismatches
457
- - The `str + str` operator can produce a `comptime_string` in some contexts, which is not always compatible with `str`
465
+ - The `str + str` operator can produce a `comptime_str` in some contexts, which is not always compatible with `str`
458
466
  - Prefer `.concat()` method on `String` objects when building multi-part strings at runtime
459
467
 
460
468
  ## Iterator and for loop
@@ -466,22 +474,23 @@ list := ArrayList(i32).new();
466
474
  list.push(i32(10));
467
475
  list.push(i32(20));
468
476
 
469
- // Value form — implicit .into_iter().
477
+ // Value form — implicit .into_iter(). The only form.
470
478
  for(list, (value) => {
471
479
  println(value);
472
480
  });
473
481
 
474
- // Borrow form implicit .iter() + .project(pos). `x` is a writable
475
- // binding into the collection; assignments propagate back.
476
- for(list, ref(x) => {
477
- x = (x + i32(1));
482
+ // In-place element mutation: index writes (struct/scalar elements) or
483
+ // mutate the handle (object elements).
484
+ i := usize(0);
485
+ while(i < list.len(), {
486
+ list(i) = (list(i) + i32(1));
487
+ i = (i + usize(1));
478
488
  });
479
489
  ```
480
490
 
481
- - `for(coll, (x) => body)` — value form. Macro expands to `coll.into_iter()` and yields elements by value.
482
- - `for(coll, ref(x) => body)` borrow form. Macro expands to `coll.iter()` (a position iterator yielding `usize`) + `coll.project(pos)` (from the `Indexable` trait) so the body sees a writable binding.
483
- - Combinator chains (`coll.iter().map(f).filter(g)`) only support the value form — pass the chain as the first arg with `(x) => body`.
484
- - The for macro accepts the collection directly; do NOT call `.iter()` for the value form, that yields positions (usize), not elements.
491
+ - `for(coll, (x) => body)` — macro expands to `coll.into_iter()` and yields elements by value (a handle for object element types — mutating it mutates the element in place).
492
+ - The borrow form `for(coll, ref(x) => body)` was REMOVED (v4); it emits a teaching compile error.
493
+ - Combinator chains (`coll.into_iter().map(f).filter(g)`) work as the first arg with `(x) => body`.
485
494
 
486
495
  ## Testing
487
496
 
@@ -524,7 +533,9 @@ divide :: (fn(x : i32, y : i32, requires(y != i32(0)), ensures(result == (x / y)
524
533
  increment :: (fn(ref(n) : i32, ensures(n == (old(n) + i32(1)))) -> unit)({ n = (n + i32(1)); });
525
534
 
526
535
  // invariant(...) must be the FIRST statement of a while body.
527
- while(runtime(i < n), {
536
+ // NOTE: do NOT wrap the condition in runtime(...) while conditions are
537
+ // runtime by default, so `while(runtime(i < n), …)` is redundant; use `while(i < n, …)`.
538
+ while(i < n, {
528
539
  invariant(i <= n, acc >= i32(0));
529
540
  i = (i + i32(1)); acc = (acc + i);
530
541
  });
@@ -638,7 +649,7 @@ Variable :: object(name : String, ty : TypeValue);
638
649
 
639
650
  ### 1-element array literals require a trailing comma
640
651
 
641
- `[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:
652
+ `[expr]` without a trailing comma is **parsed as a slice-type form** (now an error — the builtin Slice type is deleted, so it surfaces "Variable \"Slice\" not found"), not an array literal. To create a 1-element array value, add a trailing comma:
642
653
 
643
654
  ```rust
644
655
  // WRONG — parsed as Slice type, not array literal:
@@ -859,19 +870,12 @@ but cannot rebind the variable (`env = other_env`).
859
870
 
860
871
  ### String cloning
861
872
 
862
- Calling `.clone()` on a `String` field from a struct/method chain requires a
863
- reference take `&` first:
873
+ `.clone()` on a `String` works directly, including on struct fields and
874
+ method-chain results (the historical `*(Self)`-overload ambiguity no
875
+ longer reproduces; verified 2026-06):
864
876
 
865
877
  ```rust
866
- // WRONG .clone() requires *(Self) but gets Self value from field access:
867
- name := token.value.clone(); // ERROR
868
-
869
- // CORRECT — take reference first:
870
- tok := some_fn_call();
871
- name := (&tok.value).clone(); // OK
872
-
873
- // ALSO CORRECT — use String.from on the str slice:
874
- name := String.from(token.value.as_str());
878
+ name := token.value.clone(); // OK
875
879
  ```
876
880
 
877
881
  ### Template strings produce `String`, literals are `str`
@@ -879,13 +883,13 @@ name := String.from(token.value.as_str());
879
883
  ```rust
880
884
  // Template string `` `...` `` → String
881
885
  // String literal "..." → str
882
-
883
- // If a function takes `str`, call .as_str() on a template string:
884
- fn_taking_str((`prefix_${value}`).as_str());
885
-
886
- // Or change the function to take String
887
886
  ```
888
887
 
888
+ A heap `String` can NEVER become `str` (`as_str()` is deleted — `str` is
889
+ the STATIC string view; plans/SLICE_REWORK.md). If a function must accept
890
+ runtime text, its parameter should be `String`; `str` parameters are for
891
+ literals/static text only.
892
+
889
893
  These features are powerful but less commonly used. Consult the linked docs for full details.
890
894
 
891
895
  | Feature | Syntax hint | Documentation |
@@ -940,25 +944,13 @@ The `+` operator does not accept mixed `String`/`str` operands.
940
944
  ```rust
941
945
  // ❌ Type error
942
946
  result := (parts + ", ");
943
- result := (parts + item.as_str());
947
+ result := (parts + item);
944
948
 
945
949
  // ✅ Template strings
946
950
  result := `${parts}, `;
947
951
  result := `${parts}${item}`;
948
952
  ```
949
953
 
950
- ### `clone()` on extracted String fields is ambiguous
951
-
952
- 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))`).
953
-
954
- ```rust
955
- // ❌ Ambiguous
956
- .StructVal(name, fields) => name.clone()
957
-
958
- // ✅ Use from + as_str
959
- .StructVal(name, fields) => String.from(name.as_str())
960
- ```
961
-
962
954
  ### `box(val)` is a move — cannot box the same value twice
963
955
 
964
956
  ```rust
@@ -1014,17 +1006,17 @@ lines.push(`**Implements:** ${sep.join(names)}`);
1014
1006
 
1015
1007
  ### Pushing RC struct fields into ArrayList does not need `.clone()`
1016
1008
 
1017
- 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.
1009
+ String (and other RC object) fields of structs can be passed directly to `ArrayList.push()` the RC bump happens automatically:
1018
1010
 
1019
1011
  ```rust
1020
- // ❌ Ambiguous clone call
1021
- names.push(param.name.clone());
1022
-
1023
- // ✅ Push directly — RC bump happens automatically
1024
1012
  names.push(param.name);
1025
1013
  ```
1026
1014
 
1027
- If explicit clone is needed elsewhere, use `(&field).clone()` to select the pointer overload.
1015
+ `.clone()` on String fields also works (`names.push(param.name.clone())`,
1016
+ `h.name.clone()` — verified 2026-06); the historical
1017
+ `fn(self: String)` vs `fn(self: *(String))` ambiguity error no longer
1018
+ reproduces. `x.clone()` is the idiomatic replacement for the retired
1019
+ `String.from(x.as_str())` roundtrip.
1028
1020
 
1029
1021
  ### `.Some(expr)` in expression position is parsed as a 2-arg property access
1030
1022
 
@@ -1144,13 +1136,13 @@ You cannot write `.Some(.IntLit(n))` — this is a parser error.
1144
1136
  ```rust
1145
1137
  // ❌ WRONG — nested enum pattern, parser error:
1146
1138
  match(v.get(usize(0)),
1147
- .Some(.IntLit(n)) => assert(n.as_str() == "3", "ok"),
1139
+ .Some(.IntLit(n)) => assert(n == "3", "ok"),
1148
1140
  _ => assert(false, "err")
1149
1141
  )
1150
1142
 
1151
1143
  // ✅ CORRECT — two-level match:
1152
1144
  match(v.get(usize(0)),
1153
- .Some(x) => match(x, .IntLit(n) => assert(n.as_str() == "3", "ok"), _ => assert(false, "err")),
1145
+ .Some(x) => match(x, .IntLit(n) => assert(n == "3", "ok"), _ => assert(false, "err")),
1154
1146
  .None => assert(false, "err")
1155
1147
  )
1156
1148
  ```