@shd101wyy/yo 0.1.33 → 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 (46) hide show
  1. package/.github/skills/yo-core-patterns/SKILL.md +1 -1
  2. package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +51 -21
  3. package/.github/skills/yo-syntax/SKILL.md +1 -1
  4. package/.github/skills/yo-syntax/syntax-cheatsheet.md +56 -67
  5. package/out/cjs/index.cjs +664 -649
  6. package/out/cjs/yo-cli.cjs +736 -721
  7. package/out/cjs/yo-lsp.cjs +682 -667
  8. package/out/esm/index.mjs +498 -483
  9. package/out/types/src/codegen/exprs/comptime-value.d.ts +2 -1
  10. package/out/types/src/codegen/types/generation.d.ts +1 -1
  11. package/out/types/src/codegen/utils/index.d.ts +2 -4
  12. package/out/types/src/evaluator/exprs/_expr.d.ts +1 -0
  13. package/out/types/src/evaluator/types/flowability.d.ts +21 -0
  14. package/out/types/src/expr.d.ts +4 -13
  15. package/out/types/src/types/creators.d.ts +2 -3
  16. package/out/types/src/types/definitions.d.ts +2 -3
  17. package/out/types/src/types/guards.d.ts +2 -2
  18. package/out/types/src/types/tags.d.ts +2 -2
  19. package/out/types/src/value-tag.d.ts +0 -1
  20. package/out/types/src/value.d.ts +2 -11
  21. package/out/types/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +1 -1
  23. package/std/alg/hash.yo +5 -3
  24. package/std/build.yo +46 -46
  25. package/std/collections/array_list.yo +73 -66
  26. package/std/collections/hash_map.yo +2 -81
  27. package/std/collections/list_view.yo +77 -0
  28. package/std/crypto/random.yo +5 -5
  29. package/std/encoding/base64.yo +12 -13
  30. package/std/encoding/hex.yo +6 -6
  31. package/std/encoding/json.yo +6 -5
  32. package/std/encoding/utf16.yo +9 -9
  33. package/std/env.yo +8 -8
  34. package/std/fmt/to_string.yo +12 -12
  35. package/std/http/client.yo +1 -1
  36. package/std/imm/list.yo +4 -3
  37. package/std/imm/map.yo +3 -2
  38. package/std/imm/set.yo +4 -3
  39. package/std/imm/sorted_map.yo +3 -2
  40. package/std/imm/sorted_set.yo +4 -3
  41. package/std/imm/string.yo +12 -5
  42. package/std/imm/vec.yo +8 -3
  43. package/std/prelude.yo +174 -401
  44. package/std/string/string.yo +198 -71
  45. package/std/url/index.yo +26 -26
  46. package/out/types/src/evaluator/types/slice.d.ts +0 -8
@@ -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
 
@@ -261,6 +261,29 @@ impl(forall(T), where(T <: ToString), Box(T),
261
261
  - `forall(T)` + `where(T <: Trait)` for generic impls
262
262
  - Trait impls: `impl(MyType, MyTrait(args), : trait_field_bindings...)`
263
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
+
264
287
  ## Partial application
265
288
 
266
289
  ```rust
@@ -400,8 +423,10 @@ safe_div :: (fn(a : i32, b : i32) -> Result(i32, DivError))(
400
423
  result := inc(i32(5));
401
424
 
402
425
  transform :: (fn(values : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
403
- for(values, ref(x) => {
404
- x = f(x);
426
+ i := usize(0);
427
+ while(i < values.len(), {
428
+ values(i) = f(values(i));
429
+ i = (i + usize(1));
405
430
  });
406
431
  });
407
432
  ```
@@ -420,27 +445,28 @@ list := ArrayList(i32).new();
420
445
  list.push(i32(1));
421
446
  list.push(i32(2));
422
447
 
423
- // Value form — implicit .into_iter().
448
+ // Value form — implicit .into_iter(). The only form.
424
449
  for(list, (value) => {
425
450
  println(value);
426
451
  });
427
452
 
428
- // Borrow form implicit .iter() + .project(pos). `x` is a writable
429
- // binding into the collection; assignments propagate back.
430
- for(list, ref(x) => {
431
- 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));
432
458
  });
433
459
  ```
434
460
 
435
- | Form | Expansion | When to use |
436
- | ----------------------------- | ------------------------------------------ | --------------------------------------------- |
437
- | `for(coll, (x) => …)` | `coll.into_iter()`, yields `T` by value | Read-only iteration; combinator chains |
438
- | `for(coll, ref(x) => …)` | `coll.iter()` + `coll.project(pos)` borrow | Mutation in place; writes propagate to `coll` |
439
- | `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 |
440
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.
441
468
  - `Iterator` trait — defines `next() -> Option(Item)`. Custom iterables impl this.
