@shd101wyy/yo 0.1.29 → 0.1.30
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/SKILL.md +3 -3
- package/.github/skills/yo-async-effects/async-effects-recipes.md +19 -11
- package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +33 -13
- package/.github/skills/yo-project-workflow/workflow-cheatsheet.md +1 -1
- package/.github/skills/yo-syntax/syntax-cheatsheet.md +59 -21
- package/README.md +4 -3
- package/out/cjs/index.cjs +771 -676
- package/out/cjs/yo-cli.cjs +1003 -898
- package/out/cjs/yo-lsp.cjs +834 -739
- package/out/esm/index.mjs +716 -621
- package/out/types/src/codegen/exprs/async.d.ts +2 -0
- package/out/types/src/codegen/exprs/await.d.ts +1 -0
- package/out/types/src/codegen/exprs/closures.d.ts +4 -0
- package/out/types/src/codegen/functions/context.d.ts +6 -0
- package/out/types/src/env.d.ts +2 -0
- package/out/types/src/evaluator/builtins/pragma.d.ts +9 -0
- package/out/types/src/evaluator/builtins/unsafe.d.ts +8 -0
- package/out/types/src/evaluator/context.d.ts +2 -0
- package/out/types/src/evaluator/index.d.ts +1 -1
- package/out/types/src/evaluator/memory-safety.d.ts +14 -0
- package/out/types/src/evaluator/types/flowability.d.ts +6 -0
- package/out/types/src/expr-traversal.d.ts +1 -0
- package/out/types/src/expr.d.ts +4 -1
- package/out/types/src/public-safe-report.d.ts +19 -0
- package/out/types/src/tests/comptime-ref-gate.test.d.ts +1 -0
- package/out/types/src/tests/pragma-validation.test.d.ts +1 -0
- package/out/types/src/tests/public-safe-report.test.d.ts +1 -0
- package/out/types/src/tests/type-representation-pointer.test.d.ts +1 -0
- package/out/types/src/tests/unsafe-gate.test.d.ts +1 -0
- package/out/types/src/tests/unsafe-report-classify.test.d.ts +1 -0
- package/out/types/src/types/definitions.d.ts +2 -0
- package/out/types/src/types/utils.d.ts +4 -0
- package/out/types/src/unsafe-report.d.ts +29 -0
- package/out/types/src/value.d.ts +1 -0
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/scripts/add-pragma-for-pointer-decls.ts +134 -0
- package/scripts/add-pragma.ts +58 -0
- package/scripts/migrate-amp-method-calls.ts +186 -0
- package/scripts/migrate-clone-calls.ts +93 -0
- package/scripts/migrate-get-unwrap.ts +166 -0
- package/scripts/migrate-index-patterns.ts +210 -0
- package/scripts/migrate-index-trait.ts +142 -0
- package/scripts/migrate-iterator.ts +150 -0
- package/scripts/migrate-self-ptr.ts +220 -0
- package/scripts/migrate-skip-pragmas.ts +109 -0
- package/scripts/migrate-tostring.ts +134 -0
- package/scripts/trim-pragma.ts +130 -0
- package/scripts/wrap-extern-calls.ts +161 -0
- package/std/alg/hash.yo +3 -2
- package/std/allocator.yo +6 -5
- package/std/async.yo +2 -2
- package/std/collections/array_list.yo +59 -40
- package/std/collections/btree_map.yo +19 -18
- package/std/collections/deque.yo +9 -8
- package/std/collections/hash_map.yo +101 -13
- package/std/collections/hash_set.yo +5 -4
- package/std/collections/linked_list.yo +39 -4
- package/std/collections/ordered_map.yo +3 -3
- package/std/collections/priority_queue.yo +14 -13
- package/std/crypto/md5.yo +2 -1
- package/std/crypto/random.yo +16 -15
- package/std/crypto/sha256.yo +2 -1
- package/std/encoding/base64.yo +14 -14
- package/std/encoding/hex.yo +3 -3
- package/std/encoding/json.yo +59 -10
- package/std/encoding/punycode.yo +24 -23
- package/std/encoding/toml.yo +4 -3
- package/std/encoding/utf16.yo +2 -2
- package/std/env.yo +43 -28
- package/std/error.yo +6 -6
- package/std/fmt/display.yo +2 -2
- package/std/fmt/index.yo +6 -5
- package/std/fmt/to_string.yo +39 -38
- package/std/fmt/writer.yo +9 -8
- package/std/fs/dir.yo +34 -33
- package/std/fs/file.yo +52 -51
- package/std/fs/metadata.yo +10 -9
- package/std/fs/temp.yo +24 -13
- package/std/fs/walker.yo +10 -9
- package/std/gc.yo +1 -0
- package/std/glob.yo +7 -7
- package/std/http/client.yo +15 -14
- package/std/http/http.yo +6 -6
- package/std/http/index.yo +1 -1
- package/std/imm/list.yo +33 -0
- package/std/imm/map.yo +2 -1
- package/std/imm/set.yo +1 -0
- package/std/imm/sorted_map.yo +1 -0
- package/std/imm/sorted_set.yo +1 -0
- package/std/imm/string.yo +27 -23
- package/std/imm/vec.yo +18 -2
- package/std/io/reader.yo +2 -1
- package/std/io/writer.yo +3 -2
- package/std/libc/assert.yo +1 -0
- package/std/libc/ctype.yo +1 -0
- package/std/libc/dirent.yo +1 -0
- package/std/libc/errno.yo +1 -0
- package/std/libc/fcntl.yo +1 -0
- package/std/libc/float.yo +1 -0
- package/std/libc/limits.yo +1 -0
- package/std/libc/math.yo +1 -0
- package/std/libc/signal.yo +1 -0
- package/std/libc/stdatomic.yo +1 -0
- package/std/libc/stdint.yo +1 -0
- package/std/libc/stdio.yo +1 -0
- package/std/libc/stdlib.yo +1 -0
- package/std/libc/string.yo +1 -0
- package/std/libc/sys/stat.yo +1 -0
- package/std/libc/time.yo +1 -0
- package/std/libc/unistd.yo +1 -0
- package/std/libc/wctype.yo +1 -0
- package/std/libc/windows.yo +2 -0
- package/std/log.yo +7 -6
- package/std/net/addr.yo +5 -4
- package/std/net/dns.yo +7 -6
- package/std/net/errors.yo +8 -8
- package/std/net/tcp.yo +19 -18
- package/std/net/udp.yo +13 -12
- package/std/os/signal.yo +3 -3
- package/std/path.yo +1 -0
- package/std/prelude.yo +353 -182
- package/std/process/command.yo +40 -23
- package/std/process/index.yo +2 -1
- package/std/regex/compiler.yo +10 -9
- package/std/regex/index.yo +41 -41
- package/std/regex/match.yo +2 -2
- package/std/regex/parser.yo +21 -21
- package/std/regex/vm.yo +42 -41
- package/std/string/string.yo +95 -40
- package/std/string/string_builder.yo +9 -9
- package/std/string/unicode.yo +50 -49
- package/std/sync/channel.yo +2 -1
- package/std/sync/cond.yo +5 -4
- package/std/sync/mutex.yo +4 -3
- package/std/sys/advise.yo +1 -0
- package/std/sys/bufio/buf_reader.yo +17 -16
- package/std/sys/bufio/buf_writer.yo +10 -9
- package/std/sys/clock.yo +1 -0
- package/std/sys/copy.yo +1 -0
- package/std/sys/dir.yo +10 -9
- package/std/sys/dns.yo +6 -5
- package/std/sys/errors.yo +11 -11
- package/std/sys/events.yo +1 -0
- package/std/sys/externs.yo +38 -37
- package/std/sys/file.yo +17 -16
- package/std/sys/future.yo +4 -3
- package/std/sys/iov.yo +1 -0
- package/std/sys/mmap.yo +1 -0
- package/std/sys/path.yo +1 -0
- package/std/sys/perm.yo +2 -1
- package/std/sys/pipe.yo +1 -0
- package/std/sys/process.yo +5 -4
- package/std/sys/signal.yo +1 -0
- package/std/sys/socketpair.yo +1 -0
- package/std/sys/sockinfo.yo +1 -0
- package/std/sys/statfs.yo +2 -1
- package/std/sys/statx.yo +1 -0
- package/std/sys/sysinfo.yo +1 -0
- package/std/sys/tcp.yo +15 -14
- package/std/sys/temp.yo +1 -0
- package/std/sys/time.yo +2 -1
- package/std/sys/timer.yo +6 -6
- package/std/sys/tty.yo +2 -1
- package/std/sys/udp.yo +13 -12
- package/std/sys/unix.yo +12 -11
- package/std/testing/bench.yo +4 -3
- package/std/thread.yo +7 -6
- package/std/time/datetime.yo +18 -15
- package/std/time/duration.yo +11 -10
- package/std/time/instant.yo +4 -4
- package/std/time/sleep.yo +1 -0
- package/std/url/index.yo +3 -3
- package/std/worker.yo +4 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yo-async-effects
|
|
3
|
-
description: Write Yo async code and algebraic effect handlers. Use this when working with
|
|
3
|
+
description: Write Yo async code and algebraic effect handlers. Use this when working with Io, Future, JoinHandle, ctl/fn handlers, io.async, io.await, io.spawn, return, and unwind.
|
|
4
4
|
argument-hint: "[async task, effect, or API]"
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@ If a repository wraps these primitives, keep the same semantics and verify wheth
|
|
|
14
14
|
|
|
15
15
|
Use this skill when you need to:
|
|
16
16
|
|
|
17
|
-
- write functions that take an `io :
|
|
17
|
+
- write functions that take an `io : Io` parameter
|
|
18
18
|
- return or consume `Future(...)` values
|
|
19
19
|
- run tasks with `io.async`, `io.await`, or `io.spawn`
|
|
20
20
|
- define handlers using `ctl(args) -> R` and install them as plain local bindings
|
|
@@ -23,7 +23,7 @@ Use this skill when you need to:
|
|
|
23
23
|
## Workflow
|
|
24
24
|
|
|
25
25
|
1. Decide whether the task needs sequential async, concurrent async on one thread, or true parallelism.
|
|
26
|
-
2. Add the effect parameters (`io :
|
|
26
|
+
2. Add the effect parameters (`io : Io`, `raise : Raise`, …) to function signatures and call sites. There is no implicit injection — pass them explicitly.
|
|
27
27
|
3. Use the [async and effects recipes](./async-effects-recipes.md) for working patterns.
|
|
28
28
|
4. Re-check handler semantics before finalizing:
|
|
29
29
|
- `return(value)` resumes the continuation
|
|
@@ -16,8 +16,8 @@ These patterns cover normal Yo async code and algebraic effects.
|
|
|
16
16
|
```rust
|
|
17
17
|
{ yield } :: import("std/async");
|
|
18
18
|
|
|
19
|
-
pause_then_answer :: (fn(io :
|
|
20
|
-
io.async((io :
|
|
19
|
+
pause_then_answer :: (fn(io : Io) -> Impl(Future(i32, Io)))(
|
|
20
|
+
io.async((io : Io) => {
|
|
21
21
|
io.await(yield(), io);
|
|
22
22
|
i32(42)
|
|
23
23
|
})
|
|
@@ -25,7 +25,7 @@ pause_then_answer :: (fn(io : IO) -> Impl(Future(i32, IO)))(
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
- `io.async(...)` is lazy.
|
|
28
|
-
- The closure's parameter is the effect bundle. The simplest bundle is just `
|
|
28
|
+
- The closure's parameter is the effect bundle. The simplest bundle is just `Io`.
|
|
29
29
|
- The `Future(T, E)` return type names the same bundle type that the closure consumes.
|
|
30
30
|
|
|
31
31
|
## Sequential await
|
|
@@ -33,8 +33,8 @@ pause_then_answer :: (fn(io : IO) -> Impl(Future(i32, IO)))(
|
|
|
33
33
|
```rust
|
|
34
34
|
{ yield } :: import("std/async");
|
|
35
35
|
|
|
36
|
-
main :: (fn(io :
|
|
37
|
-
task := io.async((io :
|
|
36
|
+
main :: (fn(io : Io) -> unit)({
|
|
37
|
+
task := io.async((io : Io) => {
|
|
38
38
|
io.await(yield(), io);
|
|
39
39
|
i32(1)
|
|
40
40
|
});
|
|
@@ -51,12 +51,12 @@ export(main);
|
|
|
51
51
|
```rust
|
|
52
52
|
{ yield } :: import("std/async");
|
|
53
53
|
|
|
54
|
-
main :: (fn(io :
|
|
55
|
-
task1 := io.async((io :
|
|
54
|
+
main :: (fn(io : Io) -> unit)({
|
|
55
|
+
task1 := io.async((io : Io) => {
|
|
56
56
|
io.await(yield(), io);
|
|
57
57
|
i32(1)
|
|
58
58
|
});
|
|
59
|
-
task2 := io.async((io :
|
|
59
|
+
task2 := io.async((io : Io) => {
|
|
60
60
|
io.await(yield(), io);
|
|
61
61
|
i32(2)
|
|
62
62
|
});
|
|
@@ -128,7 +128,7 @@ bundle struct and pass that.
|
|
|
128
128
|
{ yield } :: import("std/async");
|
|
129
129
|
|
|
130
130
|
Raise :: (ctl(msg : String) -> i32);
|
|
131
|
-
TaskCtx :: struct(io :
|
|
131
|
+
TaskCtx :: struct(io : Io, raise : Raise);
|
|
132
132
|
|
|
133
133
|
work :: (fn(ctx : TaskCtx) -> Impl(Future(i32, TaskCtx)))(
|
|
134
134
|
io.async((ctx : TaskCtx) => {
|
|
@@ -153,7 +153,7 @@ work :: (fn(ctx : TaskCtx) -> Impl(Future(i32, TaskCtx)))(
|
|
|
153
153
|
```rust
|
|
154
154
|
{ read_dir, DirEntry } :: import("std/fs/dir");
|
|
155
155
|
|
|
156
|
-
WalkCtx :: struct(io :
|
|
156
|
+
WalkCtx :: struct(io : Io, exn : Exception);
|
|
157
157
|
|
|
158
158
|
process_dir :: (fn(root: Path, ctx : WalkCtx) -> Impl(Future(unit, WalkCtx)))(
|
|
159
159
|
io.async((ctx : WalkCtx) => {
|
|
@@ -190,9 +190,17 @@ process_dir :: (fn(root: Path, ctx : WalkCtx) -> Impl(Future(unit, WalkCtx)))(
|
|
|
190
190
|
(match arm, `cond` branch, `begin` block, plain `fn` body), use `return`.
|
|
191
191
|
- `unwind` inside an async task aborts the future instead of completing it normally.
|
|
192
192
|
- `io.await(...)` on an already-aborted future can panic; `JoinHandle.await(...)` converts abort into `.None`.
|
|
193
|
-
- Closures cannot be `ctl`, and they cannot capture a `ctl`-typed value. Handlers are bare (non-capturing) anonymous functions.
|
|
193
|
+
- Closures cannot be `ctl`, and they cannot capture a `ctl`-typed value. Handlers are bare (non-capturing) anonymous functions. If you need to use a `ctl` handler from inside a closure body, pass it in as an explicit parameter instead of capturing it.
|
|
194
194
|
- Pointers and references to `ctl` types (or structs containing them) are rejected.
|
|
195
195
|
- **`recur` inside `io.async` calls the lambda, not the outer function** — use an iterative worklist for async recursion.
|
|
196
|
+
- **Closure params bound to a generic `E : Type.Struct` need an explicit annotation.** When a generic function takes `callback : Impl(Fn(v : V, e : E) -> R)` and you pass a closure, the closure's `e` parameter type cannot be inferred from the call's bundle argument — `E` is still unbound at the closure body's evaluation site. Define a concrete struct first and annotate the closure parameter:
|
|
197
|
+
```rust
|
|
198
|
+
// ✗ fails with "Cannot infer the type of anonymous closure parameter `e`"
|
|
199
|
+
traverse(arr, (v, e) => { e.log(v); ... }, { yield, log });
|
|
200
|
+
// ✓ annotate via a named bundle struct
|
|
201
|
+
Eff :: struct(yield : Yield, log : Log);
|
|
202
|
+
traverse(arr, (v : i32, e : Eff) => { e.log(v); ... }, Eff(yield, log));
|
|
203
|
+
```
|
|
196
204
|
|
|
197
205
|
## Exception (non-resumable)
|
|
198
206
|
|
|
@@ -105,6 +105,12 @@ match(numbers.get(usize(0)),
|
|
|
105
105
|
.None => ()
|
|
106
106
|
);
|
|
107
107
|
|
|
108
|
+
// Do NOT write the verbose forms when X(i) works:
|
|
109
|
+
// ✗ (&(numbers)).index(usize(0)).* = i32(99); // needs pragma, scans as raw-ptr code
|
|
110
|
+
// ✗ v := numbers.get(usize(0)).unwrap(); // panic-on-OOB anyway — just use call syntax
|
|
111
|
+
// ✓ numbers(usize(0)) = i32(99);
|
|
112
|
+
// ✓ v := numbers(usize(0));
|
|
113
|
+
|
|
108
114
|
counts := HashMap(String, i32).new();
|
|
109
115
|
counts.set(`yo`, i32(1));
|
|
110
116
|
```
|
|
@@ -123,7 +129,7 @@ counts.set(`yo`, i32(1));
|
|
|
123
129
|
```rust
|
|
124
130
|
Iterator :: trait(
|
|
125
131
|
Item : Type,
|
|
126
|
-
next : (fn(self :
|
|
132
|
+
next : (fn(ref(self) : Self) -> Option(Self.Item))
|
|
127
133
|
);
|
|
128
134
|
```
|
|
129
135
|
|
|
@@ -193,6 +199,15 @@ TcpStream :: object(fd : i32, buffer : ArrayList(u8));
|
|
|
193
199
|
|
|
194
200
|
- Use `newtype(...)` when the type has exactly one field
|
|
195
201
|
- Use `object(...)` for types that need shared ownership
|
|
202
|
+
- **Parameter form by type kind:**
|
|
203
|
+
- `object(...)`: plain `name : Type` (reference semantics — no pointer or ref needed).
|
|
204
|
+
`foo :: (fn(ctx : EvalContext) -> unit)(ctx.do_stuff());`
|
|
205
|
+
- `struct(...)` / `enum(...)` / primitive, read-only: plain `name : Type`.
|
|
206
|
+
- `struct(...)` / `enum(...)` / primitive, need mutation: `ref(name) : Type`.
|
|
207
|
+
`swap :: (fn(ref(a) : i32, ref(b) : i32) -> unit)(...);`
|
|
208
|
+
- Method receiver on `object`: plain `self : Self`.
|
|
209
|
+
- Method receiver on value type (traits + inherent mutators): `ref(self) : Self`.
|
|
210
|
+
- Raw FFI pointer: `name : *(T)` (requires `pragma(Pragma.AllowUnsafe);`).
|
|
196
211
|
- Source-file imports are namespace structs. The old `module(...)`, `Module`,
|
|
197
212
|
and `SelfModule` syntax is gone; use `struct(...)`, `Type`, and normal `Self`.
|
|
198
213
|
- Fields written as `name :: value` or `comptime(name) : Type` are compile-time-only static fields/methods and have no runtime layout
|
|
@@ -361,8 +376,8 @@ safe_div :: (fn(a : i32, b : i32) -> Result(i32, DivError))(
|
|
|
361
376
|
result := inc(i32(5));
|
|
362
377
|
|
|
363
378
|
transform :: (fn(values : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
|
|
364
|
-
for(values
|
|
365
|
-
|
|
379
|
+
for(values, ref(x) => {
|
|
380
|
+
x = f(x);
|
|
366
381
|
});
|
|
367
382
|
});
|
|
368
383
|
```
|
|
@@ -381,22 +396,27 @@ list := ArrayList(i32).new();
|
|
|
381
396
|
list.push(i32(1));
|
|
382
397
|
list.push(i32(2));
|
|
383
398
|
|
|
384
|
-
|
|
385
|
-
|
|
399
|
+
// Value form — implicit .into_iter().
|
|
400
|
+
for(list, (value) => {
|
|
401
|
+
println(value);
|
|
386
402
|
});
|
|
387
403
|
|
|
388
|
-
|
|
389
|
-
|
|
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));
|
|
390
408
|
});
|
|
391
409
|
```
|
|
392
410
|
|
|
393
|
-
|
|
|
394
|
-
|
|
|
395
|
-
|
|
|
396
|
-
|
|
|
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 |
|
|
397
416
|
|
|
398
|
-
-
|
|
399
|
-
-
|
|
417
|
+
- `Iterator` trait — defines `next() -> Option(Item)`. Custom iterables impl this.
|
|
418
|
+
- `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.
|
|
400
420
|
|
|
401
421
|
## Module-level mutable variables
|
|
402
422
|
|
|
@@ -146,7 +146,7 @@ test("Async test", {
|
|
|
146
146
|
});
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
- `test("name", { body })` defines a test — `io :
|
|
149
|
+
- `test("name", { body })` defines a test — `io : Io` is automatically available
|
|
150
150
|
- All tests can use `io.async(...)`, `io.await(...)`, etc. without a `using` clause
|
|
151
151
|
- `assert(condition, "message")` — always include a message string
|
|
152
152
|
- `comptime_assert(expr)` — verified at compile time
|
|
@@ -127,6 +127,16 @@ masked := ((A | B) | C);
|
|
|
127
127
|
- Dynamic field access with unquote must keep grouping after the dot: `value.(#(field_expr))`, not `value.#(field_expr)`.
|
|
128
128
|
- Unquote splicing is the tight operator `...#(exprs)`; do not insert a space between `...` and `#`.
|
|
129
129
|
- Canonical pointer dereference is `ptr.*`; formatter should canonicalize legacy `ptr.(*)` to `ptr.*`.
|
|
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
|
+
- **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
|
+
- **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.
|
|
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
|
+
- **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
|
+
- **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
|
+
- **`// 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
|
+
- **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).
|
|
130
140
|
- Keep single-line array and tuple literals compact during formatting: `[1, 2, 3]`, `(1, 2, 3)`.
|
|
131
141
|
- Parenthesize other unary operands too: `!(ready)`, `-(value)`
|
|
132
142
|
- **`!x && y` is parsed as `!(x && y)`**, not `(!x) && y`. Prefix `!` greedily consumes the full right-hand expression. To get `(!x) && y`, write `((!x) && y)` with explicit inner parens.
|
|
@@ -216,8 +226,8 @@ caller :: (fn() -> i32)({
|
|
|
216
226
|
result := closure(i32(5));
|
|
217
227
|
|
|
218
228
|
transform :: (fn(list : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
|
|
219
|
-
for(list
|
|
220
|
-
|
|
229
|
+
for(list, ref(x) => {
|
|
230
|
+
x = f(x);
|
|
221
231
|
});
|
|
222
232
|
});
|
|
223
233
|
```
|
|
@@ -371,25 +381,29 @@ while(comptime((i < 10)), {
|
|
|
371
381
|
// body evaluated/unrolled at compile time
|
|
372
382
|
});
|
|
373
383
|
|
|
374
|
-
// for loop — 2-arg prelude macro
|
|
375
|
-
|
|
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()
|
|
376
388
|
process(x);
|
|
377
389
|
});
|
|
378
390
|
|
|
379
|
-
for(
|
|
380
|
-
|
|
391
|
+
for(list, ref(x) => { // borrow form: iter() + project(pos)
|
|
392
|
+
x = transform(x); // writes propagate back into list
|
|
381
393
|
});
|
|
382
394
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
395
|
+
// 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));
|
|
386
398
|
```
|
|
387
399
|
|
|
388
400
|
- Use `recur(...)` for self-recursion
|
|
389
401
|
- `while(cond, body)` is **always a runtime loop** — use this for open-ended loops (e.g., server accept loops, event loops)
|
|
390
402
|
- `while(comptime(cond), body)` explicitly unrolls at compile time — `cond` must be a compile-time-known value
|
|
391
403
|
- Using a comptime-only (`::`) variable in a bare `while` condition without `comptime()` is a **compile error** (would be an infinite loop at runtime)
|
|
392
|
-
- **`for(
|
|
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()`.
|
|
393
407
|
- **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`)
|
|
394
408
|
|
|
395
409
|
## Return and branch safety
|
|
@@ -452,18 +466,22 @@ list := ArrayList(i32).new();
|
|
|
452
466
|
list.push(i32(10));
|
|
453
467
|
list.push(i32(20));
|
|
454
468
|
|
|
455
|
-
|
|
456
|
-
|
|
469
|
+
// Value form — implicit .into_iter().
|
|
470
|
+
for(list, (value) => {
|
|
471
|
+
println(value);
|
|
457
472
|
});
|
|
458
473
|
|
|
459
|
-
|
|
460
|
-
|
|
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));
|
|
461
478
|
});
|
|
462
479
|
```
|
|
463
480
|
|
|
464
|
-
- `for(
|
|
465
|
-
-
|
|
466
|
-
-
|
|
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.
|
|
467
485
|
|
|
468
486
|
## Testing
|
|
469
487
|
|
|
@@ -482,7 +500,7 @@ test("Async test", {
|
|
|
482
500
|
});
|
|
483
501
|
```
|
|
484
502
|
|
|
485
|
-
- `test("description", { body })` defines a test — `io :
|
|
503
|
+
- `test("description", { body })` defines a test — `io : Io` is automatically available
|
|
486
504
|
- All tests can use `io.async(...)`, `io.await(...)`, etc. without a `using` clause
|
|
487
505
|
- `assert(condition, "message")` — runtime assertion (always include a message)
|
|
488
506
|
- `comptime_assert(condition)` — compile-time assertion
|
|
@@ -604,11 +622,11 @@ list := ArrayList(i32).new();
|
|
|
604
622
|
list.push(i32(42));
|
|
605
623
|
|
|
606
624
|
val := list(usize(0)); // → i32 (value copy via Index trait)
|
|
607
|
-
list(usize(0)) = i32(99);
|
|
625
|
+
list(usize(0)) = i32(99); // mutate in place directly
|
|
608
626
|
|
|
609
627
|
// When you need the pointer explicitly:
|
|
610
|
-
ptr := &(list(usize(0)));
|
|
611
|
-
ptr.* = i32(99);
|
|
628
|
+
ptr := &(list(usize(0))); // → *(i32)
|
|
629
|
+
ptr.* = i32(99); // also works
|
|
612
630
|
|
|
613
631
|
// Safe access (returns Option(T)):
|
|
614
632
|
match(list.get(usize(0)),
|
|
@@ -622,6 +640,26 @@ match(list.get(usize(0)),
|
|
|
622
640
|
- `&(list(i))` returns `*(T)` if you need the pointer explicitly
|
|
623
641
|
- `list.get(i)` returns `Option(T)` for safe bounds-checked access
|
|
624
642
|
|
|
643
|
+
**Don't write `(&(X)).index(i).*` or `X.get(i).unwrap()` when you mean
|
|
644
|
+
`X(i)`.** Use the call-syntax form everywhere it works:
|
|
645
|
+
|
|
646
|
+
```rust
|
|
647
|
+
// ✗ Verbose, scans like raw-pointer code (and requires the file's
|
|
648
|
+
// pragma(Pragma.AllowUnsafe); because `.*` is gated):
|
|
649
|
+
(&(self.field)).index(i).* = value;
|
|
650
|
+
elem := (&(self.field)).index(i).*;
|
|
651
|
+
v := list.get(usize(0)).unwrap();
|
|
652
|
+
|
|
653
|
+
// ✓ Same semantics, no `.*`, no pragma needed:
|
|
654
|
+
self.field(i) = value;
|
|
655
|
+
elem := self.field(i);
|
|
656
|
+
v := list(usize(0));
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
Both forms call the same `Index` trait method. The call-syntax form
|
|
660
|
+
is shorter, doesn't need raw-pointer plumbing in user code, and
|
|
661
|
+
panics on out-of-bounds identically to `.unwrap()` on `.get(...)`.
|
|
662
|
+
|
|
625
663
|
### Named fields required for `struct`/`object` constructors
|
|
626
664
|
|
|
627
665
|
```rust
|
package/README.md
CHANGED
|
@@ -55,7 +55,8 @@ Below is a non-exhaustive list of features that Yo supports:
|
|
|
55
55
|
- Homoiconicity and metaprogramming (**Yo** syntax is inspired by the **Lisp** S expression. Simple syntax rule, Human & AI friendly).
|
|
56
56
|
- Closure.
|
|
57
57
|
- [Algebraic Effects and Handlers](./docs/en-US/ALGEBRAIC_EFFECTS.md) (One-shot delimited continuation. Tail-Resumptive. Implicit parameters via `using`/`given`, effect handlers with `return`/`escape`, by [Evidence Passing](https://xnning.github.io/papers/multip.pdf)).
|
|
58
|
-
- [Async/await](./docs/en-US/ASYNC_AWAIT.md) (Builtin `
|
|
58
|
+
- [Async/await](./docs/en-US/ASYNC_AWAIT.md) (Builtin `Io` effect. Stackless coroutine & Cooperative multi-tasking. Lazy Futures, multi-await, single-threaded concurrency via state machine transformation).
|
|
59
|
+
- [Memory safety by default](./docs/en-US/MEMORY_SAFETY.md) — user code can't write UB (no raw pointers, no FFI, no inline assembly) without an explicit `pragma(Pragma.AllowUnsafe);` opt-in. `ref(name)` for in-place mutation; `yo unsafe-report` for auditing the unsafe surface.
|
|
59
60
|
- `object` type with [Non-atomic Reference Counting and Thread-Local Cycle Collection](./docs/en-US/CYCLE_COLLECTION.md).
|
|
60
61
|
- [Compile-time Reference Counting with Ownership and Lifetime Analysis](./docs/en-US/COMPILE_TIME_RC_WITH_OWNERSHIP_ANALYSIS.md).
|
|
61
62
|
- Thread-per-core parallelism model (see [PARALLELISM.md](./docs/en-US/PARALLELISM.md)).
|
|
@@ -234,7 +235,7 @@ Every Yo file automatically imports **[std/prelude.yo](./std/prelude.yo)**, whic
|
|
|
234
235
|
- **C-compatible types**: `int`, `uint`, `short`, `long`, `longlong`, `char`, etc.
|
|
235
236
|
- **Core traits**: `Eq`, `Ord`, `Add`, `Sub`, `Mul`, `Div`, `Iterator`, `IntoIterator`, `TryFrom`, `TryInto`, `Dispose`, `Send`, `Rc`, `Acyclic`, etc.
|
|
236
237
|
- **Metaprogramming**: `Type`, `Expr`, `ExprList`, `Var`
|
|
237
|
-
- **Async**: `
|
|
238
|
+
- **Async**: `Io`, `FutureState`, `JoinHandle`
|
|
238
239
|
- **Utilities**: `assert`, `unsafe`, `try`, `for`, `not`, `arc`, `Box`, `box`
|
|
239
240
|
- etc.
|
|
240
241
|
|
|
@@ -378,7 +379,7 @@ This repository ships a set of **agent skill files** that teach AI agents how to
|
|
|
378
379
|
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
379
380
|
| [`yo-syntax`](.github/skills/yo-syntax/SKILL.md) | Core language syntax: curly braces, cond/match, structs, enums, operators, modules |
|
|
380
381
|
| [`yo-core-patterns`](.github/skills/yo-core-patterns/SKILL.md) | Everyday patterns: types, generics, traits, error handling, collections, iterators |
|
|
381
|
-
| [`yo-async-effects`](.github/skills/yo-async-effects/SKILL.md) | Async/await, algebraic effects, Exception,
|
|
382
|
+
| [`yo-async-effects`](.github/skills/yo-async-effects/SKILL.md) | Async/await, algebraic effects, Exception, Io, spawning tasks |
|
|
382
383
|
| [`yo-project-workflow`](.github/skills/yo-project-workflow/SKILL.md) | `yo` CLI commands, `build.yo` project files, dependency management |
|
|
383
384
|
|
|
384
385
|
### Using in your own project
|