@loopdive/js2 0.57.0 → 0.58.0

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/README.md CHANGED
@@ -57,7 +57,7 @@ STATUS.md rather than duplicating numbers that go stale. Standalone
57
57
  README until the current standalone regression is fixed.
58
58
 
59
59
  <!-- AUTO:conformance-start -->
60
- **test262 conformance**: 32,456 / 43,135 (75.2 %)
60
+ **test262 conformance**: 32,667 / 43,135 (75.7 %)
61
61
  <!-- AUTO:conformance-end -->
62
62
 
63
63
  ## Current Status
package/dist/cli.js CHANGED
@@ -13,7 +13,7 @@ const args = process.argv.slice(2);
13
13
  if (args.includes("--ts7")) {
14
14
  process.env.JS2WASM_TS7 = "1";
15
15
  }
16
- const { compile } = await import("./index.js");
16
+ const { compile, compileProject, entryHasRelativeImports } = await import("./index.js");
17
17
  const { buildDefaultDefines } = await import("./define-substitution-BcUeKC2A.js").then((n) => n.d);
18
18
  if (args.includes("--version") || args.includes("-v")) {
19
19
  console.log(getCliVersion());
@@ -276,7 +276,7 @@ if (!emulateExplicit && !emulateNode && /['"]node:[A-Za-z0-9_./-]+['"]/.test(sou
276
276
  }
277
277
  const name = basename(absInput, ".ts");
278
278
  const dir = outDir ? resolve(outDir) : dirname(absInput);
279
- const result = await compile(source, {
279
+ const compileOptions = {
280
280
  ...optimize ? { optimize } : {},
281
281
  ...target ? { target } : {},
282
282
  ...allocator ? { allocator } : {},
@@ -289,7 +289,8 @@ const result = await compile(source, {
289
289
  fileName: absInput,
290
290
  ...strictNoHostImports !== void 0 ? { strictNoHostImports } : {},
291
291
  ...Object.keys(defines).length > 0 ? { define: defines } : {}
292
- });
292
+ };
293
+ const result = entryHasRelativeImports(source) ? await compileProject(absInput, compileOptions) : await compile(source, compileOptions);
293
294
  if (!result.success) {
294
295
  for (const e of result.errors) {
295
296
  const severity = e.severity === "warning" ? "warning" : "error";
@@ -1819,6 +1819,17 @@ export interface CodegenContext {
1819
1819
  * Wasm byte-identical.
1820
1820
  */
1821
1821
  fnctorEscapeGate?: import('../fnctor-escape-gate.js').FnctorEscapeGateResult;
1822
+ /**
1823
+ * #2773 S1 (keystone) — fnctor name → reserved `$__fnctor_<Name>` struct type
1824
+ * index. Populated up-front by `reserveFnctorStructTypes` (index.ts) at the
1825
+ * deterministic type-init phase so the index is IDENTICAL across the hoist pass
1826
+ * and the emit pass (the on-demand registration at the `new F()` site landed at
1827
+ * a pass-dependent index → `ref.test`/`struct.get` desync). When a name is
1828
+ * present here, `compileNewFunctionDeclaration` FILLS the reserved slot in place
1829
+ * instead of pushing a new type (which would re-shift every downstream typeIdx).
1830
+ * Empty for fnctor-free modules ⇒ byte-identical no-op.
1831
+ */
1832
+ fnctorReservedTypeIdx: Map<string, number>;
1822
1833
  /**
1823
1834
  * #1886 Slice B — Func index of the lazily-emitted
1824
1835
  * `__lin_u8_alloc(len:i32)->i32` bump allocator for linear-backed Uint8Array
@@ -1,4 +1,5 @@
1
1
  import { ts } from '../ts-api.js';
2
+ import { FieldDef } from '../ir/types.js';
2
3
  import { CodegenContext, FunctionContext } from './context/types.js';
3
4
  /** Classification of a `new F()` fnctor allocation site. */
4
5
  export type FnctorGateClass =
@@ -52,6 +53,28 @@ export interface FnctorEscapeGateResult {
52
53
  * `resolveReceiverStruct`; no lowering reads it, so emitted Wasm is byte-identical.
53
54
  */
54
55
  readonly receiverStruct: ReadonlyMap<ts.Expression, string>;
56
+ /**
57
+ * #2773 S2b — fnctor NAMES that own a `new this()` reconstruct site (a
58
+ * `new this()` inside a static / prototype method classified as an `F`
59
+ * reconstruct). The #2773 S1 up-front struct-type reservation
60
+ * (`reserveFnctorStructTypes`) unions this with {@link approvedNames} so a
61
+ * `Parser` reconstructed via `new this()` also gets a reserved
62
+ * `$__fnctor_Parser` slot. **S1 ships this as an EMPTY set** — S1 only READS it
63
+ * (so the reservation union is a no-op today); S2b populates it. Landing the
64
+ * field shape here keeps S2b purely additive.
65
+ */
66
+ readonly newThisOwnerNames: ReadonlySet<string>;
67
+ /**
68
+ * #2773 S1 — fnctor NAME → its function-like declaration (the body-bearer whose
69
+ * `this.<field> = …` writes derive the `$__fnctor_<Name>` struct shape). Covers
70
+ * EVERY fnctor `new F()` site seen (not just approved ones) so
71
+ * `reserveFnctorStructTypes` can resolve a name to the SAME declaration the
72
+ * on-demand `compileNewFunctionDeclaration` path uses — guaranteeing identical
73
+ * field derivation. A name with ≥2 distinct declarations keeps the first
74
+ * (deterministic by source order); ambiguity here only affects WHICH body shapes
75
+ * the reserved slot (matching the on-demand resolution at the dominant site).
76
+ */
77
+ readonly ctorDeclByName: ReadonlyMap<string, ts.FunctionDeclaration | ts.FunctionExpression>;
55
78
  }
56
79
  /**
57
80
  * Whether `expr` resolves to a plain function constructor (fnctor) rather than a
@@ -90,3 +113,25 @@ export declare function resolveReceiverStruct(ctx: CodegenContext, fctx: Functio
90
113
  * sites exist (so the pass is a no-op for class-only / fnctor-free code).
91
114
  */
92
115
  export declare function analyzeFnctorEscapeGate(checker: ts.TypeChecker, sourceFile: ts.SourceFile): FnctorEscapeGateResult;
116
+ /**
117
+ * #2773 S1 (keystone) — derive the WasmGC field shape of a fnctor's
118
+ * `$__fnctor_<Name>` struct from its constructor body's `this.<field> = …`
119
+ * assignments. This is the **single source of truth** for the field set,
120
+ * EXTRACTED verbatim from the on-demand inline logic that lived in
121
+ * `compileNewFunctionDeclaration` (new-super.ts) so both the up-front reservation
122
+ * pass and the legacy on-demand fallback produce the SAME shape — divergent field
123
+ * order would give `struct.new` a different arity than the reserved type and trap.
124
+ *
125
+ * Mirrors the original logic exactly:
126
+ * - collects EVERY `this.<field>` LHS across (possibly CHAINED) assignments
127
+ * (`this.a = this.b = expr`), recursing into if/else and loop blocks;
128
+ * - prefers the RHS type when the LHS is `any` (externref) — the RHS carries the
129
+ * concrete type (e.g. number → f64);
130
+ * - widens non-null `ref` fields to `ref_null` so `struct.new`'s `ref.null`
131
+ * default-init is well-typed (a struct.new can't default a non-null ref).
132
+ *
133
+ * @param ctx codegen context (for the checker + `resolveWasmType`)
134
+ * @param funcDecl the fnctor's function-like declaration (its body is read)
135
+ * @returns the ordered field set, or `[]` for a body-less / empty-body fnctor.
136
+ */
137
+ export declare function deriveFnctorFields(ctx: CodegenContext, funcDecl: ts.FunctionDeclaration | ts.FunctionExpression): FieldDef[];
@@ -183,6 +183,41 @@ export declare function reserveTypedArraySubviewTypes(ctx: CodegenContext): void
183
183
  * fallback can't fire without a class).
184
184
  */
185
185
  export declare function reserveObjVecArrType(ctx: CodegenContext): void;
186
+ /**
187
+ * #2773 S1 (KEYSTONE) — reserve every reconstructed-fnctor `$__fnctor_<Name>`
188
+ * struct type at the deterministic up-front type-init phase (the same stable
189
+ * point as `reserveTypedArraySubviewTypes` / `reserveObjVecArrType`), so the type
190
+ * index is IDENTICAL across the hoist pass and the emit pass.
191
+ *
192
+ * ROOT CAUSE this fixes: the on-demand registration at the `new F()` call site
193
+ * (`compileNewFunctionDeclaration`, new-super.ts — `ctx.mod.types.length`) assigns
194
+ * the index at a NON-deterministic mid-compile point that depends on which
195
+ * function the compiler reached first. The two-pass type numbering then desyncs:
196
+ * a typed-receiver `ref.test $__fnctor_<Name>` baked in the hoist pass misses the
197
+ * emit-pass `struct.new` index, and a read site compiled before the `new` site is
198
+ * excluded from `findAlternateStructsForField`'s candidate set. Reserving up-front
199
+ * collapses BOTH facets — the index is pass-invariant AND the candidate set is
200
+ * complete at every read site. This is the one thing the #2674 finalize
201
+ * dispatcher cannot retroactively fix (it can't rewrite a baked typeIdx).
202
+ *
203
+ * Two sub-passes — the ORDER is load-bearing:
204
+ * (1) reserve ALL indices + names FIRST (placeholder struct, `structMap`,
205
+ * `typeIdxToStructName`, `fnctorReservedTypeIdx`), so a cross-fnctor ref
206
+ * field in sub-pass 2 (a `Parser` field typed `Scope` →
207
+ * `(ref null $__fnctor_Scope)`) resolves against an already-registered
208
+ * `structMap` entry. Do NOT collapse the two sub-passes.
209
+ * (2) FILL each placeholder's fields via the shared `deriveFnctorFields`
210
+ * (single source of truth — identical to the on-demand derivation) and
211
+ * record `structFields` for candidate-set completeness.
212
+ *
213
+ * Determinism: the name set is SORTED, and the call-site position is fixed, so the
214
+ * reserved index is identical across the hoist pass and the emit pass (the entire
215
+ * point of the slice). Gated on a non-empty approved set ⇒ fnctor-free modules are
216
+ * byte-identical (a true no-op). Runs in BOTH host and standalone — the on-demand
217
+ * struct path is target-independent. A reserved-but-never-constructed placeholder
218
+ * is unreferenced ⇒ `dead-elimination` prunes + renumbers it cleanly.
219
+ */
220
+ export declare function reserveFnctorStructTypes(ctx: CodegenContext): void;
186
221
  export declare function ensureLinearU8AllocHelper(ctx: CodegenContext): number;
187
222
  /**
188
223
  * #1618: Ensure __wasi_write_any_string(s: ref NativeString) -> void exists and
@@ -289,6 +289,6 @@ export declare function emitThisReceiverGuardConvert(ctx: CodegenContext, fctx:
289
289
  * `ref.is_null`, and emit the index + read only in the non-null arm.
290
290
  */
291
291
  export declare function compileOptionalElementAccess(ctx: CodegenContext, fctx: FunctionContext, expr: ts.ElementAccessExpression): ValType | null;
292
- export declare function compileElementAccess(ctx: CodegenContext, fctx: FunctionContext, expr: ts.ElementAccessExpression): ValType | null;
292
+ export declare function compileElementAccess(ctx: CodegenContext, fctx: FunctionContext, expr: ts.ElementAccessExpression, expectedType?: ValType): ValType | null;
293
293
  /** Inner element access logic — assumes objType is on the stack and non-null */
294
- export declare function compileElementAccessBody(ctx: CodegenContext, fctx: FunctionContext, expr: ts.ElementAccessExpression, objType: ValType): ValType | null;
294
+ export declare function compileElementAccessBody(ctx: CodegenContext, fctx: FunctionContext, expr: ts.ElementAccessExpression, objType: ValType, expectedType?: ValType): ValType | null;
@@ -1,6 +1,36 @@
1
1
  import { ts } from '../../ts-api.js';
2
2
  import { CodegenContext, FunctionContext } from '../context/types.js';
3
3
  export declare function compileWhileStatement(ctx: CodegenContext, fctx: FunctionContext, stmt: ts.WhileStatement): void;
4
+ /**
5
+ * #1196: Detect mutations of the loop index or array binding inside a for-loop
6
+ * body. Used by the bounds-check elimination pass — we can only elide bounds
7
+ * checks for `arr[i]` if both `i` and `arr` are stable across every iteration.
8
+ *
9
+ * Returns `true` if the body contains anything that could mutate either
10
+ * binding:
11
+ * - Direct assignment / compound assignment to `i` or `arr`
12
+ * (`i = …`, `i += …`, `arr = …`, etc.)
13
+ * - `i++ / ++i / i-- / --i` or the same on `arr`
14
+ * - Method calls on `arr` (`arr.push()`, `arr.length = …`, etc.)
15
+ * - `arr.length = …` assignment
16
+ * - Any nested function / arrow / class — closures could capture and mutate
17
+ * either binding outside our static view (conservative).
18
+ *
19
+ * Notes:
20
+ * - `arr[k] = v` writes through the array but does not change the binding
21
+ * itself or `arr.length` (when `k < arr.length`), so element writes are
22
+ * allowed — they're the whole point of the optimisation.
23
+ */
24
+ export declare function loopBodyMutatesIndexOrArray(body: ts.Statement, indexName: string, arrayName: string): boolean;
25
+ /**
26
+ * #2682: the increment must strictly INCREASE `i` so that, combined with a
27
+ * non-negative init and the strict `i < recv.length` condition, `0 <= i < len`
28
+ * holds at every body point. `i++`/`++i` and `i += <positive int literal>`
29
+ * qualify; `i--`/`i -= k`/`i += <non-positive>` do NOT (would break the proof).
30
+ * Narrower than `detectI32LoopVar`'s incrementor check, which also accepts the
31
+ * decreasing forms.
32
+ */
33
+ export declare function isIncreasingStep(incr: ts.Expression | undefined, name: string): boolean;
4
34
  export declare function compileForStatement(ctx: CodegenContext, fctx: FunctionContext, stmt: ts.ForStatement): void;
5
35
  export declare function compileDoWhileStatement(ctx: CodegenContext, fctx: FunctionContext, stmt: ts.DoStatement): void;
6
36
  export declare function compileForOfStatement(ctx: CodegenContext, fctx: FunctionContext, stmt: ts.ForOfStatement): void;
@@ -2,6 +2,17 @@ import { IncrementalLanguageService } from './checker/index.js';
2
2
  import { CompileOptions, CompileResult } from './index.js';
3
3
  export { compileToObjectSource } from './compiler/output.js';
4
4
  export type { ObjectCompileResult } from './compiler/output.js';
5
+ /**
6
+ * #2771 — does the entry source statically import (or `require`) a RELATIVE
7
+ * module (`./x` / `../x`)? The single-source `compile()` path reads exactly one
8
+ * file and strips every import in `preprocessImports`, so a relative import is
9
+ * silently unresolved — its bindings lower to bogus `env.*` host imports (which
10
+ * the WASI strict-no-host gate then rejects). The CLI uses this to route such an
11
+ * entry to the multi-file bundler (`compileProject`), which resolves the
12
+ * relative deps through the TS program. Package / `node:` / bare-specifier
13
+ * imports are NOT relative and stay on the single-source path (byte-neutral).
14
+ */
15
+ export declare function entryHasRelativeImports(source: string): boolean;
5
16
  /**
6
17
  * #2657 — the source-import module string for js2wasm's raw linear-memory access
7
18
  * intrinsics (`store32`/`load32`/`store8`/`load8`). These are NOT WASI host
package/dist/index.d.ts CHANGED
@@ -476,6 +476,7 @@ export declare function createIncrementalCompiler(defaultOptions?: CompileOption
476
476
  compile: (source: string, options?: CompileOptions) => Promise<CompileResult>;
477
477
  dispose: () => void;
478
478
  };
479
+ export { entryHasRelativeImports } from './compiler.js';
479
480
  export { getBarePackageName, ModuleResolver, resolveAllImports } from './resolve.js';
480
481
  export { preloadLibFiles } from './checker/index.js';
481
482
  export { getEntryExportNames, treeshake } from './treeshake.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as path from "path";
2
- import { i as isKnownLibName, g as getLibSourceFile, a as getDefaultEnvironment, r as rewriteCjsRequire, f as forEachChild, c as compileSource, b as buildImports, d as compileFilesSource, e as compileMultiSource, h as compileToObjectSource } from "./runtime-C-4q_KwU.js";
3
- import { j, k, l, m, n, o, p, q, s } from "./runtime-C-4q_KwU.js";
2
+ import { i as isKnownLibName, g as getLibSourceFile, a as getDefaultEnvironment, r as rewriteCjsRequire, f as forEachChild, e as entryHasRelativeImports, c as compileSource, b as compileMultiSource, d as buildImports, h as compileFilesSource, j as compileToObjectSource } from "./runtime-d_SmcBlN.js";
3
+ import { k, l, m, n, o, p, q, s, t } from "./runtime-d_SmcBlN.js";
4
4
  import ts from "typescript";
5
5
  class IncrementalLanguageService {
6
6
  currentSource = "";
@@ -549,61 +549,61 @@ const RUNTIME_RECGROUP_TYPE_NAMES = [
549
549
  ];
550
550
  const RUNTIME_RECGROUP_ABI_VERSION = 1;
551
551
  const RUNTIME_NAME_SET = new Set(RUNTIME_RECGROUP_TYPE_NAMES);
552
- function typeDefName(t) {
553
- switch (t.kind) {
552
+ function typeDefName(t2) {
553
+ switch (t2.kind) {
554
554
  case "func":
555
555
  case "struct":
556
556
  case "array":
557
- return t.name;
557
+ return t2.name;
558
558
  case "sub":
559
- return t.name;
559
+ return t2.name;
560
560
  case "rec":
561
561
  return void 0;
562
562
  }
563
563
  }
564
- function valTypeToken(t, localOf) {
565
- switch (t.kind) {
564
+ function valTypeToken(t2, localOf) {
565
+ switch (t2.kind) {
566
566
  case "ref":
567
567
  case "ref_null": {
568
- const local = localOf(t.typeIdx);
569
- const nul = t.kind === "ref_null" ? "n" : "";
568
+ const local = localOf(t2.typeIdx);
569
+ const nul = t2.kind === "ref_null" ? "n" : "";
570
570
  if (local !== void 0) return `${nul}r${local}`;
571
571
  return `${nul}x`;
572
572
  }
573
573
  case "i32":
574
- return t.boolean ? "i32b" : "i32";
574
+ return t2.boolean ? "i32b" : "i32";
575
575
  case "i64":
576
- return t.bigint ? "i64big" : "i64";
576
+ return t2.bigint ? "i64big" : "i64";
577
577
  default:
578
- return t.kind;
578
+ return t2.kind;
579
579
  }
580
580
  }
581
581
  function fieldToken(f, localOf) {
582
582
  return `${f.mutable ? "m" : ""}${valTypeToken(f.type, localOf)}`;
583
583
  }
584
- function structuralToken(t, localOf) {
585
- switch (t.kind) {
584
+ function structuralToken(t2, localOf) {
585
+ switch (t2.kind) {
586
586
  case "func": {
587
- const p2 = t.params.map((v) => valTypeToken(v, localOf)).join(",");
588
- const r = t.results.map((v) => valTypeToken(v, localOf)).join(",");
587
+ const p2 = t2.params.map((v) => valTypeToken(v, localOf)).join(",");
588
+ const r = t2.results.map((v) => valTypeToken(v, localOf)).join(",");
589
589
  return `func(${p2})->(${r})`;
590
590
  }
591
591
  case "struct": {
592
- const sup = t.superTypeIdx !== void 0 && t.superTypeIdx >= 0 ? (() => {
593
- const local = localOf(t.superTypeIdx);
594
- return local !== void 0 ? `sub r${local}${t.final ? "!" : ""} ` : `sub x${t.final ? "!" : ""} `;
592
+ const sup = t2.superTypeIdx !== void 0 && t2.superTypeIdx >= 0 ? (() => {
593
+ const local = localOf(t2.superTypeIdx);
594
+ return local !== void 0 ? `sub r${local}${t2.final ? "!" : ""} ` : `sub x${t2.final ? "!" : ""} `;
595
595
  })() : "";
596
- const fields = t.fields.map((f) => fieldToken(f, localOf)).join(";");
596
+ const fields = t2.fields.map((f) => fieldToken(f, localOf)).join(";");
597
597
  return `${sup}struct{${fields}}`;
598
598
  }
599
599
  case "array":
600
- return `array<${t.mutable ? "m" : ""}${valTypeToken(t.element, localOf)}>`;
600
+ return `array<${t2.mutable ? "m" : ""}${valTypeToken(t2.element, localOf)}>`;
601
601
  case "sub": {
602
- const sup = t.superType !== null ? (() => {
603
- const local = localOf(t.superType);
604
- return local !== void 0 ? `sub r${local}${t.final ? "!" : ""} ` : `sub x${t.final ? "!" : ""} `;
605
- })() : t.final ? "final " : "";
606
- return `${sup}${structuralToken(t.type, localOf)}`;
602
+ const sup = t2.superType !== null ? (() => {
603
+ const local = localOf(t2.superType);
604
+ return local !== void 0 ? `sub r${local}${t2.final ? "!" : ""} ` : `sub x${t2.final ? "!" : ""} `;
605
+ })() : t2.final ? "final " : "";
606
+ return `${sup}${structuralToken(t2.type, localOf)}`;
607
607
  }
608
608
  }
609
609
  }
@@ -631,11 +631,11 @@ function extractRuntimeGroup(mod) {
631
631
  const out = [];
632
632
  const types = mod.types;
633
633
  for (let i = 0; i < types.length; i++) {
634
- const t = types[i];
635
- if (t.kind === "rec") continue;
636
- const name = typeDefName(t);
634
+ const t2 = types[i];
635
+ if (t2.kind === "rec") continue;
636
+ const name = typeDefName(t2);
637
637
  if (name !== void 0 && RUNTIME_NAME_SET.has(name)) {
638
- out.push({ name, absIndex: i, def: t });
638
+ out.push({ name, absIndex: i, def: t2 });
639
639
  }
640
640
  }
641
641
  return out;
@@ -729,27 +729,28 @@ export {
729
729
  RUNTIME_RECGROUP_ABI_VERSION,
730
730
  RUNTIME_RECGROUP_TYPE_NAMES,
731
731
  buildImports,
732
- j as buildStringConstants,
733
- k as buildWasiPolyfill,
732
+ k as buildStringConstants,
733
+ l as buildWasiPolyfill,
734
734
  canonicalHashOfTypeGroup,
735
- l as checkPolicy,
735
+ m as checkPolicy,
736
736
  compile,
737
- m as compileAndInstantiate,
737
+ n as compileAndInstantiate,
738
738
  compileFiles,
739
739
  compileMulti,
740
740
  compileProject,
741
741
  compileToObject,
742
742
  compileToWat,
743
743
  createIncrementalCompiler,
744
+ entryHasRelativeImports,
744
745
  extractRuntimeGroup,
745
746
  fingerprintRuntimeGroup,
746
- n as generateWit,
747
+ o as generateWit,
747
748
  getBarePackageName,
748
749
  getEntryExportNames,
749
- o as instantiateWasm,
750
- p as instantiateWasmStreaming,
751
- q as jsString,
752
- s as preloadLibFiles,
750
+ p as instantiateWasm,
751
+ q as instantiateWasmStreaming,
752
+ s as jsString,
753
+ t as preloadLibFiles,
753
754
  resolveAllImports,
754
755
  treeshake
755
756
  };
@@ -126,6 +126,7 @@ export interface FieldDef {
126
126
  export type ValType = {
127
127
  kind: "i32";
128
128
  boolean?: true;
129
+ symbol?: true;
129
130
  } | {
130
131
  kind: "i64";
131
132
  bigint?: boolean;