442
469
  - `IntoIterator` trait — defines `into_iter() -> IntoIter`. Collections impl this so `for(coll, ...)` works.
443
- - `Indexable(Position)` trait — defines `project(pos) -> ref(Element)`. Collections impl this to support the borrow form.
444
470
 
445
471
  ## Module-level mutable variables
446
472
 
@@ -474,13 +500,17 @@ result := my_module.helper(i32(5));
474
500
 
475
501
  Several `yo-self/` APIs take `String` (not `str`) parameters even when the argument is conceptually a name:
476
502
 
477
- - `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
478
504
  - Most other env/value/type lookup functions follow the same convention
505
+ - (`as_str()` no longer exists — heap Strings can never become `str`.)
479
506
 
480
507
  ```rust
481
- // Wrong .as_str() converts String → str but the param is String
482
- vars := get_variables_from_env(env, prop_name_su.as_str());
483
-
484
- // ✅ Correct — pass the String directly
508
+ // Pass the String directly
485
509
  vars := get_variables_from_env(env, prop_name_su);
486
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,11 +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`.
137
- - **Return-slot modifier placement: on the LABEL, not the type.** In a _labeled_ return slot, a `ref`/`comptime` modifier attaches to the label, mirroring the parameter convention (`ref(name) : T`). Valid: `-> ref(T)` and `-> comptime(T)` (unlabeled modifier on the sole type), `-> (ref(name) : T)`, `-> (comptime(name) : T)`. **Rejected:** `-> (name : ref(T))`, `-> (name : comptime(T))` (modifier on the type when labeled), and `-> (ref(name) : ref(T))` (double-ref "pick one"). Enforced at function-type eval in `src/evaluator/types/function.ts` (and the yo-self port).
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`.
138
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'`.
139
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.
140
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).
@@ -227,8 +227,10 @@ caller :: (fn() -> i32)({
227
227
  result := closure(i32(5));
228
228
 
229
229
  transform :: (fn(list : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
230
- for(list, ref(x) => {
231
- x = f(x);
230
+ i := usize(0);
231
+ while(i < list.len(), {
232
+ list(i) = f(list(i));
233
+ i = (i + usize(1));
232
234
  });
233
235
  });
234
236
  ```
@@ -382,29 +384,34 @@ while(comptime((i < 10)), {
382
384
  // body evaluated/unrolled at compile time
383
385
  });
384
386
 
385
- // for loop — 2-arg prelude macro. First arg is the collection
386
- // directly; the macro dispatches on the body's binding shape to
387
- // pick value-form vs borrow-form iteration:
388
- 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) => {
389
391
  process(x);
390
392
  });
393
+ for(names, (s) => {
394
+ s.push_str("!"); // String element mutated in place
395
+ });
391
396
 
392
- for(list, ref(x) => { // borrow form: iter() + project(pos)
393
- 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));
394
402
  });
395
403
 
396
404
  // Combinator chains (.map / .filter / .into_iter / etc.) yield
397
- // computed values; pass them as the first arg in the value form:
398
- 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));
399
407
  ```
400
408
 
401
409
  - Use `recur(...)` for self-recursion
402
410
  - `while(cond, body)` is **always a runtime loop** — use this for open-ended loops (e.g., server accept loops, event loops)
403
411
  - `while(comptime(cond), body)` explicitly unrolls at compile time — `cond` must be a compile-time-known value
404
412
  - Using a comptime-only (`::`) variable in a bare `while` condition without `comptime()` is a **compile error** (would be an infinite loop at runtime)
405
- - **`for(coll, (x) => body)`** — value form; macro expands to `coll.into_iter()` then iterates by value (`x : T`).
406
- - **`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.
407
- - **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.
408
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`)
409
416
 
410
417
  ## Return and branch safety
@@ -444,7 +451,7 @@ get_value :: (fn(opt : Option(i32)) -> i32)(
444
451
  ## String concatenation pitfall
445
452
 
446
453
  ```rust
447
- // WRONG — str + str causes "comptime_string vs str" type unification error:
454
+ // WRONG — str + str causes "comptime_str vs str" type unification error:
448
455
  content := String.from("line1\n" + "line2\n");
449
456
 
450
457
  // CORRECT — use .concat() on String objects:
@@ -455,7 +462,7 @@ content := String.from("line1\nline2\n");
455
462
  ```
456
463
 
457
464
  - `"hello" + "world"` at runtime uses `+` on `str` values, which can cause type mismatches
458
- - 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`
459
466
  - Prefer `.concat()` method on `String` objects when building multi-part strings at runtime
460
467
 
461
468
  ## Iterator and for loop
@@ -467,22 +474,23 @@ list := ArrayList(i32).new();
467
474
  list.push(i32(10));
468
475
  list.push(i32(20));
469
476
 
470
- // Value form — implicit .into_iter().
477
+ // Value form — implicit .into_iter(). The only form.
471
478
  for(list, (value) => {
472
479
  println(value);
473
480
  });
474
481
 
475
- // Borrow form implicit .iter() + .project(pos). `x` is a writable
476
- // binding into the collection; assignments propagate back.
477
- for(list, ref(x) => {
478
- 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));
479
488
  });
480
489
  ```
481
490
 
482
- - `for(coll, (x) => body)` — value form. Macro expands to `coll.into_iter()` and yields elements by value.
483
- - `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.
484
- - Combinator chains (`coll.iter().map(f).filter(g)`) only support the value form — pass the chain as the first arg with `(x) => body`.
485
- - 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`.
486
494
 
487
495
  ## Testing
488
496
 
@@ -641,7 +649,7 @@ Variable :: object(name : String, ty : TypeValue);
641
649
 
642
650
  ### 1-element array literals require a trailing comma
643
651
 
644
- `[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:
645
653
 
646
654
  ```rust
647
655
  // WRONG — parsed as Slice type, not array literal:
@@ -862,19 +870,12 @@ but cannot rebind the variable (`env = other_env`).
862
870
 
863
871
  ### String cloning
864
872
 
865
- Calling `.clone()` on a `String` field from a struct/method chain requires a
866
- 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):
867
876
 
868
877
  ```rust
869
- // WRONG .clone() requires *(Self) but gets Self value from field access:
870
- name := token.value.clone(); // ERROR
871
-
872
- // CORRECT — take reference first:
873
- tok := some_fn_call();
874
- name := (&tok.value).clone(); // OK
875
-
876
- // ALSO CORRECT — use String.from on the str slice:
877
- name := String.from(token.value.as_str());
878
+ name := token.value.clone(); // OK
878
879
  ```
879
880
 
880
881
  ### Template strings produce `String`, literals are `str`
@@ -882,13 +883,13 @@ name := String.from(token.value.as_str());
882
883
  ```rust
883
884
  // Template string `` `...` `` → String
884
885
  // String literal "..." → str
885
-
886
- // If a function takes `str`, call .as_str() on a template string:
887
- fn_taking_str((`prefix_${value}`).as_str());
888
-
889
- // Or change the function to take String
890
886
  ```
891
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
+
892
893
  These features are powerful but less commonly used. Consult the linked docs for full details.
893
894
 
894
895
  | Feature | Syntax hint | Documentation |
@@ -943,25 +944,13 @@ The `+` operator does not accept mixed `String`/`str` operands.
943
944
  ```rust
944
945
  // ❌ Type error
945
946
  result := (parts + ", ");
946
- result := (parts + item.as_str());
947
+ result := (parts + item);
947
948
 
948
949
  // ✅ Template strings
949
950
  result := `${parts}, `;
950
951
  result := `${parts}${item}`;
951
952
  ```
952
953
 
953
- ### `clone()` on extracted String fields is ambiguous
954
-
955
- 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))`).
956
-
957
- ```rust
958
- // ❌ Ambiguous
959
- .StructVal(name, fields) => name.clone()
960
-
961
- // ✅ Use from + as_str
962
- .StructVal(name, fields) => String.from(name.as_str())
963
- ```
964
-
965
954
  ### `box(val)` is a move — cannot box the same value twice
966
955
 
967
956
  ```rust
@@ -1017,17 +1006,17 @@ lines.push(`**Implements:** ${sep.join(names)}`);
1017
1006
 
1018
1007
  ### Pushing RC struct fields into ArrayList does not need `.clone()`
1019
1008
 
1020
- 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:
1021
1010
 
1022
1011
  ```rust
1023
- // ❌ Ambiguous clone call
1024
- names.push(param.name.clone());
1025
-
1026
- // ✅ Push directly — RC bump happens automatically
1027
1012
  names.push(param.name);
1028
1013
  ```
1029
1014
 
1030
- 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.
1031
1020
 
1032
1021
  ### `.Some(expr)` in expression position is parsed as a 2-arg property access
1033
1022
 
@@ -1147,13 +1136,13 @@ You cannot write `.Some(.IntLit(n))` — this is a parser error.
1147
1136
  ```rust
1148
1137
  // ❌ WRONG — nested enum pattern, parser error:
1149
1138
  match(v.get(usize(0)),
1150
- .Some(.IntLit(n)) => assert(n.as_str() == "3", "ok"),
1139
+ .Some(.IntLit(n)) => assert(n == "3", "ok"),
1151
1140
  _ => assert(false, "err")
1152
1141
  )
1153
1142
 
1154
1143
  // ✅ CORRECT — two-level match:
1155
1144
  match(v.get(usize(0)),
1156
- .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")),
1157
1146
  .None => assert(false, "err")
1158
1147
  )
1159
1148
  ```