@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
package/package.json
CHANGED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Re-add `pragma(Pragma.AllowUnsafe);` to files that mention raw
|
|
4
|
+
* pointer types in their declarations (e.g. `*(u8)` in a parameter,
|
|
5
|
+
* field, or return slot, or `&(x)` to take an address).
|
|
6
|
+
*
|
|
7
|
+
* Phase C tightened the structural gate so that `*(T)` and `&(...)`
|
|
8
|
+
* are themselves rejected in safe code — not just `unsafe(...)`-
|
|
9
|
+
* wrapped ops like `.* ` and `&+`. The previous trim pass
|
|
10
|
+
* (scripts/trim-pragma.ts) removed the pragma from files that used
|
|
11
|
+
* only declarations, which now need it back.
|
|
12
|
+
*
|
|
13
|
+
* Detection: file contains `*(<word>` or `&(<word>` outside string
|
|
14
|
+
* literals / comments. The heuristic is loose on purpose — better
|
|
15
|
+
* to over-add the pragma than to leave a file failing to compile.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* bun scripts/add-pragma-for-pointer-decls.ts <root-dir>
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readFileSync, writeFileSync, statSync, readdirSync } from "fs";
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
import { argv, exit } from "process";
|
|
24
|
+
|
|
25
|
+
const PRAGMA_LINE = "pragma(Pragma.AllowUnsafe);";
|
|
26
|
+
|
|
27
|
+
function stripCommentsAndStrings(src: string): string {
|
|
28
|
+
const out: string[] = [];
|
|
29
|
+
let inStr: string | null = null;
|
|
30
|
+
let i = 0;
|
|
31
|
+
while (i < src.length) {
|
|
32
|
+
const ch = src[i]!;
|
|
33
|
+
if (!inStr && ch === "/" && src[i + 1] === "/") {
|
|
34
|
+
while (i < src.length && src[i] !== "\n") i++;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (inStr) {
|
|
38
|
+
if (ch === "\\") {
|
|
39
|
+
out.push(" ", " ");
|
|
40
|
+
i += 2;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (ch === inStr) {
|
|
44
|
+
inStr = null;
|
|
45
|
+
out.push(" ");
|
|
46
|
+
i++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
out.push(" ");
|
|
50
|
+
i++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (ch === '"' || ch === "`") {
|
|
54
|
+
inStr = ch;
|
|
55
|
+
out.push(" ");
|
|
56
|
+
i++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
out.push(ch);
|
|
60
|
+
i++;
|
|
61
|
+
}
|
|
62
|
+
return out.join("");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const POINTER_TYPE_RE = /\*\(/;
|
|
66
|
+
const ADDRESS_OF_RE = /(?:^|[^A-Za-z0-9_])&\(/;
|
|
67
|
+
|
|
68
|
+
function fileNeedsPragma(src: string): boolean {
|
|
69
|
+
const cleaned = stripCommentsAndStrings(src);
|
|
70
|
+
return POINTER_TYPE_RE.test(cleaned) || ADDRESS_OF_RE.test(cleaned);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function fileHasPragma(src: string): boolean {
|
|
74
|
+
return src.includes("pragma(Pragma.AllowUnsafe)");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Insert the pragma after any leading `//!` module-doc lines and
|
|
79
|
+
* before the first code declaration. Empty files get the pragma at
|
|
80
|
+
* the top.
|
|
81
|
+
*/
|
|
82
|
+
function insertPragma(src: string): string {
|
|
83
|
+
const lines = src.split("\n");
|
|
84
|
+
let insertAt = 0;
|
|
85
|
+
// Skip leading shebang / module-doc / blank / line-comment.
|
|
86
|
+
while (insertAt < lines.length) {
|
|
87
|
+
const trimmed = lines[insertAt]!.trim();
|
|
88
|
+
if (
|
|
89
|
+
trimmed === "" ||
|
|
90
|
+
trimmed.startsWith("#!") ||
|
|
91
|
+
trimmed.startsWith("//!") ||
|
|
92
|
+
trimmed.startsWith("//")
|
|
93
|
+
) {
|
|
94
|
+
insertAt++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
lines.splice(insertAt, 0, PRAGMA_LINE);
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function walk(dir: string, out: string[]): void {
|
|
104
|
+
for (const entry of readdirSync(dir)) {
|
|
105
|
+
const p = join(dir, entry);
|
|
106
|
+
const s = statSync(p);
|
|
107
|
+
if (s.isDirectory()) {
|
|
108
|
+
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
109
|
+
walk(p, out);
|
|
110
|
+
} else if (s.isFile() && p.endsWith(".yo")) {
|
|
111
|
+
out.push(p);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const root = argv[2];
|
|
117
|
+
if (!root) {
|
|
118
|
+
console.error(`usage: bun ${argv[1]} <root-dir>`);
|
|
119
|
+
exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const files: string[] = [];
|
|
123
|
+
walk(root, files);
|
|
124
|
+
|
|
125
|
+
let added = 0;
|
|
126
|
+
for (const file of files) {
|
|
127
|
+
const src = readFileSync(file, "utf8");
|
|
128
|
+
if (fileHasPragma(src)) continue;
|
|
129
|
+
if (!fileNeedsPragma(src)) continue;
|
|
130
|
+
writeFileSync(file, insertPragma(src));
|
|
131
|
+
added++;
|
|
132
|
+
console.log(`added pragma: ${file}`);
|
|
133
|
+
}
|
|
134
|
+
console.log(`---\nadded pragma to ${added} file(s).`);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Add `pragma(Pragma.AllowUnsafe);` at the top of every .yo file
|
|
4
|
+
* passed on the command line, unless it already has one.
|
|
5
|
+
*
|
|
6
|
+
* Phase C of plans/MEMORY_SAFETY.md: explicit per-file pragma replaces
|
|
7
|
+
* the path-based MVP heuristic. Run this once over `std/`, `yo-self/`,
|
|
8
|
+
* and `tests/` during the migration.
|
|
9
|
+
*
|
|
10
|
+
* Heuristics:
|
|
11
|
+
* - Skip the prelude itself (it defines `Pragma`; the pragma in the
|
|
12
|
+
* prelude is placed by hand, mid-file, after `Pragma :: enum(...)`).
|
|
13
|
+
* - Skip files that already contain `pragma(Pragma.AllowUnsafe);`.
|
|
14
|
+
* - Insert after any leading `//` line comments at the top of the file
|
|
15
|
+
* so the docstring stays visually at the top.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync, writeFileSync, statSync } from "fs";
|
|
19
|
+
import { argv } from "process";
|
|
20
|
+
|
|
21
|
+
const PRAGMA_LINE = "pragma(Pragma.AllowUnsafe);";
|
|
22
|
+
|
|
23
|
+
function processFile(file: string): "added" | "already" | "skip" {
|
|
24
|
+
const src = readFileSync(file, "utf8");
|
|
25
|
+
if (file.endsWith("/std/prelude.yo")) return "skip";
|
|
26
|
+
if (src.includes(PRAGMA_LINE)) return "already";
|
|
27
|
+
|
|
28
|
+
const lines = src.split("\n");
|
|
29
|
+
// Find first non-comment, non-blank line; insert pragma above it.
|
|
30
|
+
let insertAt = 0;
|
|
31
|
+
while (insertAt < lines.length) {
|
|
32
|
+
const ln = lines[insertAt]!.trim();
|
|
33
|
+
if (ln === "" || ln.startsWith("//") || ln.startsWith("/*")) {
|
|
34
|
+
insertAt++;
|
|
35
|
+
} else {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
lines.splice(insertAt, 0, PRAGMA_LINE);
|
|
40
|
+
writeFileSync(file, lines.join("\n"));
|
|
41
|
+
return "added";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let added = 0;
|
|
45
|
+
let already = 0;
|
|
46
|
+
let skip = 0;
|
|
47
|
+
for (const file of argv.slice(2)) {
|
|
48
|
+
try {
|
|
49
|
+
if (!statSync(file).isFile()) continue;
|
|
50
|
+
} catch {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const r = processFile(file);
|
|
54
|
+
if (r === "added") added++;
|
|
55
|
+
else if (r === "already") already++;
|
|
56
|
+
else skip++;
|
|
57
|
+
}
|
|
58
|
+
console.log(`added: ${added}, already: ${already}, skipped: ${skip}`);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite `(&(X)).METHOD(...)` → `X.METHOD(...)` for methods that
|
|
3
|
+
* have been migrated from `(self : *(Self))` to `(inout(self) :
|
|
4
|
+
* Self)`. Inout method dispatch auto-wraps the receiver with
|
|
5
|
+
* `&(...)`, so the explicit `&(X)` at the call site now produces
|
|
6
|
+
* `&(&(X))` = `*(*(T))`, which doesn't match the expected `*(T)`.
|
|
7
|
+
*
|
|
8
|
+
* The script takes a method-name allow-list (passed via env var
|
|
9
|
+
* `METHODS` or a hardcoded default list). Receivers `X` can be any
|
|
10
|
+
* balanced expression; we don't touch the receiver, only strip the
|
|
11
|
+
* `&(...)` wrap.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* bun run scripts/migrate-amp-method-calls.ts # dry-run
|
|
15
|
+
* bun run scripts/migrate-amp-method-calls.ts --write # apply
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
|
|
21
|
+
const ROOTS = ["std", "yo-self", "tests"];
|
|
22
|
+
|
|
23
|
+
// Methods migrated to inout(self) : Self in this session, plus the
|
|
24
|
+
// emitter/Clone/Hash/ToString migrations from earlier. If a method
|
|
25
|
+
// here was actually NOT migrated, no harm — dispatch only changes
|
|
26
|
+
// for the migrated ones, and the rewrite stays semantically
|
|
27
|
+
// correct (`X.method()` already worked for `*(Self)`-style methods
|
|
28
|
+
// via Yo's auto-deref).
|
|
29
|
+
const METHODS = [
|
|
30
|
+
// Array / Slice
|
|
31
|
+
"iter",
|
|
32
|
+
"len",
|
|
33
|
+
// String mutators
|
|
34
|
+
"push_str",
|
|
35
|
+
"push_string",
|
|
36
|
+
"push_byte",
|
|
37
|
+
"reserve",
|
|
38
|
+
"clear",
|
|
39
|
+
// StringBuilder
|
|
40
|
+
"is_empty",
|
|
41
|
+
"write_str",
|
|
42
|
+
"write_string",
|
|
43
|
+
"write_byte",
|
|
44
|
+
"write_rune",
|
|
45
|
+
"write_line",
|
|
46
|
+
// Time
|
|
47
|
+
"as_secs",
|
|
48
|
+
"as_millis",
|
|
49
|
+
"as_micros",
|
|
50
|
+
"as_nanos",
|
|
51
|
+
"to_string",
|
|
52
|
+
"duration_since",
|
|
53
|
+
"elapsed",
|
|
54
|
+
// Sync / process / thread
|
|
55
|
+
"lock",
|
|
56
|
+
"unlock",
|
|
57
|
+
"signal",
|
|
58
|
+
"broadcast",
|
|
59
|
+
"wait",
|
|
60
|
+
"spawn",
|
|
61
|
+
"join",
|
|
62
|
+
// Collections inherent (the few that got migrated)
|
|
63
|
+
"trim_to_fit",
|
|
64
|
+
// Existing migrated (Hash, Clone, ToString) — safe no-op if not
|
|
65
|
+
// explicitly wrapped at the call site
|
|
66
|
+
"hash",
|
|
67
|
+
"clone",
|
|
68
|
+
// Iterator
|
|
69
|
+
"next",
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
function matchAmpersandWrap(
|
|
73
|
+
content: string,
|
|
74
|
+
start: number
|
|
75
|
+
): { x: string; end: number } | null {
|
|
76
|
+
if (content.slice(start, start + 3) !== "(&(") return null;
|
|
77
|
+
let depth = 1;
|
|
78
|
+
let i = start + 3;
|
|
79
|
+
const xStart = i;
|
|
80
|
+
while (i < content.length) {
|
|
81
|
+
const ch = content[i]!;
|
|
82
|
+
if (ch === "(") depth++;
|
|
83
|
+
else if (ch === ")") {
|
|
84
|
+
depth--;
|
|
85
|
+
if (depth === 0) break;
|
|
86
|
+
}
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
if (i >= content.length) return null;
|
|
90
|
+
const x = content.slice(xStart, i);
|
|
91
|
+
if (content[i + 1] !== ")") return null;
|
|
92
|
+
return { x, end: i + 2 };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function migrate(
|
|
96
|
+
content: string,
|
|
97
|
+
methodSet: Set<string>
|
|
98
|
+
): { result: string; rewrites: number } {
|
|
99
|
+
const out: string[] = [];
|
|
100
|
+
let i = 0;
|
|
101
|
+
let rewrites = 0;
|
|
102
|
+
while (i < content.length) {
|
|
103
|
+
if (content.slice(i, i + 3) !== "(&(") {
|
|
104
|
+
out.push(content[i]!);
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const wrap = matchAmpersandWrap(content, i);
|
|
109
|
+
if (!wrap) {
|
|
110
|
+
out.push(content[i]!);
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// After `(&(X))`, look for `.METHOD(`.
|
|
115
|
+
if (content[wrap.end] !== ".") {
|
|
116
|
+
out.push(content[i]!);
|
|
117
|
+
i++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// Extract method name.
|
|
121
|
+
const methodMatch = content
|
|
122
|
+
.slice(wrap.end + 1)
|
|
123
|
+
.match(/^([A-Za-z_][A-Za-z0-9_]*)\(/);
|
|
124
|
+
if (!methodMatch) {
|
|
125
|
+
out.push(content[i]!);
|
|
126
|
+
i++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const method = methodMatch[1]!;
|
|
130
|
+
if (!methodSet.has(method)) {
|
|
131
|
+
out.push(content[i]!);
|
|
132
|
+
i++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// Rewrite: `(&(X))` → `X`. Keep `.method(` as-is.
|
|
136
|
+
out.push(wrap.x);
|
|
137
|
+
i = wrap.end;
|
|
138
|
+
rewrites++;
|
|
139
|
+
}
|
|
140
|
+
return { result: out.join(""), rewrites };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function* walk(dir: string): Generator<string> {
|
|
144
|
+
for (const entry of readdirSync(dir)) {
|
|
145
|
+
if (entry.startsWith(".")) continue;
|
|
146
|
+
const fullPath = path.join(dir, entry);
|
|
147
|
+
const st = statSync(fullPath);
|
|
148
|
+
if (st.isDirectory()) {
|
|
149
|
+
yield* walk(fullPath);
|
|
150
|
+
} else if (st.isFile() && entry.endsWith(".yo")) {
|
|
151
|
+
yield fullPath;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function main(): void {
|
|
157
|
+
const write = process.argv.includes("--write");
|
|
158
|
+
const methodsFromEnv = process.env.METHODS;
|
|
159
|
+
const methodList = methodsFromEnv ? methodsFromEnv.split(",") : METHODS;
|
|
160
|
+
const methodSet = new Set(methodList);
|
|
161
|
+
let total = 0;
|
|
162
|
+
let filesTouched = 0;
|
|
163
|
+
for (const root of ROOTS) {
|
|
164
|
+
const absRoot = path.resolve(root);
|
|
165
|
+
for (const filePath of walk(absRoot)) {
|
|
166
|
+
const content = readFileSync(filePath, "utf-8");
|
|
167
|
+
const { result, rewrites } = migrate(content, methodSet);
|
|
168
|
+
if (result === content) continue;
|
|
169
|
+
filesTouched++;
|
|
170
|
+
total += rewrites;
|
|
171
|
+
console.log(
|
|
172
|
+
`${write ? "migrated" : "would migrate"}: ${path.relative(
|
|
173
|
+
process.cwd(),
|
|
174
|
+
filePath
|
|
175
|
+
)} (${rewrites})`
|
|
176
|
+
);
|
|
177
|
+
if (write) writeFileSync(filePath, result, "utf-8");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
console.log(
|
|
181
|
+
`\n${write ? "Migrated" : "Would migrate"} ${total} call sites across ${filesTouched} file(s).`
|
|
182
|
+
);
|
|
183
|
+
if (!write) console.log("(dry-run — pass --write to apply)");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Migrate `(&(x)).clone()` to `x.clone()` in the given files.
|
|
4
|
+
*
|
|
5
|
+
* Phase D of plans/MEMORY_SAFETY.md: Clone trait now takes
|
|
6
|
+
* `inout(self) : Self` instead of `(self : *(Self))`, so the old
|
|
7
|
+
* pattern `(&(x)).clone()` (passing a `*(T)` to a `*(Self)` receiver)
|
|
8
|
+
* no longer matches. The caller can just write `x.clone()` and the
|
|
9
|
+
* compiler auto-wraps for the inout calling convention.
|
|
10
|
+
*
|
|
11
|
+
* Match: `(&(EXPR)).clone()` — handles balanced parens inside EXPR.
|
|
12
|
+
* Replace with `EXPR.clone()`. Conservative: only single-line matches.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
16
|
+
import { argv } from "process";
|
|
17
|
+
|
|
18
|
+
function matchedClose(s: string, openPos: number): number {
|
|
19
|
+
// openPos is the position of `(`. Return position of matching `)`.
|
|
20
|
+
let depth = 1;
|
|
21
|
+
let i = openPos + 1;
|
|
22
|
+
let inStr: string | null = null;
|
|
23
|
+
while (i < s.length) {
|
|
24
|
+
const ch = s[i]!;
|
|
25
|
+
if (inStr) {
|
|
26
|
+
if (ch === "\\") {
|
|
27
|
+
i += 2;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (ch === inStr) inStr = null;
|
|
31
|
+
i++;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (ch === '"' || ch === "`") {
|
|
35
|
+
inStr = ch;
|
|
36
|
+
i++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (ch === "(") depth++;
|
|
40
|
+
else if (ch === ")") {
|
|
41
|
+
depth--;
|
|
42
|
+
if (depth === 0) return i;
|
|
43
|
+
}
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
return -1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function migrate(src: string): string {
|
|
50
|
+
let out = "";
|
|
51
|
+
let i = 0;
|
|
52
|
+
while (i < src.length) {
|
|
53
|
+
// Look for `(&(` start.
|
|
54
|
+
if (src.slice(i, i + 3) === "(&(") {
|
|
55
|
+
const inner_open = i + 2; // position of inner `(`
|
|
56
|
+
const inner_close = matchedClose(src, inner_open);
|
|
57
|
+
if (inner_close < 0) {
|
|
58
|
+
out += src[i]!;
|
|
59
|
+
i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const outer_close = matchedClose(src, i);
|
|
63
|
+
if (outer_close < 0 || outer_close !== inner_close + 1) {
|
|
64
|
+
out += src[i]!;
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Check that the trailing chars are `.clone()`.
|
|
69
|
+
const trailing = src.slice(outer_close + 1, outer_close + 9);
|
|
70
|
+
if (trailing === ".clone()") {
|
|
71
|
+
const inner = src.slice(inner_open + 1, inner_close);
|
|
72
|
+
out += `${inner}.clone()`;
|
|
73
|
+
i = outer_close + 9;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
out += src[i]!;
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let changed = 0;
|
|
84
|
+
for (const file of argv.slice(2)) {
|
|
85
|
+
const src = readFileSync(file, "utf8");
|
|
86
|
+
const next = migrate(src);
|
|
87
|
+
if (next !== src) {
|
|
88
|
+
writeFileSync(file, next);
|
|
89
|
+
changed++;
|
|
90
|
+
console.log(`migrated: ${file}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
console.log(`done: ${changed} file(s) changed`);
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replace `X.get(IDX).unwrap()` with `X(IDX)`.
|
|
3
|
+
*
|
|
4
|
+
* `X.get(idx)` on an indexable type (ArrayList, Deque, HashMap,
|
|
5
|
+
* BTreeMap, String, Array, imm Vec, etc.) returns `Option(T)`.
|
|
6
|
+
* Calling `.unwrap()` panics on a missing element. Yo's Index-trait
|
|
7
|
+
* call-syntax `X(idx)` returns `T` directly and panics identically
|
|
8
|
+
* (via the trait's bounds assertion).
|
|
9
|
+
*
|
|
10
|
+
* The substitution is safe whenever:
|
|
11
|
+
* 1. `X.get(...)` has exactly ONE argument.
|
|
12
|
+
* 2. `X` is followed by `.get(...)` then immediately `.unwrap()`.
|
|
13
|
+
* 3. The receiver type implements Index — for which we have no
|
|
14
|
+
* type info here, but it covers the vast majority of cases
|
|
15
|
+
* since Option/Result don't have `.get(IDX)` shape (Option has
|
|
16
|
+
* no `.get` at all; Result.ok() returns Option).
|
|
17
|
+
*
|
|
18
|
+
* Skipped:
|
|
19
|
+
* - `X.get(K, V).unwrap()` — multi-arg get (not Index trait).
|
|
20
|
+
* - `X.get().unwrap()` — zero-arg get.
|
|
21
|
+
* - Bare `.get(...)` without `.unwrap()`.
|
|
22
|
+
* - Anything followed by another `.method(...)` chain after
|
|
23
|
+
* `.unwrap()` — we leave the result chain alone (still works
|
|
24
|
+
* since `X(i)` returns the same type as `X.get(i).unwrap()`).
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* bun run scripts/migrate-get-unwrap.ts # dry-run
|
|
28
|
+
* bun run scripts/migrate-get-unwrap.ts --write # apply
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
32
|
+
import * as path from "node:path";
|
|
33
|
+
|
|
34
|
+
const ROOTS = ["std", "yo-self", "tests"];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Walk a balanced parenthesized group starting at `start` (where
|
|
38
|
+
* `content[start]` is `(`). Returns the inner content and the index
|
|
39
|
+
* just after the closing `)`. Returns null on unbalanced input.
|
|
40
|
+
*/
|
|
41
|
+
function matchBalancedParens(
|
|
42
|
+
content: string,
|
|
43
|
+
start: number
|
|
44
|
+
): { inner: string; end: number } | null {
|
|
45
|
+
if (content[start] !== "(") return null;
|
|
46
|
+
let depth = 1;
|
|
47
|
+
let i = start + 1;
|
|
48
|
+
const innerStart = i;
|
|
49
|
+
while (i < content.length) {
|
|
50
|
+
const ch = content[i]!;
|
|
51
|
+
if (ch === "(") depth++;
|
|
52
|
+
else if (ch === ")") {
|
|
53
|
+
depth--;
|
|
54
|
+
if (depth === 0) {
|
|
55
|
+
return { inner: content.slice(innerStart, i), end: i + 1 };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Count top-level commas in `s` — useful for detecting multi-arg
|
|
65
|
+
* function calls. Skips commas inside nested parens / brackets / braces.
|
|
66
|
+
*/
|
|
67
|
+
function topLevelCommaCount(s: string): number {
|
|
68
|
+
let count = 0;
|
|
69
|
+
let depth = 0;
|
|
70
|
+
for (let i = 0; i < s.length; i++) {
|
|
71
|
+
const ch = s[i]!;
|
|
72
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
73
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
74
|
+
else if (ch === "," && depth === 0) count++;
|
|
75
|
+
}
|
|
76
|
+
return count;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function migrate(content: string): { result: string; rewrites: number } {
|
|
80
|
+
let rewrites = 0;
|
|
81
|
+
// We scan for the literal `.get(`, then walk:
|
|
82
|
+
// ... `.get(` BALANCED `)` `.unwrap()`
|
|
83
|
+
// and rewrite the entire span back to `(` BALANCED `)`.
|
|
84
|
+
//
|
|
85
|
+
// The receiver `X` is whatever precedes `.get(`. We DO NOT rewrite
|
|
86
|
+
// the receiver — just leave it in place and append `(args)` after
|
|
87
|
+
// it. That way `foo.bar.get(i).unwrap()` becomes `foo.bar(i)`.
|
|
88
|
+
const out: string[] = [];
|
|
89
|
+
let i = 0;
|
|
90
|
+
while (i < content.length) {
|
|
91
|
+
const idx = content.indexOf(".get(", i);
|
|
92
|
+
if (idx === -1) {
|
|
93
|
+
out.push(content.slice(i));
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
out.push(content.slice(i, idx));
|
|
97
|
+
const argStart = idx + 4;
|
|
98
|
+
const args = matchBalancedParens(content, argStart);
|
|
99
|
+
if (!args) {
|
|
100
|
+
out.push(content[i]!);
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
// Check for `.unwrap()` immediately after.
|
|
105
|
+
if (content.slice(args.end, args.end + 9) !== ".unwrap()") {
|
|
106
|
+
// Not a `.get(IDX).unwrap()` pair — leave alone.
|
|
107
|
+
out.push(content.slice(idx, args.end));
|
|
108
|
+
i = args.end;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// Require exactly one top-level arg (skip empty-arg get, skip
|
|
112
|
+
// 2+arg get). Allow whitespace-only emptiness check.
|
|
113
|
+
if (args.inner.trim().length === 0 || topLevelCommaCount(args.inner) > 0) {
|
|
114
|
+
out.push(content.slice(idx, args.end + 9));
|
|
115
|
+
i = args.end + 9;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// Rewrite: `.get(IDX).unwrap()` → `(IDX)`.
|
|
119
|
+
out.push(`(${args.inner})`);
|
|
120
|
+
rewrites++;
|
|
121
|
+
i = args.end + 9; // skip past `.unwrap()`
|
|
122
|
+
}
|
|
123
|
+
return { result: out.join(""), rewrites };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function* walk(dir: string): Generator<string> {
|
|
127
|
+
for (const entry of readdirSync(dir)) {
|
|
128
|
+
if (entry.startsWith(".")) continue;
|
|
129
|
+
const fullPath = path.join(dir, entry);
|
|
130
|
+
const st = statSync(fullPath);
|
|
131
|
+
if (st.isDirectory()) {
|
|
132
|
+
yield* walk(fullPath);
|
|
133
|
+
} else if (st.isFile() && entry.endsWith(".yo")) {
|
|
134
|
+
yield fullPath;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function main(): void {
|
|
140
|
+
const write = process.argv.includes("--write");
|
|
141
|
+
let totalRewrites = 0;
|
|
142
|
+
let filesTouched = 0;
|
|
143
|
+
for (const root of ROOTS) {
|
|
144
|
+
const absRoot = path.resolve(root);
|
|
145
|
+
for (const filePath of walk(absRoot)) {
|
|
146
|
+
const content = readFileSync(filePath, "utf-8");
|
|
147
|
+
const { result, rewrites } = migrate(content);
|
|
148
|
+
if (result === content) continue;
|
|
149
|
+
filesTouched++;
|
|
150
|
+
totalRewrites += rewrites;
|
|
151
|
+
console.log(
|
|
152
|
+
`${write ? "migrated" : "would migrate"}: ${path.relative(
|
|
153
|
+
process.cwd(),
|
|
154
|
+
filePath
|
|
155
|
+
)} (${rewrites})`
|
|
156
|
+
);
|
|
157
|
+
if (write) writeFileSync(filePath, result, "utf-8");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
console.log(
|
|
161
|
+
`\n${write ? "Migrated" : "Would migrate"} ${totalRewrites} rewrites across ${filesTouched} file(s).`
|
|
162
|
+
);
|
|
163
|
+
if (!write) console.log("(dry-run — pass --write to apply)");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
main();
|