@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.
- package/.github/skills/yo-async-effects/async-effects-recipes.md +2 -2
- package/.github/skills/yo-core-patterns/SKILL.md +1 -1
- package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +76 -22
- package/.github/skills/yo-syntax/SKILL.md +1 -1
- package/.github/skills/yo-syntax/syntax-cheatsheet.md +59 -67
- package/out/cjs/index.cjs +662 -574
- package/out/cjs/yo-cli.cjs +803 -715
- package/out/cjs/yo-lsp.cjs +771 -683
- package/out/esm/index.mjs +555 -467
- package/out/types/src/codegen/exprs/comptime-value.d.ts +2 -1
- package/out/types/src/codegen/functions/context.d.ts +1 -0
- package/out/types/src/codegen/types/generation.d.ts +1 -1
- package/out/types/src/codegen/utils/index.d.ts +2 -4
- package/out/types/src/evaluator/exprs/_expr.d.ts +1 -0
- package/out/types/src/evaluator/types/flowability.d.ts +22 -0
- package/out/types/src/expr.d.ts +4 -13
- package/out/types/src/types/creators.d.ts +2 -3
- package/out/types/src/types/definitions.d.ts +2 -3
- package/out/types/src/types/guards.d.ts +2 -2
- package/out/types/src/types/tags.d.ts +2 -2
- package/out/types/src/value-tag.d.ts +0 -1
- package/out/types/src/value.d.ts +2 -11
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/std/alg/hash.yo +5 -3
- package/std/build.yo +46 -46
- package/std/collections/array_list.yo +73 -66
- package/std/collections/hash_map.yo +53 -95
- package/std/collections/list_view.yo +77 -0
- package/std/crypto/random.yo +5 -5
- package/std/encoding/base64.yo +12 -13
- package/std/encoding/hex.yo +6 -6
- package/std/encoding/json.yo +6 -5
- package/std/encoding/utf16.yo +9 -9
- package/std/env.yo +8 -8
- package/std/fmt/to_string.yo +12 -12
- package/std/http/client.yo +1 -1
- package/std/imm/list.yo +4 -3
- package/std/imm/map.yo +3 -2
- package/std/imm/set.yo +4 -3
- package/std/imm/sorted_map.yo +3 -2
- package/std/imm/sorted_set.yo +4 -3
- package/std/imm/string.yo +12 -5
- package/std/imm/vec.yo +8 -3
- package/std/prelude.yo +178 -401
- package/std/string/string.yo +198 -71
- package/std/url/index.yo +26 -26
- 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(
|
|
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(
|
|
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 `
|
|
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 |
|
|
27
|
+
| `str` | `"hello"` in runtime contexts | View of STATIC bytes, no constraints |
|
|
28
28
|
| `String` | Template strings `` `hello` `` | Owned UTF-8, reference-counted |
|
|
29
|
-
| `
|
|
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 `
|
|
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(
|
|
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
|
-
|
|
380
|
-
|
|
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
|
-
//
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
|
412
|
-
| ----------------------------- |
|
|
413
|
-
| `for(coll, (x) => …)` | `coll.into_iter()`, yields `T` by value
|
|
414
|
-
|
|
|
415
|
-
| `for(chain.map(f), (x) => …)` | Treats chain as the iterator (value form)
|
|
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
|
|
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
|
-
//
|
|
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 `
|
|
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"` | `
|
|
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 `
|
|
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:**
|
|
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
|
-
- **
|
|
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
|
-
|
|
230
|
-
|
|
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
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
for(list, (x) => {
|
|
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
|
-
|
|
392
|
-
|
|
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
|
|
397
|
-
for(list.
|
|
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)`** —
|
|
405
|
-
-
|
|
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 "
|
|
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 `
|
|
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
|
-
//
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
|
|
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)` —
|
|
482
|
-
- `for(coll, ref(x) => body)`
|
|
483
|
-
- Combinator chains (`coll.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
863
|
-
|
|
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
|
-
|
|
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
|
|
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()
|
|
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
|
-
|
|
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
|
|
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
|
|
1145
|
+
.Some(x) => match(x, .IntLit(n) => assert(n == "3", "ok"), _ => assert(false, "err")),
|
|
1154
1146
|
.None => assert(false, "err")
|
|
1155
1147
|
)
|
|
1156
1148
|
```
|