diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbc921b74..bbc9ddb10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,21 +4,23 @@ on: push: branches: [main] pull_request: - # Nightly full macOS sweep, including on days without a push to main. + # Nightly full macOS sweep. Pushes to main run no macOS jobs, and pull requests run them only when they change + # darwin code (scripts/ci-lanes.mjs). schedule: - cron: "17 7 * * *" - # A manual run; macos_full runs the complete suite on macOS as the nightly sweep does (for example on a - # release candidate commit). + # A manual run, for example on a release candidate commit. It runs every lane; macos_suite selects the complete + # suite on macOS, as the nightly sweep runs it, or the darwin suite that pull requests run. workflow_dispatch: inputs: - macos_full: - description: Run the complete test suite on macOS - type: boolean - default: false + macos_suite: + description: The test suite to run on macOS + type: choice + options: [full, darwin] + default: full concurrency: # A new push to a pull request cancels its previous run. Every push to main runs to completion in a group of - # its own commit, so each merged commit gets a full result (releases require a green run on their commit). The + # its own commit, so each merged commit gets a result (releases require a green run on their commit). The # nightly sweep and manual runs neither cancel nor are cancelled by main pushes. group: ci-${{ github.event_name == 'schedule' && 'schedule' || github.event_name == 'workflow_dispatch' && format('dispatch-{0}', github.run_id) || github.event.pull_request.number || github.sha }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} @@ -59,7 +61,8 @@ jobs: NODE # Which expensive lanes a pull request needs (scripts/ci-lanes.mjs, run from the base commit against the pull - # request's diff, so a pull request cannot change its own selection). Pushes and the nightly sweep run every lane. + # request's diff, so a pull request cannot change its own selection). Pushes to main run every lane except macos, + # whose jobs the nightly sweep runs on main. The nightly sweep and manual runs run every lane. lanes: needs: validation_scope if: ${{ needs.validation_scope.outputs.full == 'true' }} @@ -86,7 +89,9 @@ jobs: HEAD: ${{ github.event.pull_request.head.sha }} run: | every() { printf 'linux=true\nmacos=true\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT"; } - if [ "$EVENT" != pull_request ]; then + if [ "$EVENT" = push ]; then + printf 'linux=true\nmacos=false\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT" + elif [ "$EVENT" != pull_request ]; then every elif git show "$BASE:scripts/ci-lanes.mjs" > "$RUNNER_TEMP/ci-lanes.mjs" 2> /dev/null; then node "$RUNNER_TEMP/ci-lanes.mjs" --base "$BASE" --head "$HEAD" --github-output @@ -219,25 +224,25 @@ jobs: tests_macos: needs: [validation_scope, lanes, native_macos] if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }} - name: test (macOS, ${{ matrix.flavor }}, ${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin ' || '' }}${{ matrix.shard }}/${{ (github.event_name != 'schedule' && !inputs.macos_full) && '3' || '10' }}) + name: test (macOS, ${{ matrix.flavor }}, ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin ' || '' }}${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }}) # arm64 macOS: same clang/libc the differential + number-formatting - # oracles were verified against. Pull requests and pushes to main run the - # darwin suite (scripts/ci-test-plan.mjs) in three slices; the nightly - # schedule and manual runs with macos_full run the complete suite in ten. - # The complete suite on every push needed more macOS runners than the - # repository has. + # oracles were verified against. Pull requests that change darwin code and + # manual runs with macos_suite set to darwin run the darwin suite + # (scripts/ci-test-plan.mjs) in three slices; the nightly schedule and + # other manual runs run the complete suite in ten. The complete suite on + # every push needed more macOS runners than the repository has. runs-on: macos-15 strategy: fail-fast: false matrix: flavor: [plain, san] - shard: ${{ fromJSON((github.event_name != 'schedule' && !inputs.macos_full) && '[1, 2, 3]' || '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') }} + shard: ${{ fromJSON((github.event_name != 'schedule' && inputs.macos_suite != 'full') && '[1, 2, 3]' || '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') }} env: # See the Linux tests job for the worker cap and case partitioning. SCRIPTC_TEST_WORKERS: "4" SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }} - SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/${{ (github.event_name != 'schedule' && !inputs.macos_full) && '3' || '10' }} - SCRIPTC_CI_SUITE: ${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin' || 'full' }} + SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }} + SCRIPTC_CI_SUITE: ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin' || 'full' }} steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -254,7 +259,7 @@ jobs: path: ${{ runner.temp }} - run: tar -xzf "$RUNNER_TEMP/native-darwin-arm64.tar.gz" - run: pnpm build - - name: Tests (${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin suite' || 'corpus and integration suites' }}) + - name: Tests (${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin suite' || 'corpus and integration suites' }}) run: node scripts/ci-test.mjs native_linux: @@ -868,14 +873,25 @@ jobs: strategy: fail-fast: false matrix: - include: - - runtime: runtime-ios-arm64 - runner: macos-15 - - runtime: runtime-ios-simulator-arm64 - runner: macos-15 - - runtime: runtime-android-arm64 - runner: ubuntu-24.04 - runs-on: ${{ matrix.runner }} + runtime: [runtime-android-arm64] + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/mobile-runtime-pack + with: + runtime: ${{ matrix.runtime }} + + # The iOS runtime packs build on macOS runners, so they run in the macos lane. + mobile_runtime_packs_ios: + needs: [validation_scope, lanes] + if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }} + name: test (${{ matrix.runtime }}) + strategy: + fail-fast: false + matrix: + runtime: [runtime-ios-arm64, runtime-ios-simulator-arm64] + runs-on: macos-15 timeout-minutes: 20 steps: - uses: actions/checkout@v4 @@ -908,6 +924,7 @@ jobs: windows_cli, llvm_artifacts_windows_x64, mobile_runtime_packs, + mobile_runtime_packs_ios, ] # Run even after failed or cancelled lanes: skipped required jobs allow merging. if: ${{ always() }} @@ -939,6 +956,7 @@ jobs: lane windows_cli "${{ needs.windows_cli.result }}" "${{ needs.lanes.outputs.linux }}" lane llvm_artifacts_windows_x64 "${{ needs.llvm_artifacts_windows_x64.result }}" "${{ needs.lanes.outputs.linux }}" lane mobile_runtime_packs "${{ needs.mobile_runtime_packs.result }}" "${{ needs.lanes.outputs.linux }}" + lane mobile_runtime_packs_ios "${{ needs.mobile_runtime_packs_ios.result }}" "${{ needs.lanes.outputs.macos }}" ;; false) echo "::notice::Full CI is deferred until this PR becomes the lowest unmerged layer. Merge the stack from the bottom up." diff --git a/docs/content/limitations/builtin-apis/eventemitter.mdx b/docs/content/limitations/builtin-apis/eventemitter.mdx index 4f7142094..a9f03b857 100644 --- a/docs/content/limitations/builtin-apis/eventemitter.mdx +++ b/docs/content/limitations/builtin-apis/eventemitter.mdx @@ -1,4 +1,5 @@ - **EventEmitter:** Literal event names use fixed argument tuples. Computed string names require a constant prefix or suffix that excludes `error`, meta events, and stream-internal events; their arguments must match the exact arity and support checked conversion. Unrestricted names, symbols, names that may reach internal events, and typed `listeners()`/`rawListeners()` results for names with possible computed registrations are unsupported. + `events.once` accepts EventEmitter and stream instances with a literal event name and an optional object-literal `{ signal }`; a stream's `data` event, other options values, and EventTarget emitters are unsupported. Reading `defaultMaxListeners` through a CommonJS destructured or member binding (`const { defaultMaxListeners } = require("events")`) is unsupported. diff --git a/docs/content/limitations/builtin-function-values.mdx b/docs/content/limitations/builtin-function-values.mdx index 541c9fb2b..2c08ecc6d 100644 --- a/docs/content/limitations/builtin-function-values.mdx +++ b/docs/content/limitations/builtin-function-values.mdx @@ -19,7 +19,11 @@ Native calls accept standard process streams; custom native streams and mutation In JavaScript, a constructor function can inherit from `EventEmitter`, the legacy `Stream` constructor, or a stream class through `util.inherits` or `Object.setPrototypeOf`. `EventEmitter.call(this)` and `Stream.call(this)` initialize the object as Node does, and its listener methods keep their state in the object's own `_events`, `_eventsCount`, and `_maxListeners` properties. The `captureRejections` option and `events.errorMonitor` listeners are not supported on such objects. -Calling `Readable`, `Writable`, `Duplex`, `Transform`, or `PassThrough` with a constructor function's receiver throws `SC2020` where the call runs. +`Readable.call(this, options)` and the same call on `Writable`, `Duplex`, `Transform`, or `PassThrough` give the object a native stream. +The stream calls the object's `_read`, `_write`, `_final`, `_transform`, `_flush`, and `_destroy` with the object as `this`, and the stream prototypes' methods and accessors work on it. +Its listeners live in the native stream rather than in `_events`, and `_readableState` and `_writableState` are read-only views that answer the commonly read fields. +Typed code that receives such an object as a stream class uses its native stream, and piping a native stream into it works. +The options the native streams refuse (`objectMode`, `decodeStrings: false`, `signal`, and others) are refused here too, as are `_writev`, `_construct`, symbol event names, `listeners()`, `rawListeners()`, `wrap()`, strings written in encodings other than UTF-8, and pipes into a destination that overrides `write()` or `end()`. Other class constructors passed to `util.inherits` or `util.deprecate` are refused. Deprecation warnings are emitted on the next tick and follow assignments to `process.noDeprecation` and `process.throwDeprecation`; compiled binaries take no Node command-line flags such as `--no-deprecation` or `--trace-deprecation`, and `process.traceDeprecation` is unsupported. A stored `util.deprecate` reports a `length` of 4, where Node reports 3. diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 411291d94..6dfcc90ef 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -6569,7 +6569,7 @@ declare module "events" { export { internal as EventEmitter }; // The event's arguments, as checked-dynamic values. export function once( - emitter: EventEmitter, + emitter: EventEmitter | NodeJS.ReadableStream | NodeJS.WritableStream, eventName: string, options?: { signal?: AbortSignal }, ): Promise; diff --git a/packages/compiler/package.json b/packages/compiler/package.json index b6a18db06..97c7688f5 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -39,7 +39,7 @@ }, "dependencies": { "@scriptc/runtime": "workspace:*", - "tsc-ts": "0.0.3", + "tsc-ts": "0.0.5", "typescript": "7.0.2", "typescript5": "npm:typescript@5.9.3" }, diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 9cbf030e7..0fcf86ada 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -27,6 +27,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { dynAsync: features.dynAsync, ...(features.dynTier ? { dynTier: true } : {}), ...(features.streamDyn ? { streamDyn: true } : {}), + ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), events: features.processEvents, emitter: features.emitter, symbol: features.symbol, @@ -34,6 +35,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { searchParams: features.searchParams, qs: features.qs, dynBuiltinClass: features.dynBuiltinClass, + dynBuiltinStream: features.dynBuiltinStream, parseArgs: features.parseArgs, stream: features.stream, net: features.net, diff --git a/packages/compiler/src/backend/library-pack.ts b/packages/compiler/src/backend/library-pack.ts index 42275c5af..cb929e4b0 100644 --- a/packages/compiler/src/backend/library-pack.ts +++ b/packages/compiler/src/backend/library-pack.ts @@ -43,6 +43,7 @@ export async function compilePackedLibrary( searchParams: options.searchParams ?? false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 7e414daee..731385825 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -130,6 +130,8 @@ export interface DynHost extends WalkerHost { /** The shared adapter record: live array capsules read its `arrayOps`, so a * narrower return type would hand the native compiler a copy without it. */ liveDynRefAdapter(t: IrType): LlStreamTypedRefAdapter; + /** See moduleUsesDynBuiltinStream. */ + usesDynBuiltinStream?(): boolean; liveDynUnionRefAdapter(t: IrType & { kind: "union" }): string; dynPromiseAdapter(t: IrType): string; isErrorClass(className: string): boolean; @@ -140,6 +142,19 @@ export interface DynHost extends WalkerHost { const FN_ATTRS = "#0"; +/** scr_dyn_builtin_stream_check's kind for a runtime stream class (the + * numbering of scr_dyn_builtin_class.c), or null. */ +function builtinStreamKind(className: string): number | null { + const kinds: Record = { + "%Readable": 2, + "%Writable": 3, + "%Duplex": 4, + "%Transform": 5, + "%PassThrough": 6, + }; + return kinds[className] ?? null; +} + export class LlDyn { private readonly dynMatchers = new Map(); private readonly dynBuilders = new Map(); @@ -1491,6 +1506,24 @@ export class LlDyn { B.line(`${ref} = call ptr @scr_dyn_typed_ref_unbox(ptr %d)`); B.terminate(`ret ptr ${ref}`); B.startBlock(lNext); + // A constructor-function stream (scr_dyn_builtin_stream.c) carries + // its runtime stream. + const streamKind = t.kind === "object" ? builtinStreamKind(t.className) : null; + if (streamKind !== null && this.host.usesDynBuiltinStream?.() === true) { + host.declare(`declare ptr @scr_dyn_builtin_stream_check(ptr, double)`); + const own = B.tmp(), + hit = B.tmp(); + B.line( + `${own} = call ptr @scr_dyn_builtin_stream_check(ptr %d, double ${f64Lit(streamKind)})`, + ); + B.line(`${hit} = icmp ne ptr ${own}, null`); + const lOwn = B.newLabel("dc.ctor"), + lNotOwn = B.newLabel("dc.notctor"); + B.condBr(hit, lOwn, lNotOwn); + B.startBlock(lOwn); + B.terminate(`ret ptr ${own}`); + B.startBlock(lNotOwn); + } if (t.kind !== "union" && preserveRefs) { const kind = this.kindOf(B, "%d"); const capsule = B.tmp(); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index b72a6e226..3c0ab2378 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -53,7 +53,11 @@ import { VOID, classJsName, } from "../../ir/ir.js"; -import { moduleRuntimeFeatures, moduleUsesMockTimers } from "../../ir/runtime-features.js"; +import { + moduleRuntimeFeatures, + moduleUsesDynBuiltinStream, + moduleUsesMockTimers, +} from "../../ir/runtime-features.js"; import { captureFreeFunctions, observesStacks } from "../stack-observations.js"; import { type DenseArrayLoop } from "./dense-array-loops.js"; import { findInitializedByteLoopBindings } from "./byte-windows.js"; @@ -902,6 +906,11 @@ export class LlEmitter { /** Identity-preserving static→dyn capsules for program classes and Web * APIs whose values remain directly observable. */ readonly liveDynRefAdapters = new Map(); + private dynBuiltinStreamUse: boolean | undefined; + /** See moduleUsesDynBuiltinStream. */ + get usesDynBuiltinStream(): boolean { + return (this.dynBuiltinStreamUse ??= moduleUsesDynBuiltinStream(this.mod)); + } /** Runtime-arm dispatchers for live union values. */ readonly liveDynUnionRefAdapters = new Map(); readonly dynPromiseAdapters = new Map(); @@ -1405,6 +1414,7 @@ export class LlEmitter { fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId), absentInstanceRef: (unionId) => this.absentInstanceRef(unionId), liveDynRefAdapter: (type) => this.liveDynRefAdapter(type), + usesDynBuiltinStream: () => this.usesDynBuiltinStream, liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type), dynPromiseAdapter: (type) => this.dynPromiseAdapter(type), isErrorClass: (name) => this.classMeta.get(name)?.root.def.name === "%Error", @@ -5599,8 +5609,11 @@ export class LlEmitter { return emitterFixedAdapter(this, cbT); } - emitterOnceAdapter(boxerT: IrType & { kind: "func" }): { fn: string; shim: string } { - return emitterOnceAdapter(this, boxerT); + emitterOnceAdapter( + boxerT: IrType & { kind: "func" }, + fire?: string, + ): { fn: string; shim: string } { + return emitterOnceAdapter(this, boxerT, fire); } wrapEmitterListener(target: string, adapterFn: string): string { diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index 28bd15759..b91cdc543 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -864,23 +864,27 @@ export function emitterFixedAdapter( * (the event's tuple to any[]): the runtime-built listener closure holds * the boxer in caps[0], so the adapter calls it with the event's typed * arguments, as emitterFixedAdapter calls a listener, and hands the array - * to scr_events_once_fire. */ + * to scr_events_once_fire (or `fire`, the signal form's entry). */ export function emitterOnceAdapter( host: LlvmEmitterContext, boxerT: IrType & { kind: "func" }, + fire = "scr_events_once_fire", ): { fn: string; shim: string } { const cursor = boxerT.params.length > 4; const shim = cursor ? "scr_ee_inv_args" : `scr_ee_inv_fixed${boxerT.params.length}`; if (cursor) host.declare("declare ptr @scr_ee_arg_next(ptr)"); host.declare(`declare void @${shim}(ptr, ptr)`); - const key = `eeOnce:${typeKey(boxerT)}`; + const key = + fire === "scr_events_once_fire" + ? `eeOnce:${typeKey(boxerT)}` + : `eeOnce:${fire}:${typeKey(boxerT)}`; let sym = host.resolveThunks.get(key); if (sym) return { fn: sym, shim }; sym = `sc_ee_once_${host.resolveThunks.size}`; host.resolveThunks.set(key, sym); host.declare(`declare ptr @scr_box_get_ref(ptr)`); host.declare(`declare void @scr_closure_release(ptr)`); - host.declare(`declare void @scr_events_once_fire(ptr, ptr)`); + host.declare(`declare void @${fire}(ptr, ptr)`); const params = cursor ? "ptr %args" : boxerT.params.map((_, i) => `ptr %a${i}`).join(", "); const d: string[] = [ `define internal void @${sym}(ptr %cb${params ? ", " + params : ""}) ${FN_ATTRS} { ; events.once adapter ${typeKey(boxerT)}`, @@ -911,7 +915,7 @@ export function emitterOnceAdapter( ` %fn = load ptr, ptr %fnp`, ` %arr = call ptr %fn(${passed.join(", ")}) ; the boxed arguments, +1`, ` call void @scr_closure_release(ptr %boxer)`, - ` call void @scr_events_once_fire(ptr %cb, ptr %arr) ; moves %arr`, + ` call void @${fire}(ptr %cb, ptr %arr) ; moves %arr`, ` ret void`, `}`, ``, diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 847ed1e20..bbd0226fb 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -766,8 +766,13 @@ export function streamTypedRefMaterializeAdapter( !meta.def.instancePrototypeHelper && !meta.def.prototypeDataHelper ) { - host.declare(`declare ptr @scr_stream_dyn_view(ptr)`); - B.line(`${out} = call ptr @scr_stream_dyn_view(ptr %p)`); + // Programs with constructor-function streams view them through + // scr_dyn_builtin_stream.c, which answers their own objects. + const view = host.usesDynBuiltinStream + ? "scr_dyn_builtin_stream_view" + : "scr_stream_dyn_view"; + host.declare(`declare ptr @${view}(ptr)`); + B.line(`${out} = call ptr @${view}(ptr %p)`); } else B.line(`${out} = call ptr @scr_dyn_new_obj()`); const withAttributes = meta.def.fields.some((field) => field.name === CLASS_ATTRIBUTES); if (meta.def.tracksOwnFields) { diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 6cec9d403..924027ee2 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -548,7 +548,12 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll mockTimerSym(host, e) ?? (e.fn === "timers.queueMicrotaskDyn" && host.schedulesInContext ? "scr_queue_microtask_dyn_als" - : LIB_FN_SYMS[e.fn]); + : // Programs with constructor-function streams unbox untyped pipe + // destinations and pipeline stages through scr_dyn_builtin_stream.c, + // which also takes those streams. + e.fn === "stream.unboxDyn" && host.usesDynBuiltinStream + ? "scr_dyn_builtin_stream_unbox" + : LIB_FN_SYMS[e.fn]); if (sym === undefined) throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc); const args = READ_ONLY_STRING_LIB_FNS.has(e.fn) ? emitBorrowedInputs(host, e.args) @@ -755,7 +760,8 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { } const prefix = e.fn.slice(0, e.fn.indexOf(".")) as LibCallPrefix; // events.once builds typed listener adapters beside the emitter calls. - if (e.fn === "events.once") return host.emitErrorsEventsLibCall(e); + if (e.fn === "events.once" || e.fn === "events.onceSignal") + return host.emitErrorsEventsLibCall(e); switch (prefix) { case "fetch": case "abort": diff --git a/packages/compiler/src/backend/llvm/lib-process.ts b/packages/compiler/src/backend/llvm/lib-process.ts index 4a7be1a35..097fcb4de 100644 --- a/packages/compiler/src/backend/llvm/lib-process.ts +++ b/packages/compiler/src/backend/llvm/lib-process.ts @@ -731,27 +731,34 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr host.emitAlwaysPendingUnwind(); return out; } - if (e.fn === "events.once") { - // (recv, name, boxer, errorBoxer, listenForError): scr_events_once - // registers one-shot typed listeners behind adapters that box the - // event's tuple (and the 'error' tuple) into the fulfilled array. The - // runtime retains the boxers; the frame's temporaries release as usual. - // May-throw ('newListener' meta listeners run inside). + if (e.fn === "events.once" || e.fn === "events.onceSignal") { + // (recv, name, boxer, errorBoxer, listenForError[, signal]): + // scr_events_once registers one-shot typed listeners behind adapters + // that box the event's tuple (and the 'error' tuple) into the fulfilled + // array. The runtime retains the boxers; the frame's temporaries + // release as usual. May-throw ('newListener' meta listeners run + // inside). The signal form's adapters end in its own fire entry. const boxerT = e.args[2]!.type; const errorT = e.args[3]!.type; if (boxerT.kind !== "func" || errorT.kind !== "func") - throw new InternalCompilerError("llvm emitter bug: events.once boxer not a func"); + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} boxer not a func`); + const signal = e.fn === "events.onceSignal"; const args = e.args.map((a) => host.emitExpr(a)); - const event = host.emitterOnceAdapter(boxerT); - const error = host.emitterOnceAdapter(errorT); - host.declare(`declare ptr @scr_events_once(ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr)`); + const fire = signal ? "scr_events_once_signal_fire" : undefined; + const event = host.emitterOnceAdapter(boxerT, fire); + const error = host.emitterOnceAdapter(errorT, fire); + const entry = signal ? "scr_events_once_signal" : "scr_events_once"; + host.declare( + `declare ptr @${entry}(ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr${signal ? ", ptr" : ""})`, + ); const errorBoxer = B.tmp(); B.line(`${errorBoxer} = select i1 ${args[4]!.name}, ptr ${args[3]!.name}, ptr null`); const t = B.tmp(); B.line( - `${t} = call ptr @scr_events_once(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ` + + `${t} = call ptr @${entry}(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ` + `ptr ${args[2]!.name}, ptr @${event.fn}, ptr @${event.shim}, ` + - `ptr ${errorBoxer}, ptr @${error.fn}, ptr @${error.shim})`, + `ptr ${errorBoxer}, ptr @${error.fn}, ptr @${error.shim}` + + `${signal ? `, ptr ${args[5]!.name}` : ""})`, ); const out = host.own({ name: t, type: e.type }); host.emitPendingCheck(); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index f9deb553e..697bb3192 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1464,6 +1464,7 @@ export const LIB_FN_SYMS: Record = { "dyn.classBasePrototype": "scr_dyn_class_base_prototype", "dyn.classInherit": "scr_dyn_class_inherit", "dyn.builtinClass": "scr_dyn_builtin_class", + "dyn.builtinStreamClass": "scr_dyn_builtin_stream_class", "dyn.builtinInstanceOf": "scr_dyn_builtin_instanceof", "dyn.functionInstanceOf": "scr_dyn_function_instanceof", "dyn.classSuper": "scr_dyn_class_super", @@ -1542,6 +1543,8 @@ export function mockTimerSym( export const USES_TIMERS_LIB_FNS = new Set([ "process.emitWarning", + // Constructor-function streams defer their events on the stream ticks. + "dyn.builtinStreamClass", // Decipheriv.setAuthTag reports DEP0182 on the next tick. "crypto.cipherNew", // WebCrypto results settle through the deferred crypto queue. diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index 704945c95..84f448e93 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -46,6 +46,9 @@ export interface NativeLinkFeatures { /** Checked-dynamic stream slots: the scr_stream_dyn.c unit. Absent (not * false) for programs without them. */ streamDyn?: boolean; + /** events.once with options: scr_events_once_signal.c. Absent (not + * false) for programs without it. */ + eventsOnceSignal?: boolean; events: boolean; emitter: boolean; symbol: boolean; @@ -53,6 +56,7 @@ export interface NativeLinkFeatures { searchParams: boolean; qs: boolean; dynBuiltinClass: boolean; + dynBuiltinStream: boolean; parseArgs: boolean; stream: boolean; net: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index c7371077f..a9c93d3df 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -116,6 +116,9 @@ export interface CcOptions { /** Checked-dynamic stream slots (stream.dynNative / stream.pipeDyn): * compiles scr_stream_dyn.c. */ streamDyn?: boolean; + /** events.once with options (events.onceSignal): compiles + * scr_events_once_signal.c. */ + eventsOnceSignal?: boolean; /** The program constructs EventTarget/Event/CustomEvent values or * calls events.addAbortListener (the event.* libCalls), or uses the * native fetch AbortSignal built on the same engine: compiles @@ -160,6 +163,10 @@ export interface CcOptions { * scr_dyn_builtin_class.c, which also references the inspection, * dyn-invoke and symbol units. */ dynBuiltinClass?: boolean; + /** The program uses a stream class as an untyped constructor value: + * compiles scr_dyn_builtin_stream.c, the constructors' stream half, which + * also references the stream unit. */ + dynBuiltinStream?: boolean; /** The program uses native util parsing, comparison, or styling. Optional * utility units also require their symbol, inspection, and warning features. */ parseArgs?: boolean; diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 5ee6b6abd..949f03839 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -220,6 +220,9 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ // measures with. unit((u) => u.opts.events && u.regex, "scr_readline_tty.c"), unit((u) => u.opts.emitter || u.opts.workers, "scr_events_emitter.c"), + // events.once with options registers through the emitter and + // event-target units. + unit((u) => u.opts.eventsOnceSignal === true, "scr_events_once_signal.c"), // The checked-dynamic HANDLE support unit (listener gate + runtime // adapter closures): every referencing unit is one of the emitter or // net families (http implies net), so handle-free binaries keep @@ -247,6 +250,16 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ (u.opts.emitter || u.opts.workers), "scr_dyn_builtin_class.c", ), + unit( + (u) => + u.opts.dynBuiltinStream && + u.opts.dynBuiltinClass && + u.opts.stream && + u.opts.inspect && + u.opts.dynInvoke && + u.opts.symbol, + "scr_dyn_builtin_stream.c", + ), unit((u) => u.opts.stream, "scr_stream.c", "scr_fs_stream.c"), // The zlib stream classes reference both the zlib engine and the // stream unit. diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 748de64fc..5c9d417c5 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -85,6 +85,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { wasm: boolean; dynTier: boolean; streamDyn: boolean; + eventsOnceSignal: boolean; warningReplace: boolean; stdioExtended: boolean; nativeFetch: boolean; @@ -277,6 +278,7 @@ export function effectiveRuntimeFeatures( wasm: features.wasm === true, dynTier: features.dynTier === true, streamDyn: features.streamDyn === true, + eventsOnceSignal: features.eventsOnceSignal === true, warningReplace: features.warningReplace === true, stdioExtended: features.stdioExtended === true, nativeFetch, diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index a39ce78e2..96503e2db 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -108,6 +108,7 @@ const features = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index 1681626b7..cd342f85b 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -45,6 +45,7 @@ const BASE: NativeLinkFeatures = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/backend/stack-observations.test.ts b/packages/compiler/src/backend/stack-observations.test.ts index 0e1178ce9..d381c5307 100644 --- a/packages/compiler/src/backend/stack-observations.test.ts +++ b/packages/compiler/src/backend/stack-observations.test.ts @@ -84,6 +84,97 @@ test("a computed key reads the stack when the program spells it", () => { expect(observesStacks(mod([fn("%main", [computed])]))).toBe(false); }); +test('a computed key that cannot be "stack" reads no stack', () => { + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const spelled: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "strLit", value: "stack", type: STRING }, + }; + const read = (key: IrExpr): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "dynKeyGet", value, key, type: DYN }, + }); + const index: IrExpr = { loc, kind: "varRef", localId: "i", type: F64 }; + const indexText: IrExpr = { loc, kind: "toString", operand: index, type: STRING }; + // A number key, and a key converted from one. + expect(observesStacks(mod([fn("%main", [read(index), spelled])]))).toBe(false); + expect(observesStacks(mod([fn("%main", [read(indexText), spelled])]))).toBe(false); + // An immutable local answers with its initializer (the shape of the + // dispatch helpers' formatted index keys). + const key: IrExpr = { loc, kind: "varRef", localId: "key", type: STRING }; + const local = (init: IrExpr, mutable = false): IrFunction => ({ + ...fn("%main", [{ loc, kind: "varDecl", localId: "key", init }, read(key), spelled]), + locals: [{ id: "key", name: "key", type: STRING, mutable }], + }); + expect(observesStacks(mod([local(indexText)]))).toBe(false); + expect(observesStacks(mod([local(indexText, true)]))).toBe(true); + // A parameter of a function called by name answers with its arguments. + const helper: IrFunction = { + ...fn("readKey", [read(key)]), + params: [{ localId: "key", name: "key", type: STRING }], + locals: [{ id: "key", name: "key", type: STRING, mutable: false }], + }; + const callWith = (arg: IrExpr): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee: "readKey", args: [arg], type: DYN }, + }); + const numbers = mod([fn("%main", [callWith(indexText), spelled]), helper]); + expect(observesStacks(numbers)).toBe(false); + const stack = mod([ + fn("%main", [ + callWith(indexText), + callWith({ loc, kind: "strLit", value: "stack", type: STRING }), + ]), + helper, + ]); + expect(observesStacks(stack)).toBe(true); +}); + +test("a key forwarded through functions with several call sites is searched once", () => { + // f0 calls f1 twice, f1 calls f2 twice, and so on, each passing its + // parameter on: 2^40 call paths reach the read in f40. + const depth = 40; + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const key: IrExpr = { loc, kind: "varRef", localId: "key", type: STRING }; + const forward = (callee: string): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee, args: [key], type: VOID }, + }); + const chain = (root: IrExpr): IrModule => { + const functions: IrFunction[] = []; + for (let i = 0; i <= depth; i++) { + const body: IrStmt[] = + i < depth + ? [forward(`f${i + 1}`), forward(`f${i + 1}`)] + : [{ loc, kind: "exprStmt", expr: { loc, kind: "dynKeyGet", value, key, type: DYN } }]; + functions.push({ + ...fn(`f${i}`, body), + params: [{ localId: "key", name: "key", type: STRING }], + locals: [{ id: "key", name: "key", type: STRING, mutable: false }], + }); + } + const spelled: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "strLit", value: "stack", type: STRING }, + }; + const start: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee: "f0", args: [root], type: VOID }, + }; + return mod([fn("%main", [start, spelled]), ...functions]); + }; + expect(observesStacks(chain({ loc, kind: "strLit", value: "message", type: STRING }))).toBe( + false, + ); + expect(observesStacks(chain({ loc, kind: "strLit", value: "stack", type: STRING }))).toBe(true); +}); + test("programs without stack reads are not", () => { expect(observesStacks(mod([fn("%main", [call("helper")]), fn("helper", [ret])]))).toBe(false); }); diff --git a/packages/compiler/src/backend/stack-observations.ts b/packages/compiler/src/backend/stack-observations.ts index cc6d109e4..fa67c071f 100644 --- a/packages/compiler/src/backend/stack-observations.ts +++ b/packages/compiler/src/backend/stack-observations.ts @@ -3,7 +3,7 @@ * it answers true. */ import { exprFunctionReference, moduleFunctionReferences } from "../ir/function-references.js"; import { MAY_THROW_LIB_FNS } from "../ir/builtin-effects.js"; -import type { IrExpr, IrFunction, IrModule, IrStmt } from "../ir/ir.js"; +import type { IrExpr, IrFunction, IrModule, IrStmt, IrType } from "../ir/ir.js"; import { MAY_THROW_ARR_METHODS } from "../ir/ir.js"; import { everyExprChild, everyStmtChild, everyStmtList } from "../ir/traverse.js"; import { unreachableStatements } from "./may-throw.js"; @@ -13,16 +13,135 @@ import { writesPublished } from "./publication.js"; /** Lib calls that read a captured stack trace. */ const STACK_READS: ReadonlySet = new Set(["error.stack"]); +/** Key types whose property names are never `stack`: a number, bigint or + * boolean key names its decimal or `true`/`false` property. */ +const NON_STACK_KEY_TYPES: ReadonlySet = new Set(["f64", "bigint", "bool"]); + +/** Whether a computed key can be the string "stack", the question a read + * on a checked-dynamic value asks before it counts as a stack read. + * - A string literal answers by its value; a key of a number, bigint or + * boolean type, or one converted from such a value, cannot be "stack". + * - An immutable local answers with its one initializer, and a parameter of + * a function the program only calls by name answers with the arguments + * its call sites pass. + * - Any other key can be "stack" when the program spells the literal + * "stack" somewhere, since the literal can reach the key through fields, + * containers or calls; a key the program assembles from other strings or + * from data it reads keeps the static answer. */ +class StackKeys { + private readonly spelled: boolean; + private readonly calls = new Map(); + private readonly named: Set; + private readonly inits = new Map>(); + /** Settled answers by function and local: true where a search reached a + * key that can be "stack", false where a search that found none passed. */ + private readonly answers = new Map(); + + constructor(mod: IrModule) { + this.spelled = spellsStack(mod); + this.named = new Set(moduleFunctionReferences(mod)); + // Class methods, accessors and constructors are called through dispatch. + const classNames = new Set((mod.classes ?? []).map((cls) => cls.name)); + for (const fn of mod.functions) { + if (isClassMember(fn.name, classNames)) this.named.add(fn.name); + everyStmtList(fn.body, { + stmt: () => true, + expr: (expr) => { + if (expr.kind === "call") { + const sites = this.calls.get(expr.callee) ?? []; + sites.push({ caller: fn, args: expr.args }); + this.calls.set(expr.callee, sites); + } + const referenced = exprFunctionReference(expr); + if (referenced !== undefined) this.named.add(referenced); + return true; + }, + }); + } + } + + mayBeStack(fn: IrFunction, key: IrExpr): boolean { + const visited = new Set(); + const answer = this.keyMayBeStack(fn, key, visited); + // A search that found no way to "stack" explored everything it visited + // to the end, so none of it can be "stack" either. + if (!answer) for (const visit of visited) this.answers.set(visit, false); + return answer; + } + + private keyMayBeStack(fn: IrFunction, key: IrExpr, visited: Set): boolean { + if (key.kind === "strLit") return key.value === "stack"; + if (!this.spelled) return false; + if (NON_STACK_KEY_TYPES.has(key.type.kind)) return false; + if (key.kind === "toString" && NON_STACK_KEY_TYPES.has(key.operand.type.kind)) return false; + if (key.kind === "dynFrom" && NON_STACK_KEY_TYPES.has(key.value.type.kind)) return false; + if (key.kind !== "varRef") return true; + const visit = `${fn.name}\0${key.localId}`; + const known = this.answers.get(visit); + if (known !== undefined) return known; + // Each local is searched once per question: a cycle, or a second path + // to a local already searched, adds no value of its own. + if (visited.has(visit)) return false; + visited.add(visit); + const answer = this.localMayBeStack(fn, key.localId, visited); + if (answer) this.answers.set(visit, true); + return answer; + } + + private localMayBeStack(fn: IrFunction, localId: string, visited: Set): boolean { + const local = fn.locals.find((candidate) => candidate.id === localId); + if (local === undefined || local.mutable) return true; + const index = fn.params.findIndex((param) => param.localId === localId); + if (index !== -1) { + const sites = this.calls.get(fn.name); + if (this.named.has(fn.name) || sites === undefined) return true; + return sites.some( + (site) => + site.args.length !== fn.params.length || + this.keyMayBeStack(site.caller, site.args[index]!, visited), + ); + } + const init = this.initializerOf(fn, localId); + return init === null || this.keyMayBeStack(fn, init, visited); + } + + /** The one initializer of an immutable local, or null when it has none + * or more than one declaration. */ + private initializerOf(fn: IrFunction, localId: string): IrExpr | null { + let inits = this.inits.get(fn); + if (inits === undefined) { + inits = new Map(); + everyStmtList(fn.body, { + stmt: (stmt) => { + if (stmt.kind === "varDecl") + inits!.set(stmt.localId, inits!.has(stmt.localId) ? null : (stmt.init ?? null)); + return true; + }, + expr: () => true, + }); + this.inits.set(fn, inits); + } + return inits.get(localId) ?? null; + } +} + +/** Whether `name` follows the `%.` convention of class + * methods, accessors and constructors for one of `classNames`. */ +function isClassMember(name: string, classNames: ReadonlySet): boolean { + if (!name.startsWith("%")) return false; + for (let dot = name.indexOf("."); dot !== -1; dot = name.indexOf(".", dot + 1)) { + if (classNames.has(name.slice(1, dot))) return true; + } + return false; +} + /** A typed `error.stack` read, or a read on a checked-dynamic value that - * can reach an error's own `stack` accessor: the constant key `stack`, or - * a computed key when `computedKeys` (the program spells "stack", so a - * computed key can name it). A key the program assembles from other - * strings or from data it reads keeps the static answer. */ -function readsStack(expr: IrExpr, computedKeys: boolean): boolean { + * can reach an error's own `stack` accessor: one whose key can be "stack" + * (StackKeys). */ +function readsStack(fn: IrFunction, expr: IrExpr, keys: StackKeys): boolean { if (expr.kind === "libCall") return STACK_READS.has(expr.fn); if (expr.kind !== "dynKeyGet") return false; - if (expr.key.kind === "strLit") return expr.key.value === "stack"; - return computedKeys; + return keys.mayBeStack(fn, expr.key); } /** Whether the module contains the string literal "stack". */ @@ -36,10 +155,10 @@ function spellsStack(mod: IrModule): boolean { ); } -function containsStackRead(fn: IrFunction, computedKeys: boolean): boolean { +function containsStackRead(fn: IrFunction, keys: StackKeys): boolean { return !everyStmtList(fn.body, { stmt: () => true, - expr: (expr) => !readsStack(expr, computedKeys), + expr: (expr) => !readsStack(fn, expr, keys), }); } @@ -65,22 +184,14 @@ function visitLiveExprs(body: IrStmt[], visit: (expr: IrExpr) => void): void { * and those reads must not cost every call of the program a frame. * Programs without any stack read answer false without the walk. */ export function observesStacks(mod: IrModule): boolean { - const computedKeys = spellsStack(mod); - if (!mod.functions.some((fn) => containsStackRead(fn, computedKeys))) return false; + const keys = new StackKeys(mod); + if (!mod.functions.some((fn) => containsStackRead(fn, keys))) return false; const byName = new Map(mod.functions.map((fn) => [fn.name, fn])); const classNames = new Set((mod.classes ?? []).map((cls) => cls.name)); const pending: string[] = [...moduleFunctionReferences(mod)]; // Class methods, accessors and constructors are reachable through // dispatch the IR does not name per site: all of them are roots. - for (const fn of mod.functions) { - if (!fn.name.startsWith("%")) continue; - for (let dot = fn.name.indexOf("."); dot !== -1; dot = fn.name.indexOf(".", dot + 1)) { - if (classNames.has(fn.name.slice(1, dot))) { - pending.push(fn.name); - break; - } - } - } + for (const fn of mod.functions) if (isClassMember(fn.name, classNames)) pending.push(fn.name); const reached = new Set(); let observed = false; while (pending.length > 0 && !observed) { @@ -90,7 +201,7 @@ export function observesStacks(mod: IrModule): boolean { const fn = byName.get(name); if (fn === undefined) continue; visitLiveExprs(fn.body, (expr) => { - if (readsStack(expr, computedKeys)) observed = true; + if (readsStack(fn, expr, keys)) observed = true; if (expr.kind === "call" && !reached.has(expr.callee)) pending.push(expr.callee); const referenced = exprFunctionReference(expr); if (referenced !== undefined && !reached.has(referenced)) pending.push(referenced); diff --git a/packages/compiler/src/backend/warm-cache.ts b/packages/compiler/src/backend/warm-cache.ts index 9a505d517..389fa19e9 100644 --- a/packages/compiler/src/backend/warm-cache.ts +++ b/packages/compiler/src/backend/warm-cache.ts @@ -51,6 +51,7 @@ export async function warmNativeCaches( searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index 5b0982dc1..33da0610f 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -46,6 +46,7 @@ const native: EarlyExecutableNativeFeatures = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index f0be7e11f..064b1f867 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -67,6 +67,7 @@ export interface EarlyExecutableNativeFeatures { dynAsync: boolean; dynTier?: boolean; streamDyn?: boolean; + eventsOnceSignal?: boolean; events: boolean; emitter: boolean; symbol: boolean; @@ -74,6 +75,7 @@ export interface EarlyExecutableNativeFeatures { searchParams: boolean; qs: boolean; dynBuiltinClass: boolean; + dynBuiltinStream: boolean; parseArgs: boolean; stream: boolean; net: boolean; @@ -194,6 +196,7 @@ const BOOLEAN_NATIVE_KEYS = [ "searchParams", "qs", "dynBuiltinClass", + "dynBuiltinStream", "parseArgs", "stream", "net", diff --git a/packages/compiler/src/frontend/cjs-syntax.ts b/packages/compiler/src/frontend/cjs-syntax.ts index 41191432a..99195d994 100644 --- a/packages/compiler/src/frontend/cjs-syntax.ts +++ b/packages/compiler/src/frontend/cjs-syntax.ts @@ -1,4 +1,5 @@ import * as ts from "./ts7/syntax.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; export interface CjsLexedExports { /** Names Node's lexer detects in THIS file (no reexport resolution). */ @@ -30,7 +31,7 @@ function identRunEnd(text: string, pos: number): number { * a `a`-escaped name parses to the same AST as its plain spelling, * but merve reads bytes and stops at the backslash. */ function sourceSpellsIdentifier(node: ts.Node, sf: ts.SourceFile): boolean { - const ch = sf.text[node.getStart(sf)]; + const ch = sf.text[tokenStartOfNode(node)]; return ch !== undefined && isIdentStartChar(ch); } @@ -39,16 +40,16 @@ function sourceSpellsIdentifier(node: ts.Node, sf: ts.SourceFile): boolean { * defineProperty getter's `return IDENT.IDENT` because `this` lexes as an * identifier run — probed), escapes and private names do not. */ function isIdentTokenNode(node: ts.Node, sf: ts.SourceFile): boolean { - const start = node.getStart(sf); + const start = tokenStartOfNode(node); const ch = sf.text[start]; - return ch !== undefined && isIdentStartChar(ch) && identRunEnd(sf.text, start) === node.getEnd(); + return ch !== undefined && isIdentStartChar(ch) && identRunEnd(sf.text, start) === node.end; } /** The identifier run at the node's START — the whole name when its * spelling is plain, the byte prefix merve consumed when a unicode escape * interrupts the run mid-name. */ function identPrefixOf(node: ts.Node, sf: ts.SourceFile): string { - const start = node.getStart(sf); + const start = tokenStartOfNode(node); return sf.text.slice(start, identRunEnd(sf.text, start)); } @@ -84,20 +85,17 @@ export function isModuleExports(e: ts.Expression): boolean { * follows — so the recognizer descends the leftmost-child chain while it * still starts where `e` starts. A ParenthesizedExpression breaks the * chain naturally (its child starts after the `(`), matching merve. */ -function leadingRequireOf( - e: ts.Expression, - sf: ts.SourceFile, -): { spec: string; end: number } | null { - const start = e.getStart(sf); +function leadingRequireOf(e: ts.Expression): { spec: string; end: number } | null { + const start = tokenStartOfNode(e); let cur: ts.Node = e; for (;;) { const spec = bareRequireSpecOf(cur); - if (spec !== null && cur.getStart(sf) === start) return { spec, end: cur.getEnd() }; + if (spec !== null && tokenStartOfNode(cur) === start) return { spec, end: cur.end }; let first: ts.Node | undefined; ts.forEachChild(cur, (c) => { first ??= c; }); - if (first === undefined || first.getStart(sf) !== start) return null; + if (first === undefined || tokenStartOfNode(first) !== start) return null; cur = first; } } @@ -179,13 +177,13 @@ function scanTableLiteral( continue; // whitespace before `,` is skipped on the shorthand path } if (ts.isSpreadAssignment(prop)) { - const req = leadingRequireOf(prop.expression, sf); + const req = leadingRequireOf(prop.expression); let consumedEnd: number; if (req !== null) { reexports.push(req.spec); consumedEnd = req.end; } else if (sourceSpellsIdentifier(prop.expression, sf)) { - consumedEnd = identRunEnd(text, prop.expression.getStart(sf)); + consumedEnd = identRunEnd(text, tokenStartOfNode(prop.expression)); } else { return; // `...{}` and friends stop the scan } @@ -213,7 +211,7 @@ function scanTableLiteral( } if (key === null) return; // numeric / computed / template keys stop the scan const value = prop.initializer; - const req = leadingRequireOf(value, sf); + const req = leadingRequireOf(value); if (req !== null) { // A require VALUE adds the key, records the reexport, and stops // the scan UNCONDITIONALLY — probed: `{ a: require('x'), b: v }` @@ -223,7 +221,7 @@ function scanTableLiteral( reexports.push(req.spec); return; } - const start = value.getStart(sf); + const start = tokenStartOfNode(value); const ch0 = text[start]; if (ch0 === undefined || !isIdentStartChar(ch0)) return; // literal values stop WITHOUT adding the key const consumedEnd = identRunEnd(text, start); @@ -351,8 +349,8 @@ function starExportSpecOf(call: ts.CallExpression, sf: ts.SourceFile): string | if (!sourceSpellsIdentifier(nameNode, sf)) return null; const arg0 = call.arguments.nodes[0]; if (arg0 === undefined) return null; - if (!byteAdjacentParen(sf, nameNode.getEnd(), arg0.getStart(sf))) return null; - return leadingRequireOf(arg0, sf)?.spec ?? null; + if (!byteAdjacentParen(sf, nameNode.end, tokenStartOfNode(arg0))) return null; + return leadingRequireOf(arg0)?.spec ?? null; } /** True when the bytes between a callee name ending at `nameEnd` and its @@ -384,7 +382,7 @@ function starAssignOf(stmt: ts.VariableStatement, sf: ts.SourceFile): [string, s init.arguments.nodes.length >= 1 ) { const arg0 = init.arguments.nodes[0]!; - if (!byteAdjacentParen(sf, init.expression.getEnd(), arg0.getStart(sf))) return null; + if (!byteAdjacentParen(sf, init.expression.end, tokenStartOfNode(arg0))) return null; const spec = bareRequireSpecOf(arg0); if (spec !== null) return [decl.name.text, spec]; } @@ -717,15 +715,16 @@ export function cjsLexedExportsOfFile(sf: ts.SourceFile): CjsLexedExports { ) { exports.add(lhs.argumentExpression.text); } else if (isModuleExports(lhs)) { - events.push({ pos: n.getStart(sf), kind: "assign", rhs: n.right }); + events.push({ pos: tokenStartOfNode(n), kind: "assign", rhs: n.right }); } } else if (ts.isCallExpression(n)) { scanDefineProperty(n, sf, exports); if (braceDepth === 0) { const starSpec = starExportSpecOf(n, sf); - if (starSpec !== null) events.push({ pos: n.getStart(sf), kind: "spec", spec: starSpec }); + if (starSpec !== null) + events.push({ pos: tokenStartOfNode(n), kind: "spec", spec: starSpec }); const loopId = starLoopIdOf(n, sf); - if (loopId !== null) events.push({ pos: n.getStart(sf), kind: "loop", id1: loopId }); + if (loopId !== null) events.push({ pos: tokenStartOfNode(n), kind: "loop", id1: loopId }); } } else if (ts.isVariableStatement(n) && braceDepth === 0) { const assign = starAssignOf(n, sf); @@ -745,7 +744,7 @@ export function cjsLexedExportsOfFile(sf: ts.SourceFile): CjsLexedExports { for (const spec of starAssigns.get(ev.id1) ?? []) reexports.push(spec); } else { reexports = []; // every `module.exports =` clears — stars included (probed) - const req = leadingRequireOf(ev.rhs, sf); + const req = leadingRequireOf(ev.rhs); if (req !== null) { reexports.push(req.spec); } else if (ts.isObjectLiteralExpression(ev.rhs)) { diff --git a/packages/compiler/src/frontend/contract-types.ts b/packages/compiler/src/frontend/contract-types.ts index c30788fa1..4ded2a1a0 100644 --- a/packages/compiler/src/frontend/contract-types.ts +++ b/packages/compiler/src/frontend/contract-types.ts @@ -130,7 +130,7 @@ function markerNamed( function nameText(node: ts.Node | undefined): string { if (node === undefined) return ""; if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node)) return node.text; - return node.getText(); + return ts.getTextOfNode(node); } function contractDiag( diff --git a/packages/compiler/src/frontend/experimental-import.ts b/packages/compiler/src/frontend/experimental-import.ts index 8fc2238bf..3b35a196a 100644 --- a/packages/compiler/src/frontend/experimental-import.ts +++ b/packages/compiler/src/frontend/experimental-import.ts @@ -73,7 +73,7 @@ export function lifetimeRegionMemberOf( if ( !ts.isClassDeclaration(owner) || owner.name?.text !== "LifetimeRegion" || - !isExperimentalDeclarationFile(owner.getSourceFile(), known) + !isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known) ) return null; const name = access.name.text; @@ -105,7 +105,7 @@ export function internTableMemberOf( if ( !ts.isClassDeclaration(owner) || owner.name?.text !== "InternTable" || - !isExperimentalDeclarationFile(owner.getSourceFile(), known) + !isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known) ) return null; const name = access.name.text; @@ -135,7 +135,7 @@ export function threadSlotsMemberOf( if ( !ts.isClassDeclaration(owner) || owner.name?.text !== "ThreadSlots" || - !isExperimentalDeclarationFile(owner.getSourceFile(), known) + !isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known) ) return null; const name = access.name.text; diff --git a/packages/compiler/src/frontend/fork-target.test.ts b/packages/compiler/src/frontend/fork-target.test.ts index 7a3e23c9b..5e56cea5d 100644 --- a/packages/compiler/src/frontend/fork-target.test.ts +++ b/packages/compiler/src/frontend/fork-target.test.ts @@ -7,6 +7,7 @@ import { fallbackDtsPath } from "./dts-paths.js"; import { Ts7Host } from "./ts7/program-host.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js"; +import { getTextOfNode } from "./ts7/node-functions.js"; test("fork discovery batches only candidate bindings and preserves aliases and shadowing", () => { const directory = mkdtempSync(join(tmpdir(), "scriptc-fork-discovery-")); @@ -49,7 +50,7 @@ test("fork discovery batches only candidate bindings and preserves aliases and s ["first.ts", "second.ts", "third.ts"].map((name) => join(directory, name)), ); expect(batch).toHaveBeenCalledTimes(1); - expect(batch.mock.calls[0]![0].map((node) => node.getText())).toEqual([ + expect(batch.mock.calls[0]![0].map((node) => getTextOfNode(node))).toEqual([ "start", "child", "launch", diff --git a/packages/compiler/src/frontend/fork-target.ts b/packages/compiler/src/frontend/fork-target.ts index 592bbb2d6..38c8f5f07 100644 --- a/packages/compiler/src/frontend/fork-target.ts +++ b/packages/compiler/src/frontend/fork-target.ts @@ -12,7 +12,7 @@ export function isNodeGlobal(program: ts.Program, expression: ts.Identifier): bo return ( declarations.length > 0 && declarations.every((declaration) => { - const source = declaration.getSourceFile(); + const source = ts.getSourceFileOfNode(declaration); return ( source.isDeclarationFile && (tsgoPath(source.fileName) === fallbackDtsPath() || isNodeTypesPath(source.fileName)) @@ -61,7 +61,7 @@ export function isBuiltinMemberImport( if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { const target = checker.getAliasedSymbol(symbol); const targetDecl = target.declarations[0]; - if (targetDecl !== undefined && targetDecl.getSourceFile().isDeclarationFile) { + if (targetDecl !== undefined && ts.getSourceFileOfNode(targetDecl).isDeclarationFile) { for ( let parent: ts.Node | undefined = targetDecl.parent; parent !== undefined && !ts.isSourceFile(parent); @@ -69,7 +69,8 @@ export function isBuiltinMemberImport( ) { if (ts.isModuleDeclaration(parent) && ts.isStringLiteral(parent.name)) { return ( - canonicalBuiltinModule(parent.name.text) === moduleName && target.name === memberName + canonicalBuiltinModule(parent.name.text) === moduleName && + ts.symbolName(target) === memberName ); } } @@ -188,7 +189,7 @@ function staticString( ): string | null { const current = strip(expr); if (ts.isStringLiteralLike(current)) return current.text; - if (importMetaUrl(current)) return pathToFileURL(current.getSourceFile().fileName).href; + if (importMetaUrl(current)) return pathToFileURL(ts.getSourceFileOfNode(current).fileName).href; if (ts.isTemplateExpression(current)) { let value = current.head.text; for (const span of current.templateSpans.nodes) { diff --git a/packages/compiler/src/frontend/inferred-js-diagnostics.ts b/packages/compiler/src/frontend/inferred-js-diagnostics.ts index 7095cea5d..b21d19cb0 100644 --- a/packages/compiler/src/frontend/inferred-js-diagnostics.ts +++ b/packages/compiler/src/frontend/inferred-js-diagnostics.ts @@ -16,7 +16,7 @@ export function inferredJsDiagnosticSuppressed( const checker = program.getTypeChecker(); let suppressed = false; ts.walkPreorder(file, (node) => { - if (node.getEnd() < diagnostic.pos || node.getStart(file) > diagnostic.end) return "skip"; + if (node.end < diagnostic.pos || ts.tokenStartOfNode(node) > diagnostic.end) return "skip"; if ( diagnostic.code === 2565 && isJsSourceFileName(file.fileName) && @@ -30,7 +30,7 @@ export function inferredJsDiagnosticSuppressed( ts.isPropertyDeclaration(decl) && !decl.type && !decl.initializer && - !/@type\b/.test(file.text.slice(decl.pos, decl.getStart(file))), + !/@type\b/.test(file.text.slice(decl.pos, ts.tokenStartOfNode(decl))), ) ) { // Bare JS fields exist with value undefined before the first write; @@ -44,14 +44,14 @@ export function inferredJsDiagnosticSuppressed( const declaration = signature && signature.declaration; if ( declaration && - isJsSourceFileName(declaration.getSourceFile().fileName) && + isJsSourceFileName(ts.getSourceFileOfNode(declaration).fileName) && (ts.isFunctionDeclaration(declaration) || ts.isFunctionExpression(declaration) || ts.isMethodDeclaration(declaration)) && node.arguments.nodes.length > declaration.parameters.nodes.length && - node.arguments.nodes[declaration.parameters.nodes.length]?.getStart(file) === + ts.tokenStartOfNode(node.arguments.nodes[declaration.parameters.nodes.length]!) === diagnostic.pos && - node.arguments.nodes[node.arguments.nodes.length - 1]?.getEnd() === diagnostic.end && + node.arguments.nodes[node.arguments.nodes.length - 1]!.end === diagnostic.end && bodyReadsArguments(declaration) ) { suppressed = true; @@ -68,7 +68,7 @@ export function inferredJsDiagnosticSuppressed( const iterable = checker.getTypeAtLocation(node.expression); const iterator = checker .getPropertiesOfType(iterable) - .find((property) => property.name.startsWith("__@iterator@")); + .find((property) => ts.symbolName(property).startsWith("__@iterator@")); if (!iterator) return undefined; const signatures = checker.getCallSignatures( checker.getTypeOfSymbolAtLocation(iterator, node.expression), @@ -88,7 +88,7 @@ export function inferredJsDiagnosticSuppressed( declaration && ts.isMethodDeclaration(declaration) && declaration.type === undefined && - isJsSourceFileName(declaration.getSourceFile().fileName) && + isJsSourceFileName(ts.getSourceFileOfNode(declaration).fileName) && declaration.body?.statements?.nodes.length === 1 && ts.isThrowStatement(declaration.body.statements.nodes[0]!) ) { diff --git a/packages/compiler/src/frontend/lib-contract.ts b/packages/compiler/src/frontend/lib-contract.ts index 0432d06e2..28532ecd8 100644 --- a/packages/compiler/src/frontend/lib-contract.ts +++ b/packages/compiler/src/frontend/lib-contract.ts @@ -115,7 +115,7 @@ const CONTRACT_GLOBAL_TYPES = new Set(["Array", "ReadonlyArray", "Uint8Array"]); type TypeBindings = Map>; function locOf(file: ts.SourceFile, node: ts.Node): SrcLoc { - return { file: file.fileName, start: node.getStart(), end: node.end }; + return { file: file.fileName, start: ts.tokenStartOfNode(node), end: node.end }; } function isExported(stmt: ts.Node): boolean { @@ -248,7 +248,7 @@ function typeShape( const lit = node.literal; if (ts.isStringLiteral(lit)) return { k: "stringLit", text: lit.text }; if (lit.kind === ts.SyntaxKind.NullKeyword) return { k: "absent", unit: "null" }; - return { k: "unsupported", text: node.getText(file) }; + return { k: "unsupported", text: ts.getTextOfNode(node) }; } if (ts.isArrayTypeNode(node)) return { k: "array", elem: typeShape(file, node.elementType, bindings) }; @@ -280,15 +280,19 @@ function typeShape( if (ts.isConditionalTypeNode(node)) { return { k: "unsupported", - text: `a conditional type (${node.getText(file)})`, + text: `a conditional type (${ts.getTextOfNode(node)})`, computed: "conditional", }; } if (ts.isMappedTypeNode(node)) { - return { k: "unsupported", text: `a mapped type (${node.getText(file)})`, computed: "mapped" }; + return { + k: "unsupported", + text: `a mapped type (${ts.getTextOfNode(node)})`, + computed: "mapped", + }; } if (ts.isTypeReferenceNode(node)) { - if (!ts.isIdentifier(node.typeName)) return { k: "unsupported", text: node.getText(file) }; + if (!ts.isIdentifier(node.typeName)) return { k: "unsupported", text: ts.getTextOfNode(node) }; const name = node.typeName.text; const global = CONTRACT_GLOBAL_TYPES.has(name) && isUnshadowedGlobalType(file, node, name, bindings); @@ -302,10 +306,10 @@ function typeShape( return { k: "array", elem: typeShape(file, node.typeArguments.nodes[0]!, bindings) }; } if ((node.typeArguments?.nodes.length ?? 0) > 0) - return { k: "unsupported", text: node.getText(file) }; + return { k: "unsupported", text: ts.getTextOfNode(node) }; return { k: "ref", name }; } - return { k: "unsupported", text: node.getText(file) }; + return { k: "unsupported", text: ts.getTextOfNode(node) }; } /** Unwrap `expr as const` / parenthesized initializers. */ @@ -362,7 +366,7 @@ function envMsgArray(expr: ts.Expression): { env: string; msg: string }[] | null /** One mergeable declaration site, `file:line` (1-based line). */ function siteOf(file: ts.SourceFile, node: ts.Node): string { - return `${file.fileName}:${ts.getLineAndCharacterOfPosition(file, node.getStart()).line + 1}`; + return `${file.fileName}:${ts.getLineAndCharacterOfPosition(file, ts.tokenStartOfNode(node)).line + 1}`; } /** Everything the sidecar projection needs from the program's syntax diff --git a/packages/compiler/src/frontend/lib-exports.ts b/packages/compiler/src/frontend/lib-exports.ts index 738dd2f19..6a42074bd 100644 --- a/packages/compiler/src/frontend/lib-exports.ts +++ b/packages/compiler/src/frontend/lib-exports.ts @@ -35,7 +35,7 @@ export function entryFunctionExports(entry: ts.SourceFile): Map 0, async: mods?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true, generator: stmt.asteriskToken !== undefined, diff --git a/packages/compiler/src/frontend/lowering/assert-instances.ts b/packages/compiler/src/frontend/lowering/assert-instances.ts index ee8a34ea2..1166c6afa 100644 --- a/packages/compiler/src/frontend/lowering/assert-instances.ts +++ b/packages/compiler/src/frontend/lowering/assert-instances.ts @@ -194,7 +194,7 @@ function onlyMethodCallReferences(lowerer: Lowerer, name: ts.Identifier): boolea } ts.forEachChild(node, visit); }; - visit(name.getSourceFile()); + visit(ts.getSourceFileOfNode(name)); return ok; } diff --git a/packages/compiler/src/frontend/lowering/assertion-members.ts b/packages/compiler/src/frontend/lowering/assertion-members.ts index d61c379d6..484963fa8 100644 --- a/packages/compiler/src/frontend/lowering/assertion-members.ts +++ b/packages/compiler/src/frontend/lowering/assertion-members.ts @@ -79,7 +79,7 @@ export function extendAssertedRecordMembers( symbol.declarations.length > 0 && symbol.declarations.every( (d) => - !lowerer.isStdlibFile(d.getSourceFile()) && + !lowerer.isStdlibFile(ts.getSourceFileOfNode(d)) && (ts.isTypeLiteralNode(d) || ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d)), diff --git a/packages/compiler/src/frontend/lowering/binding-analysis.ts b/packages/compiler/src/frontend/lowering/binding-analysis.ts index 88b0f91dc..83b9aa6f9 100644 --- a/packages/compiler/src/frontend/lowering/binding-analysis.ts +++ b/packages/compiler/src/frontend/lowering/binding-analysis.ts @@ -16,7 +16,7 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t let written = false; // Text pre-check keeps the file walk cheap: only same-named // identifiers pay a symbol resolution. - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym; const scanTarget = (t: ts.Expression): void => { @@ -31,7 +31,7 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t if (ts.isArrayLiteralExpression(e) || ts.isObjectLiteralExpression(e)) { const walk = (n: ts.Node): void => { if (namesSym(n)) written = true; - else n.forEachChild(walk); + else ts.forEachChild(n, walk); }; walk(e); } @@ -54,9 +54,9 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t ) { scanTarget(n.initializer as ts.Expression); } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - decl.getSourceFile().forEachChild(visit); + ts.forEachChild(ts.getSourceFileOfNode(decl), visit); lowerer.neverReassignedCache.set(sym, !written); return !written; } @@ -123,7 +123,7 @@ export function nullishValueUnitOf( !decl || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name) || - decl.getSourceFile().isDeclarationFile || + ts.getSourceFileOfNode(decl).isDeclarationFile || decl.initializer === undefined || !ts.isVariableStatement(decl.parent?.parent) ) { @@ -154,7 +154,7 @@ export function nullishValueUnitOf( /** True when any identifier resolving to `sym` sits inside a class * heritage clause anywhere in the program. */ function usedInHeritageClause(lowerer: Lowerer, sym: ts.Symbol): boolean { - const symText = sym.name; + const symText = ts.symbolName(sym); let found = false; const visit = (n: ts.Node): void => { if (found) return; @@ -171,12 +171,12 @@ function usedInHeritageClause(lowerer: Lowerer, sym: ts.Symbol): boolean { } return; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; for (const file of lowerer.program.getImplementationSourceFiles()) { if (found) break; if (file.isDeclarationFile) continue; - file.forEachChild(visit); + ts.forEachChild(file, visit); } return found; } @@ -236,7 +236,7 @@ export function nullishGenericBindingUnitOf( * value knowable. Compound assignments, ++/--, for-in/of cursors, and * destructuring targets all disqualify. */ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): boolean { - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym; let ok = true; @@ -253,7 +253,7 @@ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): bool } else if (ts.isArrayLiteralExpression(lhs) || ts.isObjectLiteralExpression(lhs)) { const walk = (m: ts.Node): void => { if (namesSym(m)) ok = false; - else m.forEachChild(walk); + else ts.forEachChild(m, walk); }; walk(lhs); } @@ -274,9 +274,9 @@ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): bool t = (t as ts.ParenthesizedExpression).expression; if (namesSym(t)) ok = false; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - decl.getSourceFile().forEachChild(visit); + ts.forEachChild(ts.getSourceFileOfNode(decl), visit); return ok; } @@ -332,7 +332,7 @@ export function deadUnmappableBinding( // the no-storage family never claims it (catch bindings sit outside a // variable statement too and stay out the same way). if (!ts.isVariableStatement(decl.parent?.parent)) return false; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (sf.isDeclarationFile || isJsSourceFile(sf)) return false; if (decl.initializer !== undefined && !sideEffectFreeValueExpr(lowerer, decl.initializer)) return false; @@ -344,7 +344,7 @@ export function deadUnmappableBinding( // bug), so only survivors of the syntactic filters pay it. Mappable // types keep their real storage. if (lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(sym)) !== null) return false; - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym; let dead = true; @@ -353,7 +353,7 @@ export function deadUnmappableBinding( if (ts.isIdentifier(n) && n.text === symText) { // Declaration-name occurrences are not reads. if (n.parent !== undefined && ts.isVariableDeclaration(n.parent) && n.parent.name === n) { - n.forEachChild(visit); + ts.forEachChild(n, visit); return; } // A plain-assignment LHS is a WRITE — dead only when the RHS @@ -375,12 +375,12 @@ export function deadUnmappableBinding( if (namesSym(n)) dead = false; return; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; for (const file of lowerer.program.getImplementationSourceFiles()) { if (!dead) break; if (file.isDeclarationFile) continue; - file.forEachChild(visit); + ts.forEachChild(file, visit); } if (dead) lowerer.deadBindings.add(sym); return dead; diff --git a/packages/compiler/src/frontend/lowering/builtin-function-fences.ts b/packages/compiler/src/frontend/lowering/builtin-function-fences.ts index e6221384b..312596c1f 100644 --- a/packages/compiler/src/frontend/lowering/builtin-function-fences.ts +++ b/packages/compiler/src/frontend/lowering/builtin-function-fences.ts @@ -25,7 +25,7 @@ export function builtinFunctionFenceValue( `library functions as values ('${display}' has no function-value lowering; call it directly)`, ); lowerer.runtimeFences.push(diag); - const line = ts.getLineAndCharacterOfPosition(node.getSourceFile(), loc.start).line + 1; + const line = ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(node), loc.start).line + 1; lowerer.liftedFns.push({ name, params: [], diff --git a/packages/compiler/src/frontend/lowering/builtins/async-context.ts b/packages/compiler/src/frontend/lowering/builtins/async-context.ts index e6de99b09..18bd93a86 100644 --- a/packages/compiler/src/frontend/lowering/builtins/async-context.ts +++ b/packages/compiler/src/frontend/lowering/builtins/async-context.ts @@ -35,7 +35,7 @@ export function lowerDiagnosticsSubscriber(lowerer: Lowerer, node: ts.Expression if (ts.isIdentifier(node) && node.text === "setImmediate") { const sym = lowerer.checker.getSymbolAtLocation(node); const decls = sym ? sym.declarations : []; - if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(d.getSourceFile()))) { + if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d)))) { return { kind: "libCall", fn: "timers.setImmediateFnValue", diff --git a/packages/compiler/src/frontend/lowering/builtins/blob.ts b/packages/compiler/src/frontend/lowering/builtins/blob.ts index 3fc4ec173..94c8c7ffe 100644 --- a/packages/compiler/src/frontend/lowering/builtins/blob.ts +++ b/packages/compiler/src/frontend/lowering/builtins/blob.ts @@ -52,10 +52,10 @@ export function blobClassOf(lowerer: Lowerer, node: ts.Expression): BlobClass | function blobReceiverClass(lowerer: Lowerer, node: ts.Expression): BlobClass | null { const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node)); const sym = type.getAliasSymbol() ?? type.getSymbol(); - if (!sym || (sym.name !== "Blob" && sym.name !== "File") || !lowerer.isStdlibSymbol(sym)) { - return null; - } - return sym.name; + if (!sym) return null; + const name = ts.symbolName(sym); + if ((name !== "Blob" && name !== "File") || !lowerer.isStdlibSymbol(sym)) return null; + return name; } function memberNameOf(access: ts.PropertyAccessExpression | ts.ElementAccessExpression) { diff --git a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts index ac73924ad..eaa08c9dd 100644 --- a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts +++ b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts @@ -111,8 +111,18 @@ export function builtinClassDynValueOf(lowerer: Lowerer, node: ts.Expression): I }; } +/** EventEmitter and the legacy Stream are dyn.builtinClass; the stream + * classes, which carry native stream state for constructor functions, are + * dyn.builtinStreamClass, so only programs that use them link the stream + * half. */ export function builtinClassValue(kind: number, loc: SrcLoc): IrExpr { - return { kind: "libCall", fn: "dyn.builtinClass", args: [numLit(kind, loc)], type: DYN, loc }; + return { + kind: "libCall", + fn: kind <= 1 ? "dyn.builtinClass" : "dyn.builtinStreamClass", + args: [numLit(kind, loc)], + type: DYN, + loc, + }; } /** `Base.call(this, …)` / `Base.apply(this, args)` on one of those class @@ -130,7 +140,7 @@ export function lowerBuiltinClassCall(lowerer: Lowerer, call: ts.CallExpression) kind: "dynInvoke", recv: receiver, method, - calleeName: callee.getText(), + calleeName: ts.getTextOfNode(callee), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts b/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts index afb221d04..661fec528 100644 --- a/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts +++ b/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts @@ -50,7 +50,7 @@ export function registerBuiltinCallableAlias( // These checked native functions retain a runtime slot in JavaScript so // aliases can escape without narrowing their arguments to string. if ( - isJsSourceFile(nameNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(nameNode)) && target.module === "util" && ["stripVTControlCharacters", "toUSVString", "isDeepStrictEqual", "styleText"].includes( target.member, diff --git a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts index e1a25d169..af7270ea9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts +++ b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts @@ -122,12 +122,14 @@ export function lowerChildMethodCall( } const listenerNode = stripTypeCasts(call.arguments.nodes[1]!); if ( - isJsSourceFile(listenerNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && (ts.isArrowFunction(listenerNode) || ts.isFunctionExpression(listenerNode)) && listenerNode.parameters.nodes.some((param) => param.dotDotDotToken) && listenerNode.parameters.nodes.every((param) => !param.type) && !/@(?:param|type)\b/.test( - listenerNode.getSourceFile().text.slice(listenerNode.pos, listenerNode.getStart()), + ts + .getSourceFileOfNode(listenerNode) + .text.slice(listenerNode.pos, ts.tokenStartOfNode(listenerNode)), ) ) { for (const param of listenerNode.parameters.nodes) lowerer.checkedCallbackParams.add(param); @@ -142,10 +144,10 @@ export function lowerChildMethodCall( : lowerer.lowerExpr(call.arguments.nodes[1]!); if ( narrowed.length > 0 || - (isJsSourceFile(listenerNode.getSourceFile()) && cb.type.kind === "dyn") || + (isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && cb.type.kind === "dyn") || (cb.type.kind === "func" && (cb.type.rest || - (isJsSourceFile(listenerNode.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && cb.type.params.some((param) => param.kind === "dyn")))) ) { if ( @@ -756,7 +758,7 @@ function childStreamEncodedAt( * admit null. */ function narrowedExitListenerParams(lowerer: Lowerer, listener: ts.Expression): ts.Symbol[] { if (!ts.isArrowFunction(listener) && !ts.isFunctionExpression(listener)) return []; - if (isJsSourceFile(listener.getSourceFile())) return []; + if (isJsSourceFile(ts.getSourceFileOfNode(listener))) return []; const symbols: ts.Symbol[] = []; const wanted: readonly string[] = ["f64", "string"]; listener.parameters.nodes.slice(0, 2).forEach((param, i) => { diff --git a/packages/compiler/src/frontend/lowering/builtins/class-values.ts b/packages/compiler/src/frontend/lowering/builtins/class-values.ts index 416217716..4d073c264 100644 --- a/packages/compiler/src/frontend/lowering/builtins/class-values.ts +++ b/packages/compiler/src/frontend/lowering/builtins/class-values.ts @@ -97,7 +97,7 @@ export function builtinClassAliasOwnsNoSlot(lowerer: Lowerer, symbol: ts.Symbol) if (decl === undefined || !ts.isVariableDeclaration(decl)) return false; if (builtinMemberRequireDecl(decl.name, decl.initializer)) return false; if (streamClassAliasDecl(lowerer, decl.name, decl.initializer)) return false; - if (!isJsSourceFile(decl.getSourceFile())) return true; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) return true; let init = decl.initializer; while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression; if (init === undefined) return true; @@ -157,9 +157,10 @@ export function runtimeClassValueOfType(lowerer: Lowerer, type: ts.Type): string const symbol = type.getSymbol(); if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; let info = lowerer.builtinEmitterInfoOf(symbol) ?? lowerer.builtinStreamInfoOf(symbol); - if (!info && symbol.name.endsWith("Constructor")) { + if (!info && ts.symbolName(symbol).endsWith("Constructor")) { for (const [irName, rec] of RUNTIME_ERROR_CLASSES) { - if (`${rec.lib}Constructor` === symbol.name) info = lowerer.classes.get(irName) ?? null; + if (`${rec.lib}Constructor` === ts.symbolName(symbol)) + info = lowerer.classes.get(irName) ?? null; } } return info?.valueCtorParams !== undefined ? info.def.name : null; diff --git a/packages/compiler/src/frontend/lowering/builtins/compression.ts b/packages/compiler/src/frontend/lowering/builtins/compression.ts index e69011522..717e09933 100644 --- a/packages/compiler/src/frontend/lowering/builtins/compression.ts +++ b/packages/compiler/src/frontend/lowering/builtins/compression.ts @@ -136,7 +136,7 @@ export function zlibStreamModeOf( if (alias !== null) return alias.kind === "zlib" ? alias.mode : null; const target = symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol; - const mode = ZLIB_STREAM_MODES.get(target.name); + const mode = ZLIB_STREAM_MODES.get(ts.symbolName(target)); if (mode === undefined) return null; return target.declarations.some((d) => ts.isClassDeclaration(d) && isZlibFallbackDecl(lowerer, d)) ? mode @@ -144,7 +144,7 @@ export function zlibStreamModeOf( } function isZlibFallbackDecl(lowerer: Lowerer, d: ts.Node): boolean { - const file = d.getSourceFile(); + const file = ts.getSourceFileOfNode(d); if (!lowerer.isStdlibFile(file) || isNodeTypesPath(file.fileName)) return false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { diff --git a/packages/compiler/src/frontend/lowering/builtins/constants.ts b/packages/compiler/src/frontend/lowering/builtins/constants.ts index 611805d94..35f0fc095 100644 --- a/packages/compiler/src/frontend/lowering/builtins/constants.ts +++ b/packages/compiler/src/frontend/lowering/builtins/constants.ts @@ -170,7 +170,7 @@ export function fsConstantMemberRead(expr: ts.PropertyAccessExpression): IrExpr type: DYN, loc, }; - return isJsSourceFile(expr.getSourceFile()) + return isJsSourceFile(ts.getSourceFileOfNode(expr)) ? read : { kind: "dynCheck", value: read, type: F64, loc }; } diff --git a/packages/compiler/src/frontend/lowering/builtins/crypto.ts b/packages/compiler/src/frontend/lowering/builtins/crypto.ts index cd490bc87..e6834abd4 100644 --- a/packages/compiler/src/frontend/lowering/builtins/crypto.ts +++ b/packages/compiler/src/frontend/lowering/builtins/crypto.ts @@ -240,7 +240,9 @@ function cryptoKdfInput(lowerer: Lowerer, node: ts.Expression): IrExpr { const symbol = lowerer.typeOf(node).getSymbol(); if ( type?.kind === "bytes" || - (symbol?.name === "ArrayBuffer" && lowerer.isStdlibSymbol(symbol)) + (symbol !== undefined && + ts.symbolName(symbol) === "ArrayBuffer" && + lowerer.isStdlibSymbol(symbol)) ) { const value = lowerer.coerceInto(node, lowerer.lowerExpr(node), DYN); return { kind: "libCall", fn: "bytes.bufferSource", args: [value], type: BYTES_U8, loc }; @@ -878,8 +880,8 @@ export function isCipherType(lowerer: Lowerer, type: ts.Type): boolean { const symbol = type.getAliasSymbol() ?? type.getSymbol(); return ( !!symbol && - CIPHER_TYPE_NAMES.has(symbol.name) && - symbol.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile())) + CIPHER_TYPE_NAMES.has(ts.symbolName(symbol)) && + symbol.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) ); } @@ -898,7 +900,7 @@ function v8CalleeText(node: ts.Expression): string { if (ts.isPropertyAccessExpression(node)) return `${v8CalleeText(node.expression)}.${node.name.text}`; if (ts.isCallExpression(node)) return `${v8CalleeText(node.expression)}(...)`; - return node.getText(); + return ts.getTextOfNode(node); } /** Calls on native Cipheriv/Decipheriv handles dispatch through the @@ -959,7 +961,7 @@ export function lowerCryptoHashMethodCall( return { kind: "seqExpr", stmts: evaluated, - result: nodeThrowExpr(1, "", `${access.getText()} is not a function`, DYN, loc), + result: nodeThrowExpr(1, "", `${ts.getTextOfNode(access)} is not a function`, DYN, loc), type: DYN, loc, }; diff --git a/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts b/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts index fb1c92257..1b9a9c03a 100644 --- a/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts +++ b/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts @@ -9,7 +9,12 @@ import { varRef } from "../../../ir/build.js"; function isStdlibDisposableStack(lowerer: Lowerer, node: ts.Expression): boolean { const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node)); for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) { - if (symbol?.name === "DisposableStack" && lowerer.isStdlibSymbol(symbol)) return true; + if ( + symbol !== undefined && + ts.symbolName(symbol) === "DisposableStack" && + lowerer.isStdlibSymbol(symbol) + ) + return true; } return false; } diff --git a/packages/compiler/src/frontend/lowering/builtins/errors.ts b/packages/compiler/src/frontend/lowering/builtins/errors.ts index 439c9fca3..d95442829 100644 --- a/packages/compiler/src/frontend/lowering/builtins/errors.ts +++ b/packages/compiler/src/frontend/lowering/builtins/errors.ts @@ -27,9 +27,10 @@ const ASSERTION_ERROR_PROPERTIES: ReadonlySet = new Set([ export function isExecExceptionType(lowerer: Lowerer, t: ts.Type): boolean { const sym = t.getSymbol(); return ( - sym?.name === "ExecException" && + sym !== undefined && + ts.symbolName(sym) === "ExecException" && sym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts index 3fbea04aa..49875e004 100644 --- a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts +++ b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts @@ -250,7 +250,7 @@ export function lowerFsLadderCall( ); } if (bi.module !== "fs" && bi.module !== "fs/promises") return null; - if (!isJsSourceFile(expr.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; const args = expr.arguments.nodes; if (args.some(ts.isSpreadElement)) return null; // Every ladder argument crosses as a dyn value; an argument that @@ -1564,7 +1564,7 @@ export function lowerDirentMethodCall( if (call.questionDotToken || access.questionDotToken) return null; if (!lowerer.isStdlibMember(access)) return null; const recvSym = lowerer.typeOf(access.expression).getSymbol(); - if (recvSym?.name !== "Dirent") return null; + if (recvSym === undefined || ts.symbolName(recvSym) !== "Dirent") return null; const receiver = lowerer.lowerExpr(access.expression); if (receiver.type.kind !== "record") return null; const shape = lowerer.shapes.get(receiver.type.shapeId); diff --git a/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts b/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts index 22e524690..498f9b6d4 100644 --- a/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts +++ b/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts @@ -19,7 +19,7 @@ import { } from "../../../ir/ir.js"; function isFsFallbackDecl(lowerer: Lowerer, d: ts.Node): boolean { - const file = d.getSourceFile(); + const file = ts.getSourceFileOfNode(d); if (!lowerer.isStdlibFile(file) || isNodeTypesPath(file.fileName)) return false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { @@ -153,11 +153,13 @@ export function fsStreamClassOf( lowerer: Lowerer, symbol: ts.Symbol | null | undefined, ): "ReadStream" | "WriteStream" | null { - if (!symbol || (symbol.name !== "ReadStream" && symbol.name !== "WriteStream")) return null; + if (!symbol) return null; + const name = ts.symbolName(symbol); + if (name !== "ReadStream" && name !== "WriteStream") return null; const declared = symbol.declarations.some( (d) => ts.isClassDeclaration(d) && isFsFallbackDecl(lowerer, d), ); - return declared ? symbol.name : null; + return declared ? name : null; } /** `new fs.ReadStream(path, options)` / `new fs.WriteStream(...)`: the diff --git a/packages/compiler/src/frontend/lowering/builtins/json.ts b/packages/compiler/src/frontend/lowering/builtins/json.ts index b48030ba5..54bcae4b5 100644 --- a/packages/compiler/src/frontend/lowering/builtins/json.ts +++ b/packages/compiler/src/frontend/lowering/builtins/json.ts @@ -504,7 +504,7 @@ function jsonNullishArgument(lowerer: Lowerer, node: ts.Expression): boolean { const symbol = lowerer.checker.getSymbolAtLocation(node); return ( symbol !== undefined && - symbol.declarations.every((decl) => lowerer.isStdlibFile(decl.getSourceFile())) + symbol.declarations.every((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/key-object.ts b/packages/compiler/src/frontend/lowering/builtins/key-object.ts index ff6c0b38b..7e680fa20 100644 --- a/packages/compiler/src/frontend/lowering/builtins/key-object.ts +++ b/packages/compiler/src/frontend/lowering/builtins/key-object.ts @@ -23,7 +23,7 @@ export function isKeyObjectClass(lowerer: Lowerer, node: ts.Expression): boolean export function isKeyObjectValue(lowerer: Lowerer, node: ts.Expression): boolean { const type = lowerer.typeOf(node); const sym = type.getAliasSymbol() ?? type.getSymbol(); - return !!sym && sym.name === "KeyObject" && lowerer.isStdlibSymbol(sym); + return !!sym && ts.symbolName(sym) === "KeyObject" && lowerer.isStdlibSymbol(sym); } /** `v instanceof KeyObject`. */ @@ -51,7 +51,7 @@ export function lowerKeyObjectCall(lowerer: Lowerer, call: ts.CallExpression): I kind: "dynInvoke", recv: lowerer.lowerExprExpecting(access.expression, DYN), method: access.name.text, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc: locOf(call), diff --git a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts index 8a5365d18..5465aeafa 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts @@ -135,7 +135,7 @@ function builtinModuleBindingOf( if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { const target = lowerer.checker.getAliasedSymbol(symbol); const tdecl = target.declarations[0]; - if (tdecl !== undefined && tdecl.getSourceFile().isDeclarationFile) { + if (tdecl !== undefined && ts.getSourceFileOfNode(tdecl).isDeclarationFile) { for ( let p: ts.Node | undefined = tdecl.parent; p !== undefined && !ts.isSourceFile(p); @@ -143,7 +143,7 @@ function builtinModuleBindingOf( ) { if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) { const module = canonicalBuiltinModule(p.name.text); - if (module !== null) return { module, member: target.name }; + if (module !== null) return { module, member: ts.symbolName(target) }; break; } } @@ -297,14 +297,14 @@ export function createRequireCalleeFileOf( ): ts.SourceFile | null { const e = stripTypeCasts(callee); if (ts.isCallExpression(e)) { - return createRequireBaseCallOf(lowerer, e) !== null ? e.getSourceFile() : null; + return createRequireBaseCallOf(lowerer, e) !== null ? ts.getSourceFileOfNode(e) : null; } if (!ts.isIdentifier(e)) return null; const symbol = lowerer.checker.getSymbolAtLocation(e); const decl = symbol ? symbol.declarations[0] : undefined; if (!decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined) return null; return createRequireBindingDecl(lowerer, decl.name, decl.initializer) - ? decl.getSourceFile() + ? ts.getSourceFileOfNode(decl) : null; } diff --git a/packages/compiler/src/frontend/lowering/builtins/module-calls.ts b/packages/compiler/src/frontend/lowering/builtins/module-calls.ts index 895c1a9d6..c85f2c777 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-calls.ts @@ -57,7 +57,7 @@ export function lowerBuiltinModuleCall( fn: BuiltinModuleFn, loc: SrcLoc, ): IrExpr { - const name = expr.expression.getText(); + const name = ts.getTextOfNode(expr.expression); if (bi.module === "util/types") { return lowerUtilTypeCall(lowerer, expr, bi, loc, name); } diff --git a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts index afa9dc1ea..6b2e9e589 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts @@ -292,7 +292,7 @@ export function lowerImportMetaResolveCall( ); } const result = resolveImportMetaRuntime( - call.getSourceFile().fileName, + ts.getSourceFileOfNode(call).fileName, specifier, lowerer.targetPlatform, lowerer.frontendServices, @@ -314,8 +314,8 @@ function requireResolverBaseFile(lowerer: Lowerer, receiver: ts.Expression): ts. const created = createRequireCalleeFileOf(lowerer, receiver); if (created !== null) return created; return lowerer.isStdlibGlobal(receiver, "require") && - !isNodeEsmFile(receiver.getSourceFile(), lowerer.program) - ? receiver.getSourceFile() + !isNodeEsmFile(ts.getSourceFileOfNode(receiver), lowerer.program) + ? ts.getSourceFileOfNode(receiver) : null; } diff --git a/packages/compiler/src/frontend/lowering/builtins/navigator.ts b/packages/compiler/src/frontend/lowering/builtins/navigator.ts index 637edaf3c..f90448c1e 100644 --- a/packages/compiler/src/frontend/lowering/builtins/navigator.ts +++ b/packages/compiler/src/frontend/lowering/builtins/navigator.ts @@ -20,7 +20,8 @@ export function isNavigatorGlobal(lowerer: Lowerer, node: ts.Expression): boolea if (!ts.isIdentifier(expr) || expr.text !== "navigator") return false; if (lowerer.isStdlibGlobal(expr, "navigator")) return true; return ( - isJsSourceFile(expr.getSourceFile()) && lowerer.checker.getSymbolAtLocation(expr) === undefined + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + lowerer.checker.getSymbolAtLocation(expr) === undefined ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/net-address.ts b/packages/compiler/src/frontend/lowering/builtins/net-address.ts index d99d63fd3..9e795bca6 100644 --- a/packages/compiler/src/frontend/lowering/builtins/net-address.ts +++ b/packages/compiler/src/frontend/lowering/builtins/net-address.ts @@ -31,8 +31,10 @@ export function netAddressClassOf(lowerer: Lowerer, node: ts.Expression): NetAdd function netAddressValueClass(lowerer: Lowerer, node: ts.Expression): NetAddressClass | null { const type = lowerer.typeOf(node); const sym = type.getAliasSymbol() ?? type.getSymbol(); - if (!sym || (sym.name !== "SocketAddress" && sym.name !== "BlockList")) return null; - return lowerer.isStdlibSymbol(sym) ? sym.name : null; + if (!sym) return null; + const name = ts.symbolName(sym); + if (name !== "SocketAddress" && name !== "BlockList") return null; + return lowerer.isStdlibSymbol(sym) ? name : null; } /** `new SocketAddress(options?)` and `new BlockList()`: the argument @@ -127,7 +129,7 @@ export function lowerNetAddressCall(lowerer: Lowerer, call: ts.CallExpression): kind: "dynInvoke", recv: lowerer.lowerExprExpecting(access.expression, DYN), method: name, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/builtins/performance.ts b/packages/compiler/src/frontend/lowering/builtins/performance.ts index 7aac3b80c..72304e6a9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/performance.ts +++ b/packages/compiler/src/frontend/lowering/builtins/performance.ts @@ -285,7 +285,7 @@ function lowerPerfArguments(lowerer: Lowerer, call: ts.CallExpression, loc: SrcL }); continue; } - const spelling = arg.expression.getText(); + const spelling = ts.getTextOfNode(arg.expression); const items = checkedIterableSpread(lowerer, lowerer.lowerExpr(arg.expression), spelling, loc); stmts.push({ kind: "exprStmt", @@ -527,7 +527,11 @@ export function fencePerfEntryKeyWalk( * receiver's checker type. Its value is the runtime histogram handle. */ function isIntervalHistogram(lowerer: Lowerer, node: ts.Expression): boolean { const symbol = lowerer.typeOf(node).getSymbol(); - return symbol?.name === "IntervalHistogram" && lowerer.isStdlibSymbol(symbol); + return ( + symbol !== undefined && + ts.symbolName(symbol) === "IntervalHistogram" && + lowerer.isStdlibSymbol(symbol) + ); } /** IntervalHistogram's numeric members, as perf.eldStat selectors. */ diff --git a/packages/compiler/src/frontend/lowering/builtins/process.ts b/packages/compiler/src/frontend/lowering/builtins/process.ts index 9ac1fc059..78dc6ef14 100644 --- a/packages/compiler/src/frontend/lowering/builtins/process.ts +++ b/packages/compiler/src/frontend/lowering/builtins/process.ts @@ -192,14 +192,14 @@ export function lowerProcStreamMethodCall( if (!lowerer.isStdlibMember(access)) return null; const name = access.name.text; const loc = locOf(call); - if (isJsSourceFile(call.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(call))) { const receiver = lowerer.lowerExpr(access.expression); if (receiver.type.kind === "dyn") { return { kind: "dynInvoke", recv: receiver, method: name, - calleeName: call.expression.getText(), + calleeName: ts.getTextOfNode(call.expression), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -214,7 +214,7 @@ export function lowerProcStreamMethodCall( kind: "dynInvoke", recv: receiver, method: name, - calleeName: call.expression.getText(), + calleeName: ts.getTextOfNode(call.expression), args: [], type: DYN, loc, @@ -276,7 +276,7 @@ export function lowerProcessStreamProperty( ? lowerer.stdlibGlobalMember(expr.expression, "process") : null; if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (direct === "stdin" || direct === "stdout" || direct === "stderr" || @@ -584,7 +584,7 @@ export function lowerProcessProperty( } // A stored process.nextTick in JavaScript (`cond ? setImmediate : // process.nextTick`): the interned builtin function value. - if (member === "nextTick" && isJsSourceFile(expr.getSourceFile())) { + if (member === "nextTick" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { return builtinFunctionValue("process.nextTick", loc); } if (member === "getBuiltinModule") { @@ -667,7 +667,7 @@ export function lowerProcessProperty( // OUTER expressions dispatch first. if ( member === "stdin" || - ((member === "stdout" || member === "stderr") && isJsSourceFile(expr.getSourceFile())) + ((member === "stdout" || member === "stderr") && isJsSourceFile(ts.getSourceFileOfNode(expr))) ) { return { kind: "libCall", @@ -697,7 +697,10 @@ export function lowerProcessMethodCall( access: ts.PropertyAccessExpression, ): IrExpr | null { if (call.questionDotToken) return null; - if (isJsSourceFile(call.getSourceFile()) && ts.isPropertyAccessExpression(access.expression)) { + if ( + isJsSourceFile(ts.getSourceFileOfNode(call)) && + ts.isPropertyAccessExpression(access.expression) + ) { const stream = lowerer.stdlibGlobalMember(access.expression, "process"); if (stream === "stdin" || stream === "stdout" || stream === "stderr") { if (call.arguments.nodes.some(ts.isSpreadElement)) @@ -706,7 +709,7 @@ export function lowerProcessMethodCall( kind: "dynInvoke", recv: lowerer.lowerExpr(access.expression), method: access.name.text, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc: locOf(call), @@ -883,7 +886,7 @@ export function lowerProcessMethodCall( loc, }, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [ lowerer.lowerExprExpecting(call.arguments.nodes[0]!, DYN), lowerer.lowerExprExpecting(call.arguments.nodes[1]!, DYN), @@ -1080,7 +1083,7 @@ export function lowerProcessMethodCall( // `writeOut: (str) => process.stdout.write(str)` — str untyped): // a runtime string writes; anything else throws the dynCheck's // honest TypeError at the call. - if (data.type.kind === "dyn" && isJsSourceFile(call.getSourceFile())) { + if (data.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(call))) { data = { kind: "dynCheck", value: data, type: STRING, loc }; } const isBytes = data.type.kind === "bytes" && data.type.elem === "u8"; @@ -1095,7 +1098,7 @@ export function lowerProcessMethodCall( let callback: IrExpr | undefined; if (callbackNode) { callback = lowerer.lowerExpr(callbackNode); - if (callback.type.kind === "dyn" && isJsSourceFile(call.getSourceFile())) { + if (callback.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(call))) { callback = { kind: "dynCheck", value: callback, diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 55290f720..668ef01f8 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -6,6 +6,7 @@ import { varRef } from "../../../ir/build.js"; import { maybePromiseResolve, maybePromiseUnionOf } from "../maybe-promise.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { + BOOL, DYN, type IrExpr, type IrFunction, @@ -187,7 +188,11 @@ function lowerTuplePromiseAll( * type is the checker's combined promise, and each entry's inner type * must be that inner type, one of its union arms, or a sub-union of it * (the backend's interned adapters wrap/re-tag fulfillments; a wider - * entry would need machinery that doesn't exist and fences). + * entry would need machinery that doesn't exist and fences). A + * Promise entry fits any result, and an entry whose payload widens + * into the result's converts. When the checker's combined type has no + * representation, the result takes the type the entries that can fulfill + * share, or 'unknown'. * Promise.all/allSettled/any fence with the sequential-await hint; * resolve/reject and the rest fall to the member fence. Null for * non-Promise receivers. */ @@ -214,14 +219,47 @@ export function lowerPromiseStaticCall( "a non-empty array LITERAL of promises is the supported form: Promise.race([p, q])", ); } - const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); - if (resultT?.kind !== "promise") { - lowerer.noLowering( - "Promise.race with this combined result type", - call, - "the entries' value types must combine into a representable union", - ); + // A Promise entry (the deadline idiom: `new Promise((_, + // reject) => setTimeout(reject, ms))`) can only reject, so it races + // under any result type; its inner type maps to void. + const neverEntry = (el: ts.Expression): boolean => { + const t = lowerer.typeOf(el); + const arg = + ts.symbolNameOf(t.getSymbol()) === "Promise" + ? lowerer.checker.getTypeArguments(t as ts.TypeReference)[0] + : undefined; + return arg !== undefined && (arg.flags & ts.TypeFlags.Never) !== 0; + }; + const elements = argNode.elements.nodes; + let combined = lowerer.mapTypeOf(lowerer.typeOf(call)); + let lowered: IrExpr[] | null = null; + if (combined?.kind !== "promise") { + // The checker's combined type has no representation (an 'any' + // element array such as events.once's any[], or 'void | any[]'): the + // result takes the one type the entries that can fulfill share, or + // else 'unknown' when each of them widens into it. + lowered = elements.map((el) => lowerer.lowerExpr(el)); + const fulfilling = lowered.filter((_, i) => !neverEntry(elements[i]!)); + const first = fulfilling[0]?.type; + const dynResult: IrType & { kind: "promise" } = { kind: "promise", inner: DYN }; + if (first?.kind === "promise" && fulfilling.every((e) => typeEquals(e.type, first))) { + combined = first; + } else if ( + fulfilling.length > 0 && + fulfilling.every( + (e) => e.type.kind === "promise" && lowerer.promisePayloadWidens(e.type, dynResult), + ) + ) { + combined = dynResult; + } else { + lowerer.noLowering( + "Promise.race with this combined result type", + call, + "the entries' value types must combine into a representable union", + ); + } } + const resultT = combined; const resultArms = resultT.inner.kind === "union" ? (lowerer.unions.get(resultT.inner.unionId)?.arms ?? []) @@ -238,19 +276,8 @@ export function lowerPromiseStaticCall( } return resultArms.some((a) => typeEquals(a, inner)); }; - // A Promise entry (the deadline idiom: `new Promise((_, - // reject) => setTimeout(reject, ms))`) can only reject, so it races - // under any result type; its inner type maps to void. - const neverEntry = (el: ts.Expression): boolean => { - const t = lowerer.typeOf(el); - const arg = - t.getSymbol()?.name === "Promise" - ? lowerer.checker.getTypeArguments(t as ts.TypeReference)[0] - : undefined; - return arg !== undefined && (arg.flags & ts.TypeFlags.Never) !== 0; - }; - const entries = argNode.elements.nodes.map((el) => { - const entry = lowerer.lowerExpr(el); + const entries = elements.map((el, i) => { + const entry = lowered?.[i] ?? lowerer.lowerExpr(el); if (entry.type.kind !== "promise") { lowerer.noLowering( "Promise.race over non-promise entries", @@ -259,6 +286,10 @@ export function lowerPromiseStaticCall( ); } if (!compatible(entry.type.inner) && !(entry.type.inner.kind === "void" && neverEntry(el))) { + // A payload that widens into the result's (into 'unknown', or a + // checked conversion) keeps its promise and settlement turn. + if (lowerer.promisePayloadWidens(entry.type, resultT)) + return lowerer.coerceToExpected(entry, resultT); lowerer.unsupported( "SC1090", el, @@ -318,12 +349,12 @@ export function lowerPromiseStaticCall( return { kind: "intrinsic", name: "promise.all", args: [entriesArr], type: resultT, loc }; } - if (!isJsSourceFile(call.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(call))) { const tuple = lowerTuplePromiseAll(lowerer, call, elems); if (tuple) return tuple; } if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && elems.every((entry) => lowerer.dynConvertible(entry.type)) ) { return checkedPromiseAll( @@ -344,7 +375,7 @@ export function lowerPromiseStaticCall( ); } const entries = lowerer.lowerExpr(argNode); - if (isJsSourceFile(call.getSourceFile()) && entries.type.kind === "dyn") + if (isJsSourceFile(ts.getSourceFileOfNode(call)) && entries.type.kind === "dyn") return checkedPromiseAll(lowerer, entries, loc); // loadPlugin(p)))` — the loadPlugins shape, where the checker @@ -403,7 +434,7 @@ export function lowerPromiseStaticCall( `one iterable of promises is the supported form: Promise.${member}(ps)`, ); } - if (isJsSourceFile(call.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(call))) { // JavaScript entries are checked-dynamic: any iterable, thenables // and plain values included, exactly like Promise.all. if ( @@ -670,6 +701,32 @@ export function lowerPromiseStaticCall( lowerer.noLowering("Promise.resolve at a unit-typed promise", call); } const value = lowerer.lowerExprExpecting(argNode, resultT.inner); + // In JavaScript, a checked-dynamic argument may itself hold a promise + // (a TypeScript async callback's result: `Promise.resolve(fn())`): + // Promise.resolve answers THAT promise — the same object, adopted — + // and wraps every other value. + if ( + isJsSourceFile(ts.getSourceFileOfNode(call)) && + value.type.kind === "dyn" && + resultT.inner.kind === "dyn" + ) { + const local = lowerer.declareHiddenLocal("%resolveValue", DYN); + const ref: IrExpr = { kind: "varRef", localId: local.id, type: DYN, loc }; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }], + result: { + kind: "ternary", + cond: { kind: "dynTest", test: "promise", value: ref, type: BOOL, loc }, + then: { kind: "dynCheck", value: ref, type: resultT, loc }, + else_: { kind: "intrinsic", name: "promise.resolve", args: [ref], type: resultT, loc }, + type: resultT, + loc, + }, + type: resultT, + loc, + }; + } return { kind: "intrinsic", name: "promise.resolve", args: [value], type: resultT, loc }; } return null; // reject lands on lowerPromiseRejectCall / the member fence diff --git a/packages/compiler/src/frontend/lowering/builtins/readline.ts b/packages/compiler/src/frontend/lowering/builtins/readline.ts index bcf77a39b..ede65d788 100644 --- a/packages/compiler/src/frontend/lowering/builtins/readline.ts +++ b/packages/compiler/src/frontend/lowering/builtins/readline.ts @@ -46,10 +46,10 @@ function isReadlineModule(spec: string | null): boolean { export function isReadlineTyped(lowerer: Lowerer, node: ts.Expression): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getAliasSymbol() ?? t.getSymbol(); - if (sym?.name !== "Interface") return false; + if (sym === undefined || ts.symbolName(sym) !== "Interface") return false; return sym.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; return isReadlineModule(ambientModuleOf(d)); }); } @@ -74,7 +74,7 @@ const RL_EVENT_LIST = * readline/promises question. */ function returnsPromise(lowerer: Lowerer, call: ts.CallExpression): boolean { const t = lowerer.checker.getTypeAtLocation(call); - return (t.getSymbol()?.name ?? "") === "Promise"; + return (ts.symbolNameOf(t.getSymbol()) ?? "") === "Promise"; } /** The `{ signal }` options argument of the promise question: the signal @@ -107,10 +107,10 @@ function questionSignal(lowerer: Lowerer, node: ts.Expression | undefined): ts.E function isReadlineUiTyped(lowerer: Lowerer, node: ts.Expression): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getAliasSymbol() ?? t.getSymbol(); - if (sym?.name !== "Readline") return false; + if (sym === undefined || ts.symbolName(sym) !== "Readline") return false; return sym.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; const spec = ambientModuleOf(d); return spec === "readline/promises" || spec === "node:readline/promises"; }); diff --git a/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts b/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts index fc507e2bd..230441d31 100644 --- a/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts +++ b/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts @@ -10,10 +10,10 @@ export function isBuiltinClassInstance( ): boolean { const type = lowerer.checker.getTypeAtLocation(node); const symbol = type.getAliasSymbol() ?? type.getSymbol(); - if (symbol?.name !== name) return false; + if (symbol === undefined || ts.symbolName(symbol) !== name) return false; return symbol.declarations.some( (declaration) => (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && - lowerer.isStdlibFile(declaration.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration)), ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts b/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts index 4b61f98c1..7db3f8dcc 100644 --- a/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts +++ b/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts @@ -17,7 +17,7 @@ export function runtimeConstructorReceiver( let e = expr; while (ts.isParenthesizedExpression(e)) e = e.expression; if (!ts.isPropertyAccessExpression(e) || e.questionDotToken) return null; - if (e.name.text !== "constructor" || isJsSourceFile(e.getSourceFile())) return null; + if (e.name.text !== "constructor" || isJsSourceFile(ts.getSourceFileOfNode(e))) return null; const mapped = lowerer.mapTypeOf(lowerer.typeOf(e.expression)); if (mapped?.kind !== "object") return null; const info = lowerer.classes.get(mapped.className); diff --git a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts index e89b1f07e..7229d5b84 100644 --- a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts +++ b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts @@ -360,13 +360,15 @@ export function storedTextCodecClassOf( expr: ts.Expression, ): TextCodecCtor["cls"] | null { const sym = lowerer.typeOf(expr).getSymbol(); - if (!sym || (sym.name !== "TextEncoder" && sym.name !== "TextDecoder")) return null; + if (!sym) return null; + const name = ts.symbolName(sym); + if (name !== "TextEncoder" && name !== "TextDecoder") return null; return sym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) - ? sym.name + ? name : null; } @@ -517,8 +519,9 @@ function directTextCodecCtorOf(lowerer: Lowerer, expr: ts.Expression): TextCodec if (callee.text !== "TextDecoder" && callee.text !== "TextEncoder") return null; const sym = lowerer.resolveValueSymbol(callee); if (!sym || !lowerer.isStdlibSymbol(sym)) return null; - if (sym.name !== "TextDecoder" && sym.name !== "TextEncoder") return null; - return { cls: sym.name, ctor }; + const name = ts.symbolName(sym); + if (name !== "TextDecoder" && name !== "TextEncoder") return null; + return { cls: name, ctor }; } /** Inline codec calls avoid allocating a receiver; stored receivers use diff --git a/packages/compiler/src/frontend/lowering/builtins/timers.ts b/packages/compiler/src/frontend/lowering/builtins/timers.ts index 9f950cddc..7230d18cd 100644 --- a/packages/compiler/src/frontend/lowering/builtins/timers.ts +++ b/packages/compiler/src/frontend/lowering/builtins/timers.ts @@ -154,9 +154,9 @@ function isTimerHandleTyped( ): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getAliasSymbol() ?? t.getSymbol(); - if (sym?.name !== name) return false; + if (sym === undefined || ts.symbolName(sym) !== name) return false; return sym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ); } function isTimeoutTyped(lowerer: Lowerer, node: ts.Expression): boolean { diff --git a/packages/compiler/src/frontend/lowering/builtins/url.ts b/packages/compiler/src/frontend/lowering/builtins/url.ts index 3de973335..dca8a7cc0 100644 --- a/packages/compiler/src/frontend/lowering/builtins/url.ts +++ b/packages/compiler/src/frontend/lowering/builtins/url.ts @@ -842,7 +842,7 @@ export function lowerLegacyUrlCall( if (bi.member === "domainToASCII" || bi.member === "domainToUnicode") { args.push({ kind: "boolLit", value: expr.arguments.nodes.length === 0, type: BOOL, loc }); } else { - const warn = !insideNodeModules(expr.getSourceFile().fileName); + const warn = !insideNodeModules(ts.getSourceFileOfNode(expr).fileName); args.push({ kind: "boolLit", value: warn, type: BOOL, loc }); } return { kind: "libCall", fn: fn.fn, args, type: fn.result, loc }; diff --git a/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts b/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts index 2a9ff9eef..93cba84c5 100644 --- a/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts +++ b/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts @@ -103,7 +103,7 @@ export function fenceWebCryptoMember( use: "read" | "call", ): IrExpr | null { if (use !== "read" || !ts.isPropertyAccessExpression(access)) return null; - if (isJsSourceFile(access.getSourceFile())) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(access))) return null; if (isSubtleReceiver(lowerer, access)) { lowerer.noLowering("crypto.subtle as a value", access, SUBTLE_HINT); } @@ -123,8 +123,8 @@ export function isWebCryptoMemberType(lowerer: Lowerer, type: ts.Type): boolean const symbol = type.getAliasSymbol() ?? type.getSymbol(); return ( symbol !== undefined && - symbol.name !== "Crypto" && - WEBCRYPTO_DYN_TYPES.has(symbol.name) && + ts.symbolName(symbol) !== "Crypto" && + WEBCRYPTO_DYN_TYPES.has(ts.symbolName(symbol)) && lowerer.isStdlibSymbol(symbol) ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/workers.ts b/packages/compiler/src/frontend/lowering/builtins/workers.ts index 335fdb969..1426041a2 100644 --- a/packages/compiler/src/frontend/lowering/builtins/workers.ts +++ b/packages/compiler/src/frontend/lowering/builtins/workers.ts @@ -174,10 +174,10 @@ function propertyNameText(name: ts.PropertyName): string | undefined { function isWorkerThreadsClass(lowerer: Lowerer, node: ts.Expression, name: string): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getSymbol(); - if (sym?.name !== name) return false; + if (sym === undefined || ts.symbolName(sym) !== name) return false; return sym.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; let up: ts.Node | undefined = d.parent; while (up) { if (ts.isModuleDeclaration(up) && ts.isStringLiteral(up.name)) diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index db7691a1c..49f7c8ec0 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -143,7 +143,7 @@ export function completeArgs( loc: value.loc, }; } - value = checkedIterableSpread(lowerer, value, arg.expression.getText(), loc); + value = checkedIterableSpread(lowerer, value, ts.getTextOfNode(arg.expression), loc); if (spreads === 1 && index === argNodes.length - 1) { stmts.push({ kind: "exprStmt", @@ -153,7 +153,7 @@ export function completeArgs( args: [ ref(), value, - { kind: "strLit", value: arg.expression.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(arg.expression), type: STRING, loc }, ], type: VOID, loc, @@ -605,7 +605,7 @@ export function lowerSpreadArgsCall( // TypeScript reaches this lane only for checked-dynamic variadic callees // (`(...args: unknown[]) => R`), whose ABI is the JS one. if ( - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && !(callee.type.kind === "func" && callee.type.rest === true && callee.type.restAbi === undefined) ) return null; @@ -613,7 +613,7 @@ export function lowerSpreadArgsCall( if (callee.type.kind !== "dyn" && callee.type.kind !== "func") return null; const calleeName = ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression) - ? expr.expression.getText() + ? ts.getTextOfNode(expr.expression) : ts.isIdentifier(expr.expression) ? expr.expression.text : "value"; @@ -640,8 +640,8 @@ export function lowerSpreadArgsCall( if (p.spreadOf !== null) { // The spelling rides along for V8's nullish spread-call // TypeError ("v is not iterable (cannot read property ...)"). - spreads.push({ arg: args.length, what: p.spreadOf.getText() }); - args.push(checkedIterableSpread(lowerer, p.v, p.spreadOf.getText(), loc)); + spreads.push({ arg: args.length, what: ts.getTextOfNode(p.spreadOf) }); + args.push(checkedIterableSpread(lowerer, p.v, ts.getTextOfNode(p.spreadOf), loc)); } else { args.push(lowerer.coerceInto(p.node!, p.v, DYN)); } diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index 954122a16..47cdd834f 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -148,7 +148,9 @@ export function documentedParameter(param: ts.ParameterDeclaration): boolean { return ( param.type !== undefined || /@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ); } @@ -158,7 +160,7 @@ export function checkedJsDocumentedParameter( lowerer: Lowerer, param: ts.ParameterDeclaration, ): boolean { - return lowerer.checkedJsFile(param.getSourceFile()) && documentedParameter(param); + return lowerer.checkedJsFile(ts.getSourceFileOfNode(param)) && documentedParameter(param); } /** One parameter's ParamShape — the shared signature-shaped collection @@ -180,10 +182,10 @@ export function checkedJsDocumentedParameter( export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): ParamShape { const stage = lowerer.stageSourceType(param.name); if (stage) return { type: stage, mode: "required" }; - if (isJsSourceFile(param.getSourceFile()) && ts.isSetAccessorDeclaration(param.parent)) + if (isJsSourceFile(ts.getSourceFileOfNode(param)) && ts.isSetAccessorDeclaration(param.parent)) return { type: DYN, mode: "required" }; if ( - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && ts.isConstructorDeclaration(param.parent) && !param.dotDotDotToken && !checkedJsDocumentedParameter(lowerer, param) @@ -207,12 +209,14 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa lowerer.isStdlibGlobal(construction.expression, "Promise"); if ( !nativeExecutor && - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && !param.dotDotDotToken && !ts.isSetAccessorDeclaration(param.parent) && !/@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ) { return param.initializer @@ -221,10 +225,12 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa } if ( param.initializer && - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && !/@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ) { return { type: DYN, mode: "omittable", bodyType: DYN }; @@ -233,10 +239,12 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa // contract. Consume the actual iterable, including surplus elements. if ( ts.isArrayBindingPattern(param.name) && - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && !/@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ) { if (param.dotDotDotToken) return { type: DYN, mode: "dynRest" }; @@ -298,7 +306,7 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa // ARRAY param the dyn call thunk fills with the call's surplus // arguments; the binding is that array (dynRest — funcType marks // `rest`, and the value only ever calls through the boxed thunk). - if (isJsSourceFile(param.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(param))) { const restMapped = lowerer.mapTypeOf(lowerer.typeOf(param.name)); // Unmapped JS rest parameters use the native variadic checked-value ABI. @@ -325,7 +333,7 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa // including typed arrays passed through checked callbacks. Extracting // a native array here would copy it and discard element writes. if ( - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && (raw.kind === "array" || (raw.kind === "union" && @@ -337,7 +345,7 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa } let type = lowerer.runtimeOptionalBindingType(param.name, lowerer.irTypeOf(param.name)); const body = param.parent?.body; - if (isJsSourceFile(param.getSourceFile()) && body && type.kind !== "dyn") { + if (isJsSourceFile(ts.getSourceFileOfNode(param)) && body && type.kind !== "dyn") { const symbol = lowerer.checker.getSymbolAtLocation(param.name); let acceptsUndefined = false; ts.walkPreorder(body, (node) => { @@ -397,7 +405,7 @@ export function restTupleShapes( if ( param.dotDotDotToken === undefined || !ts.isIdentifier(param.name) || - isJsSourceFile(param.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(param)) ) { return null; } @@ -435,7 +443,7 @@ export function paramShapes( // rather than as the declaration's unbound type parameter. if ( blameOf !== undefined && - !isJsSourceFile(declParam.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(declParam)) && (tsType.flags & ts.TypeFlags.Any) === 0 && declParam.type !== undefined && mentionsTypeParameter(lowerer, lowerer.checker.getTypeFromTypeNode(declParam.type)) && @@ -450,7 +458,8 @@ export function paramShapes( lowerer.mapTypeOf(tsType) ?? // A TypeScript instantiation at `any` (a type argument inferred // from an untyped value) takes the checked-dynamic slot. - ((tsType.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(declParam.getSourceFile()) + ((tsType.flags & ts.TypeFlags.Any) !== 0 && + !isJsSourceFile(ts.getSourceFileOfNode(declParam)) ? DYN : lowerer.irTypeOf(declParam.name)), ), @@ -668,7 +677,7 @@ export function requireExactArityValue( const ownType = lowerer.typeOf(contextual); mappedFn = lowerer.mapTypeOf(ownType) ?? - (isJsSourceFile(contextual.getSourceFile()) + (isJsSourceFile(ts.getSourceFileOfNode(contextual)) ? dynFallbackType(lowerer, contextual, ownType) : null); } @@ -706,7 +715,7 @@ export function requireExactArityValue( target === undefined && contextual !== null && (ts.isArrowFunction(contextual) || ts.isFunctionExpression(contextual)) && - npmStaticPackageOfPath(contextual.getSourceFile().fileName) !== null + npmStaticPackageOfPath(ts.getSourceFileOfNode(contextual).fileName) !== null ) { return; } @@ -771,7 +780,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara // routes generic declarations (direct calls, namespace/CJS member // calls, value references) resolves it through genericFnsBySymbol // unchanged. - if (implicitMonoFile(decl.getSourceFile()) && !isAsync && !isGenerator) { + if (implicitMonoFile(ts.getSourceFileOfNode(decl)) && !isAsync && !isGenerator) { const implicit = implicitAnyParamSymbolsOf(lowerer, decl); if (implicit) { const nameText = decl.name?.text ?? "%default"; @@ -780,7 +789,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara lowerer.genericFnsBySymbol.set(symbol, { decl, baseName: nameText, - qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(decl) + nameText), + qualifiedName: lowerer.qualify(ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + nameText), typeParams: [], instances: new Map(), implicitParams: implicit, @@ -815,7 +824,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara // collide. The anonymous default export takes the synthetic // "%default" spelling ('%' cannot appear in a user identifier). name: lowerer.qualify( - decl.getSourceFile(), + ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + (decl.name?.text ?? "%default"), ), params, @@ -844,7 +853,7 @@ function appendArgumentsParameter( ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) ) || - !isJsSourceFile(node.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(node)) || !bodyReadsArguments(node) ) return; @@ -854,7 +863,7 @@ function appendArgumentsParameter( (ts.isClassDeclaration(node.parent) || ts.isClassExpression(node.parent)); if ( !classMethod && - !isNodeEsmFile(node.getSourceFile(), lowerer.program) && + !isNodeEsmFile(ts.getSourceFileOfNode(node), lowerer.program) && !unmappedArguments(node) ) { lowerer.unsupported( @@ -925,7 +934,7 @@ export function lambdaSignature( // actual call tuple, including overloads and omitted arguments. Node's // contextual overload signature cannot define this closure's ABI. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type && (ts.isFunctionExpression(node) || ts.isArrowFunction(node)) && node.parameters.nodes.every( @@ -1043,7 +1052,7 @@ export function lambdaSignature( : []; const ctxDecl = ctxSigs.length === 1 ? ctxSigs[0]!.declaration : undefined; const ctxRetRaw = - ctxDecl && !ctxDecl.getSourceFile().isDeclarationFile + ctxDecl && !ts.getSourceFileOfNode(ctxDecl).isDeclarationFile ? lowerer.mapTypeOf(lowerer.checker.getReturnTypeOfSignature(ctxSigs[0]!)) : null; // An async lambda always produces a promise, so a slot returning a @@ -1065,7 +1074,7 @@ export function lambdaSignature( // must match the slot's ABI exactly. const dynVariadicSlot = ctxRet?.kind === "dyn" && - !isJsSourceFile(node.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(node)) && shapes.some((shape) => shape.mode === "dynRest"); if ( dynVariadicSlot || diff --git a/packages/compiler/src/frontend/lowering/cjs-exports-object.ts b/packages/compiler/src/frontend/lowering/cjs-exports-object.ts index f06dde5a2..30030b18f 100644 --- a/packages/compiler/src/frontend/lowering/cjs-exports-object.ts +++ b/packages/compiler/src/frontend/lowering/cjs-exports-object.ts @@ -40,12 +40,12 @@ import { DYN, type IrExpr, type IrGlobal } from "../../ir/ir.js"; * source declaration) in a CommonJS JS file. */ export function isCjsExportsIdentifier(lowerer: Lowerer, node: ts.Expression): boolean { if (!ts.isIdentifier(node) || node.text !== "exports") return false; - if (!isCjsJsFile(node.getSourceFile(), lowerer.program)) return false; + if (!isCjsJsFile(ts.getSourceFileOfNode(node), lowerer.program)) return false; const symbol = lowerer.checker.getSymbolAtLocation(node); const decls = symbol ? symbol.declarations : []; return !decls.some( (d) => - !d.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(d).isDeclarationFile && (ts.isVariableDeclaration(d) || ts.isParameter(d) || ts.isBindingElement(d) || @@ -62,7 +62,7 @@ export function isCjsModuleExports(lowerer: Lowerer, node: ts.Expression): boole return ( isModuleExportsAccess(node) && isCjsModuleGlobal(lowerer, node.expression) && - isCjsJsFile(node.getSourceFile(), lowerer.program) + isCjsJsFile(ts.getSourceFileOfNode(node), lowerer.program) ); } @@ -75,7 +75,7 @@ export function cjsInFileExportGlobalOf( access: ts.PropertyAccessExpression, ): IrGlobal | null { if (access.questionDotToken || !ts.isIdentifier(access.name)) return null; - const sf = access.getSourceFile(); + const sf = ts.getSourceFileOfNode(access); const recv = isModuleExportsAccess(access.expression) || (ts.isIdentifier(access.expression) && @@ -186,7 +186,7 @@ function exportsAssignments( * it runs: `exports` unless the file rebinds it; `module.exports` unless the * file rebinds `exports` or replaces `module.exports`. */ function namesOriginalExports(lowerer: Lowerer, node: ts.Expression): boolean { - const assigned = exportsAssignments(lowerer, node.getSourceFile()); + const assigned = exportsAssignments(lowerer, ts.getSourceFileOfNode(node)); if (assigned.exports) return false; return ts.isIdentifier(node) || !assigned.moduleExports; } @@ -252,7 +252,7 @@ export function usesCjsExportsIdentity(lowerer: Lowerer, sf: ts.SourceFile): boo * refusal. Null when `node` is not such a use. */ export function lowerCjsExportsObjectValue(lowerer: Lowerer, node: ts.Expression): IrExpr | null { if (!isCjsExportsObjectValue(lowerer, node)) return null; - const sf = node.getSourceFile(); + const sf = ts.getSourceFileOfNode(node); // A module that replaces module.exports answers `module.exports` reads // through the replacement's own machinery (the class-expression whole // export, the replaced-exports reads). @@ -328,7 +328,7 @@ export function cjsExportMemberWriteGlobalOf( lowerer: Lowerer, target: ts.PropertyAccessExpression, ): IrGlobal | null { - const sf = target.getSourceFile(); + const sf = ts.getSourceFileOfNode(target); if (!isCjsJsFile(sf, lowerer.program)) return null; const assigned = exportsAssignments(lowerer, sf); if (assigned.exports || assigned.moduleExports) return null; @@ -458,7 +458,7 @@ export function cjsMemberAliasValueOf(lowerer: Lowerer, ref: ts.Identifier): ts. ) return null; const spec = importDecl.moduleSpecifier.text; - const from = importDecl.getSourceFile(); + const from = ts.getSourceFileOfNode(importDecl); const dep = resolveImport(lowerer.program, from, spec, "import") ?? npmStaticDepSf7(lowerer.program, from, spec, "import"); @@ -552,7 +552,7 @@ function rereadableMemberValue( const sym = lowerer.resolveValueSymbol(value); if (sym === null || sym.declarations.length !== 1) return false; const decl = sym.declarations[0]!; - if (decl.getSourceFile() !== stmt.getSourceFile()) return false; + if (ts.getSourceFileOfNode(decl) !== ts.getSourceFileOfNode(stmt)) return false; // A function declaration is initialized when the module starts; any // other binding must be initialized before the write stores it. if (!ts.isFunctionDeclaration(decl) && decl.end > stmt.pos) return false; diff --git a/packages/compiler/src/frontend/lowering/cjs-inline-require.ts b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts index 174f9dc7c..2b28c6533 100644 --- a/packages/compiler/src/frontend/lowering/cjs-inline-require.ts +++ b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts @@ -56,7 +56,7 @@ export function inlineProgramRequireOf( if (!ts.isCallExpression(e) || !ts.isIdentifier(e.expression)) return null; const spec = requireSpecOf(e); if (spec === null || canonicalBuiltinModule(spec) !== null) return null; - const sf = e.getSourceFile(); + const sf = ts.getSourceFileOfNode(e); if (!isCjsJsFile(sf, lowerer.program)) return null; if (!isCjsRequireGlobal(lowerer, e.expression)) return null; if (createRequireCalleeFileOf(lowerer, e.expression) !== null) return null; @@ -201,7 +201,7 @@ export function lowerUncompiledRequireCall( call: ts.CallExpression, ): IrExpr | null { if (!ts.isIdentifier(call.expression) || call.expression.text !== "require") return null; - const sf = call.getSourceFile(); + const sf = ts.getSourceFileOfNode(call); if (!isCjsJsFile(sf, lowerer.program) || !isCjsRequireGlobal(lowerer, call.expression)) return null; if (createRequireCalleeFileOf(lowerer, call.expression) !== null) return null; @@ -240,7 +240,7 @@ export function requireNestedPatternsOf(decl: ts.VariableDeclaration): ts.Bindin if (!ts.isObjectBindingPattern(decl.name) || decl.initializer === undefined) return []; const spec = requireSpecOf(decl.initializer); if (spec === null || canonicalBuiltinModule(spec) !== null) return []; - if (!isJsSourceFile(decl.getSourceFile())) return []; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) return []; return decl.name.elements.nodes.filter( (e) => e.name !== undefined && diff --git a/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts b/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts index cd83c8709..aae221b30 100644 --- a/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts +++ b/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts @@ -175,7 +175,7 @@ export function tscInteropDefaultValueOf( export function isDeadTscHelperDecl(lowerer: Lowerer, stmt: ts.Statement): boolean { const name = tscHelperDeclName(stmt); if (name === null) return false; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); const helperSpans = new Map(); for (const s of sf.statements.nodes) { const n = tscHelperDeclName(s); @@ -192,11 +192,11 @@ export function isDeadTscHelperDecl(lowerer: Lowerer, stmt: ts.Statement): boole const parent = node.parent; if (parent !== undefined && ts.isVariableDeclaration(parent) && parent.name === node) return undefined; - const at = node.getStart(sf); - if (at >= decl.getStart(sf) && at < decl.getEnd()) return undefined; + const at = ts.tokenStartOfNode(node); + if (at >= ts.tokenStartOfNode(decl) && at < decl.end) return undefined; for (const [other, otherDecl] of helperSpans) { if (other !== helper && dead.has(other)) { - if (at >= otherDecl.getStart(sf) && at < otherDecl.getEnd()) return undefined; + if (at >= ts.tokenStartOfNode(otherDecl) && at < otherDecl.end) return undefined; } } const call = parent; diff --git a/packages/compiler/src/frontend/lowering/class-callbacks.ts b/packages/compiler/src/frontend/lowering/class-callbacks.ts index 859938845..6ca685684 100644 --- a/packages/compiler/src/frontend/lowering/class-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/class-callbacks.ts @@ -39,8 +39,8 @@ export function isClassCallback(lowerer: Lowerer, info: ClassInfo, name: string) !found.sig.abstract && (lowerer.prototypeMethodAccesses.has(name) || (info.decl !== null && - isJsSourceFile(info.decl.getSourceFile()) && - !lowerer.checkedJsFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && + !lowerer.checkedJsFile(ts.getSourceFileOfNode(info.decl)) && found.sig.params.length === 0 && found.sig.ret.kind === "void")) ); @@ -171,7 +171,7 @@ export function classCallbackCall( const spreads: { arg: number; what: string }[] = []; const args = argumentNodes.map((argument, index) => { if (!ts.isSpreadElement(argument)) return lowerer.lowerExprExpecting(argument, DYN); - const spelling = argument.expression.getText(); + const spelling = ts.getTextOfNode(argument.expression); spreads.push({ arg: index, what: spelling }); return checkedIterableSpread(lowerer, lowerer.lowerExpr(argument.expression), spelling, loc); }); diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index 5fc2a3cec..de84e3fed 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -1158,7 +1158,7 @@ export class ClassDynamicDispatch { if (this.bagClasses.has(plan.key)) continue; this.bagClasses.add(plan.key); this.ensurePropertyBag(info); - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { let root = info; while (root.base && !root.base.def.runtime) root = root.base; const track = (current: ClassInfo): void => { @@ -3246,7 +3246,7 @@ export class ClassDynamicDispatch { for (const name of this.boxedConstructors) { if (this.staticBagClasses.has(name)) continue; const info = lowerer.classes.get(name); - if (!info?.decl || !isJsSourceFile(info.decl.getSourceFile())) continue; + if (!info?.decl || !isJsSourceFile(ts.getSourceFileOfNode(info.decl))) continue; const loc = this.staticPropertyBag.loc; const bag = classStaticData(lowerer, info, loc); if (!bag) continue; diff --git a/packages/compiler/src/frontend/lowering/class-method-mutations.ts b/packages/compiler/src/frontend/lowering/class-method-mutations.ts index 981cd0c1a..cc8700469 100644 --- a/packages/compiler/src/frontend/lowering/class-method-mutations.ts +++ b/packages/compiler/src/frontend/lowering/class-method-mutations.ts @@ -152,10 +152,11 @@ export function collectClassMethodMutations( const required = properties.filter( (property) => (property.flags & ts.SymbolFlags.Optional) === 0, ); - if (required.some((property) => !FUNCTION_MEMBER_NAMES.has(property.name))) return true; + if (required.some((property) => !FUNCTION_MEMBER_NAMES.has(ts.symbolName(property)))) + return true; return ( required.length === 0 && - properties.every((property) => !FUNCTION_MEMBER_NAMES.has(property.name)) + properties.every((property) => !FUNCTION_MEMBER_NAMES.has(ts.symbolName(property))) ); }; // A string index whose values cannot be functions excludes callable @@ -167,7 +168,7 @@ export function collectClassMethodMutations( .getIndexInfosOfType(lowerer.typeOf(value)) .some( (index) => - (index.keyType.flags & ts.TypeFlags.StringLike) !== 0 && functionFree(index.valueType), + (index.keyType.flags & ts.TypeFlags.StringLike) !== 0 && functionFree(index.type), ); const literalLoopKeys = (key: ts.Expression): (string | number)[] | null => { if (!ts.isIdentifier(key)) return null; diff --git a/packages/compiler/src/frontend/lowering/class-method-values.ts b/packages/compiler/src/frontend/lowering/class-method-values.ts index 795307c93..1197d9bdd 100644 --- a/packages/compiler/src/frontend/lowering/class-method-values.ts +++ b/packages/compiler/src/frontend/lowering/class-method-values.ts @@ -92,7 +92,8 @@ export function lowerClassMethodValue( loc, }; }; - const checked = isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(value.type); + const checked = + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(value.type); if (checked) value = lowerer.coerceToExpected(value, DYN); // Select the declaration when extracting the value. Calling the value // later must not redispatch the method name on a different receiver. @@ -374,7 +375,7 @@ function reflectedMethodValue( if (!member || (!ts.isMethodDeclaration(member) && !ts.isAccessor(member)) || !member.body) return null; let needsNativeReceiver = - !isJsSourceFile(declaration.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(declaration)) || info.localClass !== undefined || lowerer.errorHierarchyClassOf(info.def.name); ts.walkPreorder(member, (node) => { diff --git a/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts b/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts index 298ff45dc..33515bf2c 100644 --- a/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts @@ -87,7 +87,7 @@ function codeOwner(node: ts.Node): ts.Node { !ts.isPropertyDeclaration(current) ) current = current.parent; - return current ?? node.getSourceFile(); + return current ?? ts.getSourceFileOfNode(node); } /** A local variable or parameter that only its own function's code @@ -102,21 +102,21 @@ function ownedBinding(lowerer: Lowerer, id: ts.Identifier): boolean { const decl = symbol.valueDeclaration; let owned = decl !== undefined && - decl.getSourceFile() === id.getSourceFile() && + ts.getSourceFileOfNode(decl) === ts.getSourceFileOfNode(id) && ((ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name)) || (ts.isParameter(decl) && ts.isIdentifier(decl.name))); if (owned && decl) { const owner = codeOwner(decl); const names = (node: ts.Node): boolean => ts.isIdentifier(node) && - node.text === symbol.name && + node.text === ts.symbolName(symbol) && lowerer.resolveValueSymbol(node) === symbol; const written = (target: ts.Node): boolean => { let found = false; const walk = (node: ts.Node): void => { if (found) return; if (names(node)) found = true; - else node.forEachChild(walk); + else ts.forEachChild(node, walk); }; walk(target); return found; @@ -144,9 +144,9 @@ function ownedBinding(lowerer: Lowerer, id: ts.Identifier): boolean { ) target = node.initializer; if (target && codeOwner(node) !== owner && written(target)) owned = false; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - owner.forEachChild(visit); + ts.forEachChild(owner, visit); } lowerer.protocolOwnedBindings.set(symbol, owned); return owned; @@ -302,7 +302,7 @@ export function lowerProtocolMethodCall( kind: "dynCall", callee: probed ? varRef("p.callee", DYN, loc) : read(), receiver: varRef("p.0", DYN, loc), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), calleeNameValue: varRef("p.calleeName", STRING, loc), args: params.map((type, i) => lowerer.coerceToExpected(varRef(`p.${i + 1}`, type, loc), DYN)), type: DYN, @@ -378,7 +378,7 @@ export function lowerProtocolMethodCall( receiver, ...(callee ? [varRef(callee.id, DYN, loc)] : []), ...args, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: result, loc, @@ -487,7 +487,7 @@ export function lowerProtocolPropertyRead( lowerer: Lowerer, access: ts.PropertyAccessExpression, ): IrExpr | null { - if (isJsSourceFile(access.getSourceFile())) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(access))) return null; const receiverType = lowerer.typeOf(access.expression); if (!lowerer.classImplementedProtocol(receiverType)) return null; const name = access.name.text; diff --git a/packages/compiler/src/frontend/lowering/class-protocols.ts b/packages/compiler/src/frontend/lowering/class-protocols.ts index 5a27f0815..4de7f03ad 100644 --- a/packages/compiler/src/frontend/lowering/class-protocols.ts +++ b/packages/compiler/src/frontend/lowering/class-protocols.ts @@ -46,9 +46,9 @@ export class ClassProtocols { ) ) continue; - const entries = this.methods.get(property.name) ?? []; + const entries = this.methods.get(ts.symbolName(property)) ?? []; entries.push(node); - this.methods.set(property.name, entries); + this.methods.set(ts.symbolName(property), entries); } } if ( @@ -472,7 +472,7 @@ export class ClassProtocols { if (facts.observed.has(symbol) || facts.written.has(symbol)) return true; const target = facts.targets.get(symbol); if (target === undefined) return true; - const names = new Set(this.checker.getPropertiesOfType(target).map((p) => p.name)); + const names = new Set(this.checker.getPropertiesOfType(target).map((p) => ts.symbolName(p))); const origins = facts.origins.get(symbol) ?? new Set(); const writesNames = (written: Set | "all"): boolean => written === "all" || [...written].some((name) => names.has(name)); @@ -517,7 +517,7 @@ export class ClassProtocols { .getPropertiesOfType(declared) .some( (property) => - names.has(property.name) && + names.has(ts.symbolName(property)) && property.declarations.some( (declaration) => ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration), @@ -575,7 +575,9 @@ export class ClassProtocols { return ( symbol !== undefined && (symbol.flags & ts.SymbolFlags.Class) !== 0 && - symbol.declarations.some((declaration) => !declaration.getSourceFile().isDeclarationFile) && + symbol.declarations.some( + (declaration) => !ts.getSourceFileOfNode(declaration).isDeclarationFile, + ) && // `typeof C` (the class value) shares the class symbol. this.checker.getConstructSignatures(type).length === 0 ); @@ -598,7 +600,8 @@ export class ClassProtocols { this.methods.size > 0 && symbol.declarations.some( (declaration) => - ts.isInterfaceDeclaration(declaration) && !declaration.getSourceFile().isDeclarationFile, + ts.isInterfaceDeclaration(declaration) && + !ts.getSourceFileOfNode(declaration).isDeclarationFile, ) && this.protocol(type) ); @@ -617,7 +620,7 @@ export class ClassProtocols { const declarations = symbol.declarations; return ( declarations.length > 0 && - declarations.every((declaration) => !declaration.getSourceFile().isDeclarationFile) + declarations.every((declaration) => !ts.getSourceFileOfNode(declaration).isDeclarationFile) ); } @@ -704,14 +707,15 @@ export class ClassProtocols { !symbol || !symbol.declarations.some( (declaration) => - ts.isInterfaceDeclaration(declaration) && !declaration.getSourceFile().isDeclarationFile, + ts.isInterfaceDeclaration(declaration) && + !ts.getSourceFileOfNode(declaration).isDeclarationFile, ) ) return false; for (const property of properties) { const memberType = this.checker.getTypeOfSymbol(property); if (this.checker.getCallSignatures(memberType).length === 0) continue; - for (const declaration of this.methods.get(property.name) ?? []) { + for (const declaration of this.methods.get(ts.symbolName(property)) ?? []) { const classSymbol = declaration.name ? this.checker.getSymbolAtLocation(declaration.name) : undefined; diff --git a/packages/compiler/src/frontend/lowering/class-static-data.ts b/packages/compiler/src/frontend/lowering/class-static-data.ts index af5425727..dd6927d18 100644 --- a/packages/compiler/src/frontend/lowering/class-static-data.ts +++ b/packages/compiler/src/frontend/lowering/class-static-data.ts @@ -38,7 +38,7 @@ export function classStaticDataFor( ): IrExpr | null { if ( !info.decl || - !isJsSourceFile(info.decl.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(info.decl)) || name.startsWith("#") || constructorMembers.has(name) ) diff --git a/packages/compiler/src/frontend/lowering/code-generation.ts b/packages/compiler/src/frontend/lowering/code-generation.ts index b3bd9243a..c3a5c0ea7 100644 --- a/packages/compiler/src/frontend/lowering/code-generation.ts +++ b/packages/compiler/src/frontend/lowering/code-generation.ts @@ -126,8 +126,12 @@ function useSiteType(lowerer: Lowerer, expr: ts.Expression): IrType { // elsewhere — the throw always fires first. const type = lowerer.typeOf(expr); const symbol = type.getSymbol(); - if (symbol?.name === "Function" && lowerer.isStdlibSymbol(symbol)) { - return isJsSourceFile(expr.getSourceFile()) ? DYN : F64; + if ( + symbol !== undefined && + ts.symbolName(symbol) === "Function" && + lowerer.isStdlibSymbol(symbol) + ) { + return isJsSourceFile(ts.getSourceFileOfNode(expr)) ? DYN : F64; } const mapped = lowerer.mapTypeOf(type); if ( diff --git a/packages/compiler/src/frontend/lowering/collection-ingestion.ts b/packages/compiler/src/frontend/lowering/collection-ingestion.ts index 5449b0943..5d14d5492 100644 --- a/packages/compiler/src/frontend/lowering/collection-ingestion.ts +++ b/packages/compiler/src/frontend/lowering/collection-ingestion.ts @@ -91,7 +91,7 @@ export function collectionInput( const symbol = type.getSymbol(); if ( !symbol || - (symbol.name !== "MapIterator" && symbol.name !== "SetIterator") || + (ts.symbolName(symbol) !== "MapIterator" && ts.symbolName(symbol) !== "SetIterator") || !lowerer.isStdlibSymbol(symbol) ) return null; diff --git a/packages/compiler/src/frontend/lowering/collection-narrowing.ts b/packages/compiler/src/frontend/lowering/collection-narrowing.ts index 29b358e99..6c1ca80f4 100644 --- a/packages/compiler/src/frontend/lowering/collection-narrowing.ts +++ b/packages/compiler/src/frontend/lowering/collection-narrowing.ts @@ -11,9 +11,12 @@ export function concreteCollectionNarrow( ): ts.Type | null { if (!ts.isIdentifier(node) && !ts.isPropertyAccessExpression(node)) return null; const symbol = narrowed.getSymbol(); - const name = symbol?.name; + const name = ts.symbolNameOf(symbol); if (name !== "Map" && name !== "Set") return null; - if (!symbol || !symbol.declarations.some((decl) => lowerer.isStdlibFile(decl.getSourceFile()))) + if ( + !symbol || + !symbol.declarations.some((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) + ) return null; const args = lowerer.checker.getTypeArguments(narrowed as ts.TypeReference); if (args.length === 0 || !args.every((arg) => (arg.flags & ts.TypeFlags.Any) !== 0)) return null; diff --git a/packages/compiler/src/frontend/lowering/construction-escapes.test.ts b/packages/compiler/src/frontend/lowering/construction-escapes.test.ts index 77f72dc15..6807946d0 100644 --- a/packages/compiler/src/frontend/lowering/construction-escapes.test.ts +++ b/packages/compiler/src/frontend/lowering/construction-escapes.test.ts @@ -16,7 +16,7 @@ function analyze(source: string, options: { derived?: boolean; baseEscapes?: boo const ctor = cls.members.nodes.find(ts.isConstructorDeclaration) ?? null; const fields = cls.members.nodes .filter(ts.isPropertyDeclaration) - .map((member) => ({ name: member.name.getText(), initializer: member.initializer })); + .map((member) => ({ name: ts.getTextOfNode(member.name), initializer: member.initializer })); const fieldNames = new Set(fields.map((field) => field.name)); return constructionEscape({ ctor, @@ -30,7 +30,7 @@ function analyze(source: string, options: { derived?: boolean; baseEscapes?: boo const name = call.expression.name.text; const method = cls.members.nodes.find( (member): member is ts.MethodDeclaration => - ts.isMethodDeclaration(member) && member.name.getText() === name, + ts.isMethodDeclaration(member) && ts.getTextOfNode(member.name) === name, ); const isPrivate = method !== undefined && diff --git a/packages/compiler/src/frontend/lowering/construction-escapes.ts b/packages/compiler/src/frontend/lowering/construction-escapes.ts index b9c8d7e07..3fdae4ab4 100644 --- a/packages/compiler/src/frontend/lowering/construction-escapes.ts +++ b/packages/compiler/src/frontend/lowering/construction-escapes.ts @@ -23,7 +23,7 @@ export function mentionsReceiver(node: ts.Node): boolean { found = true; return; } - child.forEachChild(visit); + ts.forEachChild(child, visit); }; visit(node); return found; @@ -68,9 +68,9 @@ function containsReturn(node: ts.Node): boolean { found = true; return; } - child.forEachChild(visit); + ts.forEachChild(child, visit); }; - node.forEachChild(visit); + ts.forEachChild(node, visit); return found; } @@ -100,7 +100,7 @@ export function constructionEscape(inputs: ConstructionInputs): ConstructionEsca return !(ts.isIdentifier(node.name) && assigned.has(name) && inputs.isField(name)); } let found = false; - node.forEachChild((child) => { + ts.forEachChild(node, (child) => { if (!found && mentionsReceiver(child)) found = exposes(child, depth); }); return found; diff --git a/packages/compiler/src/frontend/lowering/constructor-returns.ts b/packages/compiler/src/frontend/lowering/constructor-returns.ts index bee2e7567..0e600e700 100644 --- a/packages/compiler/src/frontend/lowering/constructor-returns.ts +++ b/packages/compiler/src/frontend/lowering/constructor-returns.ts @@ -86,9 +86,9 @@ function ownValueReturns(ctor: ts.ConstructorDeclaration): ts.ReturnStatement[] const visit = (node: ts.Node): void => { if (ts.isFunctionLike(node) || ts.isClassLikeDeclaration(node)) return; if (ts.isReturnStatement(node) && node.expression !== undefined) out.push(node); - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - ctor.body?.forEachChild(visit); + if (ctor.body !== undefined) ts.forEachChild(ctor.body, visit); return out; } @@ -188,9 +188,9 @@ function ownReturns(body: ts.Node): ts.ReturnStatement[] { const visit = (node: ts.Node): void => { if (ts.isFunctionLike(node) || ts.isClassLikeDeclaration(node)) return; if (ts.isReturnStatement(node)) out.push(node); - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return out; } @@ -225,7 +225,7 @@ export function constructorReturnPlan( // the class too (`if (C.instance) return C.instance;`). const runtimeInstance = info.localClass === undefined && - isJsSourceFile(ctor.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(ctor)) && [...returns].some(([stmt, kind]) => kind === "mixed" && !insideFinallyTry(stmt, ctor)); if ([...returns.values()].includes("instance") || runtimeInstance) { slot = { @@ -277,7 +277,7 @@ export function lowerConstructorReturn(lowerer: Lowerer, stmt: ts.ReturnStatemen }; } const feature = foreignObjectFeature(info); - if (kind === "object" || !isJsSourceFile(stmt.getSourceFile())) + if (kind === "object" || !isJsSourceFile(ts.getSourceFileOfNode(stmt))) lowerer.unsupported("SC1090", stmt, feature); // A JavaScript value that is an object only sometimes: a primitive // still answers the constructed instance, an object refuses. @@ -296,7 +296,7 @@ export function lowerConstructorReturn(lowerer: Lowerer, stmt: ts.ReturnStatemen }); const diag = unsupportedDiag("SC1090", loc, feature); lowerer.runtimeFences.push(diag); - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); const line = ts.getLineAndCharacterOfPosition(sf, loc.start).line + 1; return { kind: "block", @@ -460,7 +460,7 @@ export function superIntoResultSlot( const slot = plan.slot; const display = base.def.name.replace(/^%|^%m\d+\./, ""); const feature = `extending a class whose constructor can return an existing instance ('super()' would rebind 'this' to the instance '${display}' returns)`; - if (!isJsSourceFile(blame.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(blame))) { lowerer.unsupported("SC1090", blame, feature); return call; } @@ -470,7 +470,8 @@ export function superIntoResultSlot( if (!lowerer.liftedFns.some((fn) => fn.name === name)) { const diag = unsupportedDiag("SC1090", loc, feature); lowerer.runtimeFences.push(diag); - const line = ts.getLineAndCharacterOfPosition(blame.getSourceFile(), loc.start).line + 1; + const line = + ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(blame), loc.start).line + 1; const instance = { id: "instance.0", name: "instance", type, mutable: false }; const returned = { id: "returned.0", name: "returned", type: slot.type, mutable: false }; lowerer.liftedFns.push({ diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index 735361f54..5406c3ec1 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -304,7 +304,7 @@ function lowerFastCallback( fastLoweringDepth > 0 || slow.arity <= elemIndex || !ts.isArrowFunction(argNode) || - isJsSourceFile(argNode.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(argNode)) || valueT?.kind !== "union" || typeEquals(valueT, elem) || elem.kind === "union" || @@ -1489,7 +1489,7 @@ export function lowerArrayFlatMapCall( kind: "dynInvoke", recv: { kind: "dynFrom", value: receiver, liveRef: true, type: DYN, loc }, method: "flatMap", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [{ kind: "dynFrom", value: fnArg, type: DYN, loc }], type: DYN, loc, @@ -2085,7 +2085,7 @@ export function lowerArraySortCall( // Arithmetic on inferred JS values retains a checked result because // it can produce BigInt. The sort callback still requires a number. const result = - fnArg.type.ret.kind === "dyn" && isJsSourceFile(argNode.getSourceFile()) + fnArg.type.ret.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(argNode)) ? F64 : fnArg.type.ret; const expected = funcOf( @@ -2223,7 +2223,7 @@ export function lowerDynArrayFilterCall( recv, fnArg, { kind: "strLit", value: access.name.text, type: STRING, loc }, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: arrayOf(elemT), loc, @@ -2264,7 +2264,7 @@ export function lowerDynArrayFlatMapCall( kind: "dynInvoke", recv, method: "flatMap", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [boxed], type: DYN, loc, @@ -2286,7 +2286,7 @@ export function lowerDynArrayFlatMapCall( recv, fnArg, { kind: "strLit", value: access.name.text, type: STRING, loc }, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: arrayOf(ret.elem), loc, diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 110af2b56..53d46d00d 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -296,7 +296,7 @@ export function lowerArrayConstructor( // reference. Known element forms retain native arrays, including their // existing sparse storage and indexed property contracts. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (result?.kind !== "array" || result.elem.kind === "dyn") ) { return { @@ -321,7 +321,7 @@ export function lowerArrayConstructor( : null; if (elem !== null && isSupportedArrayElem(elem)) result = arrayOf(elem); } - if (isJsSourceFile(expr.getSourceFile()) && (result === null || result.kind === "dyn")) + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && (result === null || result.kind === "dyn")) result = arrayOf(DYN); if (result?.kind !== "array" || !isSupportedArrayElem(result.elem)) { lowerer.badType(expr, lowerer.typeOf(expr)); @@ -823,7 +823,7 @@ export function lowerArrayFromCall( const loc = locOf(call); const args = call.arguments.nodes; if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && args.length >= 1 && args.length <= 3 && !args.some(ts.isSpreadElement) diff --git a/packages/compiler/src/frontend/lowering/containers/array-methods.ts b/packages/compiler/src/frontend/lowering/containers/array-methods.ts index 7ef96457c..12078720e 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-methods.ts @@ -353,7 +353,7 @@ export function lowerArrayMethodCall( // machinery answered string[]): same rule, any-typed spelling. ((lowerer.typeOf(access.expression).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 && - isJsSourceFile(access.getSourceFile()))) + isJsSourceFile(ts.getSourceFileOfNode(access)))) ) { const probe = lowerer.lowerExpr(access.expression); if (probe.type.kind === "array") { @@ -1039,7 +1039,7 @@ export function lowerArrayMethodCall( kind: "dynInvoke", recv: receiver, method: "join", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -2096,7 +2096,7 @@ export function lowerTupleReadMethodCall( kind: "dynInvoke", recv: { kind: "dynFrom", value: receiver, type: DYN, loc }, method: "flatMap", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [{ kind: "dynFrom", value: fnArg, type: DYN, loc }], type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/containers/bytes.ts b/packages/compiler/src/frontend/lowering/containers/bytes.ts index c5a41cf32..f8e49a516 100644 --- a/packages/compiler/src/frontend/lowering/containers/bytes.ts +++ b/packages/compiler/src/frontend/lowering/containers/bytes.ts @@ -120,7 +120,7 @@ function lowerBytesSortCall( fnArg.type.params.every((param) => lowerer.coercibleValue(F64, param)) ) { const result = - fnArg.type.ret.kind === "dyn" && isJsSourceFile(argNode.getSourceFile()) + fnArg.type.ret.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(argNode)) ? F64 : fnArg.type.ret; const expected = funcOf( @@ -187,7 +187,7 @@ export function lowerBytesNew( ): IrExpr | null { if ( symbol != null && - (symbol.name === "ArrayBuffer" || symbol.name === "SharedArrayBuffer") && + (ts.symbolName(symbol) === "ArrayBuffer" || ts.symbolName(symbol) === "SharedArrayBuffer") && lowerer.isStdlibSymbol(symbol) ) { const args = expr.arguments?.nodes ?? []; @@ -203,18 +203,19 @@ export function lowerBytesNew( : dynUndefinedExpr(locOf(expr)); return { kind: "libCall", - fn: symbol.name === "SharedArrayBuffer" ? "sharedArrayBuffer.new" : "arrayBuffer.new", + fn: + ts.symbolName(symbol) === "SharedArrayBuffer" ? "sharedArrayBuffer.new" : "arrayBuffer.new", args: [length], type: DYN, loc: locOf(expr), }; } - if (symbol && symbol.name === "DataView" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "DataView" && lowerer.isStdlibSymbol(symbol)) { return lowerDataViewNew(lowerer, expr); } - const elem = symbol ? own(BYTES_CTORS, symbol.name) : undefined; + const elem = symbol ? own(BYTES_CTORS, ts.symbolName(symbol)) : undefined; if (!elem || !symbol || !lowerer.isStdlibSymbol(symbol)) return null; - const name = symbol.name; + const name = ts.symbolName(symbol); const type = bytesOf(elem); const loc = locOf(expr); const args = expr.arguments?.nodes ?? []; @@ -291,7 +292,7 @@ export function lowerBytesStaticCall( } if (access.name.text !== "from" || !ts.isIdentifier(access.expression)) return null; const symbol = lowerer.resolveValueSymbol(access.expression); - const elem = symbol ? own(BYTES_CTORS, symbol.name) : undefined; + const elem = symbol ? own(BYTES_CTORS, ts.symbolName(symbol)) : undefined; if (!elem || !lowerer.isStdlibSymbol(symbol ?? undefined)) return null; if ( call.arguments.nodes.length < 1 || @@ -442,7 +443,7 @@ export function lowerBytesMethodCall( const nArgs = call.arguments.nodes.length; // Encoded stream callbacks can receive strings even when JavaScript's // contextual inference still describes their parameter as a Buffer. - if (name === "toString" && nArgs <= 1 && isJsSourceFile(call.getSourceFile())) { + if (name === "toString" && nArgs <= 1 && isJsSourceFile(ts.getSourceFileOfNode(call))) { const lowered = lowerer.lowerExpr(access.expression); const receiver = lowered.kind === "dynCheck" && lowered.value.type.kind === "dyn" ? lowered.value : lowered; @@ -455,7 +456,7 @@ export function lowerBytesMethodCall( call.arguments.nodes[0] ? lowerer.lowerExprExpecting(call.arguments.nodes[0], DYN) : dynUndefinedExpr(loc), - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: STRING, loc, @@ -1334,7 +1335,8 @@ function lowerBufferStaticValue( args.length <= 3 && !args.some(ts.isSpreadElement) && ((symbol) => - symbol != null && (symbol.name === "ArrayBuffer" || symbol.name === "SharedArrayBuffer"))( + symbol != null && + (ts.symbolName(symbol) === "ArrayBuffer" || ts.symbolName(symbol) === "SharedArrayBuffer"))( lowerer.typeOf(args[0]!).getSymbol(), ) ) { @@ -1650,7 +1652,9 @@ function lowerBufferStaticValue( // flavor. Plain bytes are Uint8Arrays at this boundary; inspecting // only the bytes tag would incorrectly identify them as Buffers. const checked = - isJsSourceFile(call.getSourceFile()) && v.kind === "dynCheck" && v.value.type.kind === "dyn" + isJsSourceFile(ts.getSourceFileOfNode(call)) && + v.kind === "dynCheck" && + v.value.type.kind === "dyn" ? v.value : v; if (checked.type.kind === "dyn") { diff --git a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts index dcd9934bd..18754f088 100644 --- a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts +++ b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts @@ -59,7 +59,7 @@ export function lowerArrayCallback( if ( ts.isIdentifier(argNode) && argNode.text === "Boolean" && - !isJsSourceFile(argNode.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(argNode)) && lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode) ?? undefined) ) { const valueT = lead[0]; diff --git a/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts b/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts index 7c4ddf220..0bf126830 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts @@ -67,7 +67,7 @@ export function lowerGroupByStaticCall( const fNode = call.arguments.nodes[1]!; // An unannotated JavaScript callback parameter takes the element. const f = - isJsSourceFile(fNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(fNode)) && (ts.isArrowFunction(fNode) || ts.isFunctionExpression(fNode)) ? lowerArrayCallback(lowerer, fNode, [elem], groupT, true).fnArg : lowerer.lowerExpr(fNode); diff --git a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts index 6a5290c0f..6aa6d6faf 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts @@ -60,7 +60,7 @@ export function lowerMapMethodCall( if (!MAP_METHODS.has(name)) return null; let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); let probedUntyped = false; - if (receiverIr?.kind !== "map" && isJsSourceFile(access.getSourceFile())) { + if (receiverIr?.kind !== "map" && isJsSourceFile(ts.getSourceFileOfNode(access))) { const probed = tryLowerExpression(lowerer, access.expression); if (probed?.type.kind === "map") { receiverIr = probed.type; @@ -427,7 +427,7 @@ export function lowerCollectionSpread( loc, }, then: entries, - else_: nodeThrowExpr(1, "", `${node.getText()} is not iterable`, entries.type, loc), + else_: nodeThrowExpr(1, "", `${ts.getTextOfNode(node)} is not iterable`, entries.type, loc), type: entries.type, loc, }, @@ -544,7 +544,7 @@ export function lowerSetMethodCall( // Set, but the VALUE lowered as a real Set of // identity tokens (the new-Set probe) — the lowered receiver's type // is the honest dispatch key. - if (receiverIr?.kind !== "set" && isJsSourceFile(access.getSourceFile())) { + if (receiverIr?.kind !== "set" && isJsSourceFile(ts.getSourceFileOfNode(access))) { const probed = tryLowerExpression(lowerer, access.expression); if (probed?.type.kind === "set") { receiverIr = probed.type; @@ -973,7 +973,7 @@ export function lowerSetSeedNew( { kind: "libCall", fn: "dyn.iterPack", - args: [source, strLit(node.getText(), loc)], + args: [source, strLit(ts.getTextOfNode(node), loc)], type: DYN, loc, }, @@ -1165,7 +1165,7 @@ export function lowerMapSeedNew( } if (ts.isArrayLiteralExpression(argNode)) { if ( - isJsSourceFile(argNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(argNode)) && argNode.elements.nodes.some((entry) => !ts.isArrayLiteralExpression(entry)) ) { return mapFromSeedValue(lowerer, lowerer.lowerExprExpecting(argNode, DYN), mapT); diff --git a/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts b/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts index 523fda46e..32f90bfe7 100644 --- a/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts +++ b/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts @@ -142,7 +142,7 @@ export function dynStringReceiver( args: [ recv, { kind: "strLit", value: access.name.text, type: STRING, loc }, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: STRING, loc, diff --git a/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts b/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts index f2967c298..397bf6ec8 100644 --- a/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts +++ b/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts @@ -29,7 +29,7 @@ export function execResultCaptureMask(lowerer: Lowerer, call: ts.CallExpression) const symbol = lowerer.resolveValueSymbol(decl.name); if (!symbol) return null; const read = new Set(); - for (const use of identifiersNamed(lowerer, decl.getSourceFile(), decl.name.text)) { + for (const use of identifiersNamed(lowerer, ts.getSourceFileOfNode(decl), decl.name.text)) { if (use === decl.name || lowerer.resolveValueSymbol(use) !== symbol) continue; const ref = outerValue(use); const parent = ref.parent; diff --git a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts index 4017335ae..c23e6931c 100644 --- a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts +++ b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts @@ -684,7 +684,7 @@ function isHelperReceiver(lowerer: Lowerer, expr: ts.Expression): boolean { return ( mapped?.kind === "dyn" && symbol !== undefined && - ADAPTED_ITERATOR_TYPES.has(symbol.name) && + ADAPTED_ITERATOR_TYPES.has(ts.symbolName(symbol)) && lowerer.isStdlibSymbol(symbol) ); } diff --git a/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts b/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts index f936e27cb..794bd27c2 100644 --- a/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts +++ b/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts @@ -242,7 +242,7 @@ function constBindingReadsByElements( } function usesOf(lowerer: Lowerer, name: ts.Identifier, symbol: ts.Symbol): ts.Identifier[] { - return identifiersNamed(lowerer, name.getSourceFile(), name.text).filter( + return identifiersNamed(lowerer, ts.getSourceFileOfNode(name), name.text).filter( (use) => use !== name && lowerer.resolveValueSymbol(use) === symbol, ); } diff --git a/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts b/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts index e3ca2270d..b80eb7ac0 100644 --- a/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts +++ b/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts @@ -62,8 +62,9 @@ export class DeferredModuleInitializers { const info = exactClassOfReceiver(lowerer, owner); if ( info && - info.decl?.getSourceFile() === statement.getSourceFile() && - info.decl.getStart() < statement.getStart() && + info.decl !== null && + ts.getSourceFileOfNode(info.decl) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(info.decl) < ts.tokenStartOfNode(statement) && !info.localClass && !info.classDecorators && !info.generic && @@ -101,7 +102,7 @@ export class DeferredModuleInitializers { loc: locOf(statement), }; const context = lowerer.ctx; - const moduleIndex = [...lowerer.initNameOf.keys()].indexOf(statement.getSourceFile()); + const moduleIndex = [...lowerer.initNameOf.keys()].indexOf(ts.getSourceFileOfNode(statement)); const initializers = this.pending.get(key) ?? []; initializers.push({ loc: block.loc, @@ -269,14 +270,14 @@ function registryInitializer( return false; expression = expression.arguments.nodes[0]!; } - const packageSource = npmStaticPackageOfPath(statement.getSourceFile().fileName) !== null; + const packageSource = npmStaticPackageOfPath(ts.getSourceFileOfNode(statement).fileName) !== null; const initialized = (declaration: ts.Node): boolean => { - const source = declaration.getSourceFile(); - return source === statement.getSourceFile() - ? declaration.getStart() < statement.getStart() + const source = ts.getSourceFileOfNode(declaration); + return source === ts.getSourceFileOfNode(statement) + ? ts.tokenStartOfNode(declaration) < ts.tokenStartOfNode(statement) : [...lowerer.initNameOf.keys()].indexOf(source) >= 0 && [...lowerer.initNameOf.keys()].indexOf(source) < - [...lowerer.initNameOf.keys()].indexOf(statement.getSourceFile()); + [...lowerer.initNameOf.keys()].indexOf(ts.getSourceFileOfNode(statement)); }; const callable = (callee: ts.Expression): boolean => { if (!ts.isIdentifier(callee)) return false; @@ -324,7 +325,7 @@ function registryInitializer( // bundlers. Arguments still need to be safe to omit: an annotation // never licenses dropping argument effects or a throwing property read. if (packageSource && (ts.isCallExpression(node) || ts.isNewExpression(node))) { - const trivia = node.getSourceFile().text.slice(node.pos, node.getStart()); + const trivia = ts.getSourceFileOfNode(node).text.slice(node.pos, ts.tokenStartOfNode(node)); if (!/[@#]__PURE__/.test(trivia)) return false; if (!callable(node.expression)) return false; return (node.arguments?.nodes ?? []).every((argument) => value(argument, depth + 1)); @@ -337,8 +338,8 @@ function registryInitializer( const info = exactClassOfReceiver(lowerer, node.expression.expression); return ( !!info?.decl && - info.decl.getSourceFile() === statement.getSourceFile() && - info.decl.getStart() < statement.getStart() && + ts.getSourceFileOfNode(info.decl) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(info.decl) < ts.tokenStartOfNode(statement) && classWriteIsUnobserved(lowerer, info, statement) && (findMethodOn(lowerer, info, node.name.text) !== null || findGenericMethodOn(lowerer, info, node.name.text) !== null) @@ -398,7 +399,7 @@ function registryInitializer( if ( functionDeclaration && ts.isFunctionDeclaration(functionDeclaration) && - functionDeclaration.getSourceFile() === statement.getSourceFile() + ts.getSourceFileOfNode(functionDeclaration) === ts.getSourceFileOfNode(statement) ) return true; // Reading an earlier initialized binding executes none of its @@ -410,8 +411,8 @@ function registryInitializer( functionDeclaration && ts.isVariableDeclaration(functionDeclaration) && functionDeclaration.initializer && - functionDeclaration.getSourceFile() === statement.getSourceFile() && - functionDeclaration.getStart() < statement.getStart() + ts.getSourceFileOfNode(functionDeclaration) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(functionDeclaration) < ts.tokenStartOfNode(statement) ) return true; const info = symbol && lowerer.classBySymbol.get(symbol); @@ -421,8 +422,8 @@ function registryInitializer( // decorated classes and local factories keep ordinary initialization. return ( !!declaration && - declaration.getSourceFile() === statement.getSourceFile() && - declaration.getStart() < statement.getStart() && + ts.getSourceFileOfNode(declaration) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(declaration) < ts.tokenStartOfNode(statement) && ts.isClassDeclaration(declaration) && !info!.classDecorators && !info!.generic && @@ -476,19 +477,20 @@ function registryOwnerIsData( ) ) return false; - if (declaration.getSourceFile() !== statement.getSourceFile()) { + if (ts.getSourceFileOfNode(declaration) !== ts.getSourceFileOfNode(statement)) { const modules = [...lowerer.initNameOf.keys()]; return ( !!frozen && - modules.indexOf(declaration.getSourceFile()) >= 0 && - modules.indexOf(declaration.getSourceFile()) < modules.indexOf(statement.getSourceFile()) + modules.indexOf(ts.getSourceFileOfNode(declaration)) >= 0 && + modules.indexOf(ts.getSourceFileOfNode(declaration)) < + modules.indexOf(ts.getSourceFileOfNode(statement)) ); } - if (declaration.getStart() >= statement.getStart()) return false; + if (ts.tokenStartOfNode(declaration) >= ts.tokenStartOfNode(statement)) return false; if (frozen) return true; let safe = true; const visit = (node: ts.Node): void => { - if (!safe || node.getStart() >= statement.getStart()) return; + if (!safe || ts.tokenStartOfNode(node) >= ts.tokenStartOfNode(statement)) return; if ( ts.isIdentifier(node) && node !== declaration.name && @@ -515,7 +517,7 @@ function registryOwnerIsData( } ts.forEachChild(node, visit); }; - visit(statement.getSourceFile()); + visit(ts.getSourceFileOfNode(statement)); return safe; } @@ -527,7 +529,7 @@ function classWriteIsUnobserved( info: ClassInfo, statement: ts.Statement, ): boolean { - const source = statement.getSourceFile(); + const source = ts.getSourceFileOfNode(statement); const declaration = info.decl!; if ( declaration.members.nodes.some( @@ -543,7 +545,10 @@ function classWriteIsUnobserved( ) return false; for (const earlier of source.statements.nodes) { - if (earlier.getStart() <= declaration.getStart() || earlier.getStart() >= statement.getStart()) + if ( + ts.tokenStartOfNode(earlier) <= ts.tokenStartOfNode(declaration) || + ts.tokenStartOfNode(earlier) >= ts.tokenStartOfNode(statement) + ) continue; if ( ts.isFunctionDeclaration(earlier) || diff --git a/packages/compiler/src/frontend/lowering/divergences.ts b/packages/compiler/src/frontend/lowering/divergences.ts index a77f33c6b..1c362cb09 100644 --- a/packages/compiler/src/frontend/lowering/divergences.ts +++ b/packages/compiler/src/frontend/lowering/divergences.ts @@ -281,7 +281,7 @@ export function scanDivergences( ts.isVariableDeclaration(decl) && decl.initializer !== undefined && ts.isObjectLiteralExpression(decl.initializer) && - decl.getSourceFile() === inner.getSourceFile() + ts.getSourceFileOfNode(decl) === ts.getSourceFileOfNode(inner) ) { lit = decl.initializer; binding = inner.text; @@ -291,7 +291,9 @@ export function scanDivergences( const contextual = safely(() => checker.getContextualType(lit!)); if (contextual === undefined) continue; if (namedObjectDeclaration(checker, contextual) === null) continue; - const declared = safely(() => checker.getPropertiesOfType(contextual).map((p) => p.name)); + const declared = safely(() => + checker.getPropertiesOfType(contextual).map((p) => ts.symbolName(p)), + ); if (declared === undefined) continue; const written = lit.properties.nodes.map((p) => propertyNameText(p)!); const assigned = @@ -463,7 +465,7 @@ function identityUse(root: ts.Node, name: string, from = -1): IdentityUse | null return cur; }; const textOf = (node: ts.Node): string => { - const text = node.getText(); + const text = ts.getTextOfNode(node); return text.length > 60 ? `${text.slice(0, 57)}...` : text; }; const visit = (node: ts.Node): "skip" | "stop" | undefined => { @@ -591,7 +593,7 @@ function elementWrite(root: ts.Node, name: string, from: number): string | null ) target = node.operand; if (target !== undefined && throughElement(target)) { - found = target.getText(); + found = ts.getTextOfNode(target); return "stop"; } if ( @@ -670,8 +672,8 @@ function widthCopyBetween( // still observes the same writes: no divergence to report. const accessor = ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor; if (sourceProps.some((p) => (p.flags & accessor) !== 0)) return null; - const sourceNames = new Set(sourceProps.map((p) => p.name)); - const targetNames = checker.getPropertiesOfType(t).map((p) => p.name); + const sourceNames = new Set(sourceProps.map((p) => ts.symbolName(p))); + const targetNames = checker.getPropertiesOfType(t).map((p) => ts.symbolName(p)); if (targetNames.length === sourceNames.size && targetNames.every((n) => sourceNames.has(n))) return null; } @@ -684,7 +686,7 @@ function isProgramClassType(checker: ts.TypeChecker, type: ts.Type): boolean { return ( symbol !== undefined && (symbol.flags & ts.SymbolFlags.Class) !== 0 && - symbol.declarations.some((d) => !d.getSourceFile().isDeclarationFile) + symbol.declarations.some((d) => !ts.getSourceFileOfNode(d).isDeclarationFile) ); } @@ -698,7 +700,7 @@ function isPlainRecordType(checker: ts.TypeChecker, type: ts.Type): boolean { if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Class) !== 0) return false; // Library-declared object types (Map, Date, Promise, ...) are not records. const decls = symbol === undefined ? [] : symbol.declarations; - if (decls.some((d) => d.getSourceFile().isDeclarationFile)) return false; + if (decls.some((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) return false; return true; } @@ -711,7 +713,7 @@ function namedObjectDeclaration(checker: ts.TypeChecker, type: ts.Type): ts.Node if (symbol === undefined) return null; if ((symbol.flags & ts.SymbolFlags.Class) !== 0) return null; const decl = symbol.declarations[0]; - if (decl === undefined || decl.getSourceFile().isDeclarationFile) return null; + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; return decl; } @@ -765,7 +767,7 @@ function classDeclarationOfTypeNode( const symbol = lowerer.checker.getSymbolAtLocation(node.typeName); if (symbol === undefined || (symbol.flags & ts.SymbolFlags.Class) === 0) return null; const decl = symbol.declarations.find((d) => ts.isClassDeclaration(d) || ts.isClassExpression(d)); - if (decl === undefined || decl.getSourceFile().isDeclarationFile) return null; + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; return decl as ts.ClassLikeDeclaration; } @@ -776,7 +778,7 @@ function classDeclarationOfType( const symbol = type.getSymbol(); if (symbol === undefined || (symbol.flags & ts.SymbolFlags.Class) === 0) return null; const decl = symbol.declarations.find((d) => ts.isClassDeclaration(d) || ts.isClassExpression(d)); - if (decl === undefined || decl.getSourceFile().isDeclarationFile) return null; + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; return decl as ts.ClassLikeDeclaration; } diff --git a/packages/compiler/src/frontend/lowering/dyn-streams.ts b/packages/compiler/src/frontend/lowering/dyn-streams.ts index 9694e3820..51bcf0ea8 100644 --- a/packages/compiler/src/frontend/lowering/dyn-streams.ts +++ b/packages/compiler/src/frontend/lowering/dyn-streams.ts @@ -34,14 +34,18 @@ export function nodeStreamInterfaceSide(lowerer: Lowerer, type: ts.Type): "r" | (t.flags & ts.TypeFlags.Intersection) !== 0 ? ts.constituentTypes(t).flatMap(parts) : [t]; for (const part of parts(lowerer.checker.getNonNullableType(type))) { const psym = part.getSymbol() ?? part.getAliasSymbol(); - if (psym?.name !== "ReadableStream" && psym?.name !== "WritableStream") continue; + if ( + (psym === undefined || ts.symbolName(psym) !== "ReadableStream") && + (psym === undefined || ts.symbolName(psym) !== "WritableStream") + ) + continue; const declared = psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - lowerer.isStdlibFile(d.getSourceFile()) && + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d, "NodeJS"), ); - if (declared) return psym.name === "ReadableStream" ? "r" : "w"; + if (declared) return ts.symbolName(psym) === "ReadableStream" ? "r" : "w"; } return null; } @@ -161,7 +165,7 @@ export function lowerDynStreamMemberCall( kind: "dynInvoke", recv: receiverRef, method: access.name.text, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts index 38aa4f8aa..56b6e280e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts @@ -83,7 +83,7 @@ export function lowerArrayLiteral( // structurally equal checker types. Their native class ABIs still differ. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind === "array" && mapped.elem.kind === "classval" ) @@ -202,7 +202,7 @@ export function lowerArrayLiteral( // keep the checked-dynamic fallback below. if ( lowerer.implicitParamTypes !== null && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null && + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null && (mapped === null || mapped.kind === "dyn" || (mapped.kind === "array" && mapped.elem.kind === "dyn")) && @@ -222,7 +222,7 @@ export function lowerArrayLiteral( mapped = arrayOf(first.type); } } - if (!expected && isJsSourceFile(expr.getSourceFile()) && mapped?.kind === "array") { + if (!expected && isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind === "array") { const elem = mapped.elem; const arms = elem.kind === "union" ? lowerer.unions.get(elem.unionId)?.arms : [elem]; if (arms?.length && arms.every((arm) => arm.kind === "classval")) mapped = DYN; @@ -234,7 +234,7 @@ export function lowerArrayLiteral( } if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.elements.nodes.some( (element) => ts.isSpreadElement(element) && @@ -247,7 +247,7 @@ export function lowerArrayLiteral( // only in a checked-dynamic array: a static element type would copy it. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind !== "dyn" && expr.elements.nodes.some( (element) => !ts.isSpreadElement(element) && jsHoldsDynObject(lowerer, element), @@ -269,7 +269,7 @@ export function lowerArrayLiteral( // real keys. A structural union can otherwise drop descriptor fields. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind === "array" && mapped.elem.kind === "union" && expr.elements.nodes.some(ts.isObjectLiteralExpression) @@ -316,7 +316,7 @@ export function lowerArrayLiteral( // to DYN wholesale now (mapType's dyn-element array rule), so the // literal IS the dyn array. if ( - isJsSourceFile(expr.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(expr)) || mapped?.kind === "dyn" || lowerer.checkerAnyArray(expr) ) { diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index 8ef631f60..07d983cc0 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -531,7 +531,7 @@ export function lowerNativeFunctionAssignment( ): IrExpr | null { const target = expr.left; if ( - !isJsSourceFile(expr.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(expr)) || (!ts.isPropertyAccessExpression(target) && !ts.isElementAccessExpression(target)) || target.questionDotToken ) @@ -903,7 +903,7 @@ export function lowerCompoundValueToTarget( // catchable TypeError, as the expanded assignment does). if ( (target.type.kind === "dyn" || rhs.type.kind === "dyn") && - (isJsSourceFile(expr.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || (lowerer.dynConvertible(read.type) && lowerer.dynConvertible(rhs.type))) ) { const left = lowerer.coerceInto(expr.left, read, DYN); @@ -1142,7 +1142,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr recv && (recv.type.kind === "dyn" || (recv.type.kind === "array" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(recv.type))) ) { return lowerDynMemberAssignment( @@ -1534,7 +1534,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr // Numeric operators dispatch on ToNumeric's actual Number/BigInt // result, including values flowing through native callable properties. if ( - isJsSourceFile(expr.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(expr)) || op === ts.SyntaxKind.PlusToken || [ ts.SyntaxKind.LessThanToken, @@ -2409,7 +2409,7 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE } if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && op === ts.SyntaxKind.AmpersandAmpersandToken && left.type.kind === "union" && lowerer.unions.get(left.type.unionId)?.arms.some((arm) => arm.kind === "bool") && @@ -2457,7 +2457,7 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE op === ts.SyntaxKind.AmpersandAmpersandToken && target === null && lowerer.implicitParamTypes !== null && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null && + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null && (lowerer.typeOf(expr).flags & ts.TypeFlags.Any) !== 0 && left.type.kind === "union" ) { @@ -2618,7 +2618,7 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE // When both operands have native checked representations, preserve // the deciding value and short-circuit evaluation in that domain. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type) ) { diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index f39b7c428..7ef4b9f25 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -231,7 +231,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre let receiverIr = neverTaintedJsType(lowerer, expr.expression, lowerer.typeOf(expr.expression)) ? null : lowerer.mapTypeOf(lowerer.typeOf(expr.expression)); - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const receiver = tryLowerExpression(lowerer, expr.expression); if (receiver?.type.kind === "dyn") { const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN); @@ -316,7 +316,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre locOf(expr.argumentExpression), expr.argumentExpression, ); - if (isJsSourceFile(expr.getSourceFile()) && recv.type.kind === "bytes") { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv.type.kind === "bytes") { return { kind: "dynKeyGet", value: lowerer.coerceInto(expr.expression, recv, DYN), @@ -565,7 +565,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre if ( receiver?.type.kind === "dyn" || (receiver && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (receiver.type.kind === "union" || receiver.type.kind === "bytes") && lowerer.dynConvertible(receiver.type)) ) { @@ -769,7 +769,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre if ( arr.type.kind === "array" && (index.type.kind === "dyn" || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && index.type.kind !== "f64" && lowerer.dynConvertible(index.type))) ) { @@ -822,7 +822,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre arr.type.kind === "array" && arr.kind === "arrIntrinsic" && arr.method === "slice" && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null ) { const safe = lowerSafeIndexRead(lowerer, arr, index, locOf(expr)); if (safe) return safe; @@ -1306,7 +1306,11 @@ export function lowerRecordKeyRead( ) { declared = shape.fields[0]!.type; } - if (!declared && isJsSourceFile(expr.getSourceFile()) && recordKeyResultOk(lowerer, shape, DYN)) + if ( + !declared && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + recordKeyResultOk(lowerer, shape, DYN) + ) declared = DYN; if (!declared) { lowerer.unsupported( @@ -1425,7 +1429,7 @@ export function lowerElementCompoundParts( if (isKeyedFieldAccess(lowerer, target)) return { expr: lowerFieldCompoundValue(lowerer, target, op, expr.right, loc), stmts: null }; const checked = lowerer.lowerExpr(target.expression); - if (checked?.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + if (checked?.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const stmts: IrStmt[] = []; const save = (value: IrExpr, name: string): IrExpr => { const local = lowerer.declareHiddenLocal(name, value.type); @@ -1516,7 +1520,7 @@ export function lowerElementCompoundParts( const rhs = lowerer.lowerExpr(expr.right); const numericRhs = rhs.type.kind === "dyn" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && receiver.type.kind === "bytes" ? { kind: "libCall" as const, fn: "dyn.toNumberCoerce" as const, args: [rhs], type: F64, loc } : lowerOptionalNumber(lowerer, rhs, loc); @@ -1566,7 +1570,7 @@ export function lowerElementCompoundParts( type: F64, loc, }; - } else if (valueType.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + } else if (valueType.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const left = lowerer.coerceToExpected(oldRef(), DYN), right = lowerer.coerceToExpected(rhs, DYN); computed = dynCompoundValue(op, left, right, loc); @@ -1700,7 +1704,7 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): loc: locOf(expr), }; if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && receiver.type.kind === "object" && lowerer.dynConvertible(receiver.type) ) { @@ -1724,7 +1728,7 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): loc: locOf(expr), }; } - if (checkedReceiver?.type.kind === "object" && isJsSourceFile(expr.getSourceFile())) { + if (checkedReceiver?.type.kind === "object" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const info = lowerer.classes.get(checkedReceiver.type.className); const key = recordKeyLiteralText(target.argumentExpression) ?? @@ -1739,7 +1743,7 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): } } let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(target.expression)); - if (isJsSourceFile(expr.getSourceFile()) && checkedReceiver) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && checkedReceiver) { const represented = checkedReceiver.type.kind === "union" ? lowerer.stripUndefinedArm(checkedReceiver.type) diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index c2e5ddcbb..b5f99d682 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -133,7 +133,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: ); } if (projection?.kind === "builtin-function") { - if (!isJsSourceFile(expr.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) { const callable = lowerer.lowerBuiltinCallableValue( { module: projection.module, member: projection.member }, loc, @@ -149,7 +149,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // `EventEmitter`, `Readable` or `TypeError` (or a const alias of // one) as a value in a TypeScript file: the runtime class's class // object. JavaScript files keep the identity token. - if (!isJsSourceFile(expr.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) { const runtimeClass = runtimeClassValueOf(lowerer, expr); if (runtimeClass) return lowerer.classValueRef(runtimeClass, expr); } @@ -187,7 +187,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: const clause = decl.parent.parent.parent; if ( (ts.isCaseClause(clause) || ts.isDefaultClause(clause)) && - !(expr.getStart() >= clause.getStart() && expr.end <= clause.end) + !(ts.tokenStartOfNode(expr) >= ts.tokenStartOfNode(clause) && expr.end <= clause.end) ) { lowerer.unsupported( "SC1090", @@ -385,7 +385,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: (expr.text === "__dirname" || expr.text === "__filename") && lowerer.isStdlibGlobal(expr, expr.text) ) { - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); if (isNodeEsmFile(sf, lowerer.program) && !/\.tsx?$/.test(sf.fileName)) { return { kind: "libCall", @@ -552,7 +552,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // JavaScript sources: an otherwise unsupported builtin VALUE is // the same identity-token story as stdlib globals above (the // harness adds worker_threads.Worker to its identity Set). - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { // EventEmitter and the stream classes are the runtime's untyped // constructors, so constructor functions can inherit from them. const classKind = builtinMemberClassKind(bi.module, bi.member); @@ -652,7 +652,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // type-mapper.ts pins the one concrete signature `(value: string) => // primitive`; direct calls `String(x)` never reach here — the call // lowering intercepts them with the wider static coercions). - const jsPrimitiveCtorSelector = isJsSourceFile(expr.getSourceFile()); + const jsPrimitiveCtorSelector = isJsSourceFile(ts.getSourceFileOfNode(expr)); const primitiveName = stdlibGlobalNameOf(lowerer, expr); if ( primitiveName === "String" || @@ -664,7 +664,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: } if (lowerer.isStdlibGlobal(expr, "ArrayBuffer")) return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc }; - if (isJsSourceFile(expr.getSourceFile()) && lowerer.isStdlibGlobal(expr, "Array")) + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.isStdlibGlobal(expr, "Array")) return { kind: "libCall", fn: "dyn.arrayConstructor", args: [], type: DYN, loc }; if ( Object.values(BYTES_ELEMENT_NAME).includes(expr.text) && @@ -763,10 +763,10 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: expr, canonical, loc, - isJsSourceFile(expr.getSourceFile()), + isJsSourceFile(ts.getSourceFileOfNode(expr)), ); if (builtinValue !== null) return builtinValue; - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc }; } // The families with a WHY: each hint states what makes the @@ -986,7 +986,7 @@ function cjsExportAccessorRead(lowerer: Lowerer, ident: ts.Identifier): IrExpr | if ( binding !== undefined && (ts.isImportSpecifier(binding) || ts.isExportSpecifier(binding)) && - !isCjsJsFile(binding.getSourceFile(), lowerer.program) + !isCjsJsFile(ts.getSourceFileOfNode(binding), lowerer.program) ) { const snapshot = cjsAccessorSnapshotOf(lowerer, getter); if (snapshot !== null) diff --git a/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts b/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts index 7a4f9c557..e53d27aa9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts +++ b/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts @@ -44,7 +44,7 @@ export function literalFieldKeys(lowerer: Lowerer, keyNode: ts.Expression): stri export function isKeyedFieldAccess(lowerer: Lowerer, access: ts.ElementAccessExpression): boolean { if ( access.questionDotToken || - isJsSourceFile(access.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(access)) || lowerer.isProcessEnv(access.expression) ) return false; diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 9f98ac2db..dec06fdd9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -92,7 +92,7 @@ export function lowerTernary( ) { falseArmNarrows.push(n); } - n.forEachChild(collect); + ts.forEachChild(n, collect); }; collect(expr.whenFalse); } @@ -333,7 +333,7 @@ export function lowerTernary( // call ABI. A checker join can erase a rest/arguments pack even though // a later call supplies additional observable arguments. const callableJoin = - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && thenRaw.type.kind === "func" && elseRaw.type.kind === "func" && !typeEquals(thenRaw.type, elseRaw.type) && @@ -343,7 +343,7 @@ export function lowerTernary( // a conditional's type even after that field acquired other values. // A checked arm must retain its actual representation at the join. const checkedJsJoin = - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (thenRaw.type.kind === "dyn" || elseRaw.type.kind === "dyn") && lowerer.dynConvertible(thenRaw.type) && lowerer.dynConvertible(elseRaw.type); @@ -385,7 +385,7 @@ export function lowerTernary( export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrExpr { if ( (expr.type.kind === "union" || expr.type.kind === "dyn") && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isPropertyAccessExpression(node) ) { const symbol = lowerer.checker.getSymbolAtLocation(node.name); @@ -396,7 +396,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx ts.isPropertyDeclaration(decl) && !decl.initializer && !decl.type && - isJsSourceFile(decl.getSourceFile()), + isJsSourceFile(ts.getSourceFileOfNode(decl)), ) ) { // Inference describes later writes, not the initial undefined. @@ -416,7 +416,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx // An inferred JS member's storage may be deliberately wider than its // declaration (prototype-selected buffers, null-initialized scratch // objects). Extract at the consuming operation, not at the read. - if (isJsSourceFile(node.getSourceFile()) && ts.isPropertyAccessExpression(node)) { + if (isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isPropertyAccessExpression(node)) { const receiver = lowerer.mapTypeOf(lowerer.typeOf(node.expression)); if (receiver?.kind === "object") { const info = lowerer.classes.get(receiver.className); @@ -432,7 +432,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx } } if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) ) { let receiver = node.expression; @@ -461,7 +461,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx if (bound && lowerer.mapTypeOf(bound)?.kind === "dyn") return expr; } } - if (isJsSourceFile(node.getSourceFile()) && ts.isIdentifier(node)) { + if (isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isIdentifier(node)) { const symbol = lowerer.resolveValueSymbol(node); if ( symbol && @@ -495,7 +495,9 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx decl.initializer && !decl.type && !/@(?:param|type)\b/.test( - decl.getSourceFile().text.slice(decl.parent?.pos ?? decl.pos, decl.getStart()), + ts + .getSourceFileOfNode(decl) + .text.slice(decl.parent?.pos ?? decl.pos, ts.tokenStartOfNode(decl)), ), ) ) @@ -522,7 +524,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx // has no such property. Preserve the checked read for JS capability // probes instead of treating a declaration as a runtime type guard. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isPropertyAccessExpression(node) && ["isTTY", "columns", "rows"].includes(node.name.text) && lowerer.isStdlibMember(node) @@ -532,7 +534,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx // checker flow calls its non-null branch never. The live checked // value remains authoritative; never is not a numeric guard. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && (lowerer.typeOf(node).flags & ts.TypeFlags.Never) !== 0 ) return expr; @@ -839,11 +841,11 @@ export function v8CalleeText(node: ts.Node): string { const object = v8CalleeText(node.expression); return ts.isStringLiteral(key) || ts.isNoSubstitutionTemplateLiteral(key) ? `${object}.${key.text}` - : `${object}[${key.getText()}]`; + : `${object}[${ts.getTextOfNode(key)}]`; } if (ts.isPropertyAccessExpression(node) && !node.questionDotToken) return `${v8CalleeText(node.expression)}.${node.name.text}`; - return node.getText(); + return ts.getTextOfNode(node); } /** Node's TypeError for the operation consuming `node` when it holds a diff --git a/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts b/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts index e3b9bf025..51b7f4028 100644 --- a/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts +++ b/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts @@ -25,7 +25,7 @@ export function lowerNativeProxy(lowerer: Lowerer, expr: ts.NewExpression): IrEx if (args.length !== 2 || args.some(ts.isSpreadElement)) lowerer.noLowering("new Proxy with this argument list", expr); const handler = args[1]!; - if (isJsSourceFile(expr.getSourceFile()) && ts.isObjectLiteralExpression(handler)) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && ts.isObjectLiteralExpression(handler)) { for (const prop of handler.properties.nodes) { const callback = ts.isMethodDeclaration(prop) ? prop diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 77c4c8fc5..303b6ed55 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -276,7 +276,7 @@ export function lowerDynObjectLiteral( const raw = builtin === "path" || builtin === "path/posix" || builtin === "path/win32" ? pathModuleValue(lowerer, builtin === "path" ? "path/posix" : builtin, locOf(prop)) - : isJsSourceFile(expr.getSourceFile()) + : isJsSourceFile(ts.getSourceFileOfNode(expr)) ? lowerer.lowerExprExpecting(prop.expression, DYN) : lowerer.lowerExpr(prop.expression); fenceSymbolFieldCopy(lowerer, prop.expression, raw.type); @@ -410,7 +410,7 @@ export function lowerDynObjectLiteral( // Repeated references must compare equal and mutations remain shared. if ( !boxValue && - isJsSourceFile(prop.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(prop)) && v.kind === "dynFrom" && v.value.type.kind === "array" ) { @@ -495,7 +495,7 @@ function fenceClosureProbe( slotType: IrType | undefined, attempt: () => IrExpr, ): IrExpr | null { - if (!isJsSourceFile(node.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) return null; let inner: ts.Node = node; while (ts.isParenthesizedExpression(inner)) inner = inner.expression; if ( @@ -580,7 +580,10 @@ function literalUnionArmOf( if (own.isUnionType()) return null; props.length = 0; for (const property of lowerer.checker.getPropertiesOfType(own)) { - props.push({ name: property.name, type: lowerer.checker.getTypeOfSymbol(property) }); + props.push({ + name: ts.symbolName(property), + type: lowerer.checker.getTypeOfSymbol(property), + }); } } /** Cheap literal discriminants avoid checker traffic for unrelated arms. @@ -766,7 +769,7 @@ export function lowerObjectLiteral( // Preserve their checked arrays instead of assigning never[] a numeric ABI. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some( (prop) => ts.isPropertyAssignment(prop) && @@ -779,7 +782,7 @@ export function lowerObjectLiteral( } if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some( (prop) => ts.isPropertyAssignment(prop) && @@ -794,7 +797,7 @@ export function lowerObjectLiteral( // the represented binding rather than checking it back into that type. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some((prop) => { let value = ts.isPropertyAssignment(prop) ? prop.initializer @@ -822,7 +825,7 @@ export function lowerObjectLiteral( // JavaScript object methods/accessors carry a live receiver. Keep the // literal in checked native storage instead of erasing its prototype ABI. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.properties.nodes.length === 0 || expr.properties.nodes.some( (prop) => @@ -838,7 +841,7 @@ export function lowerObjectLiteral( // binding. Construct in that representation: an intermediate record would // check nested aliases back into inferred shapes and lose their identity. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && ts.isVariableDeclaration(expr.parent) && expr.parent.initializer === expr && ts.isIdentifier(expr.parent.name) && @@ -850,7 +853,7 @@ export function lowerObjectLiteral( // methods absent from its inferred record shape. Copy the real properties. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some( (property) => ts.isSpreadAssignment(property) && @@ -870,7 +873,7 @@ export function lowerObjectLiteral( // values. TypeScript keeps the record world and its fence: this shape // only arises in checked-dynamic JS. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some((p) => { const n = ts.isSpreadAssignment(p) ? undefined : p.name; return n !== undefined && ts.isComputedPropertyName(n); @@ -917,7 +920,7 @@ export function lowerObjectLiteral( // receiver (capturing the record under construction would be an // RC cycle), and the generic lexical-this walk would incorrectly // capture an ENCLOSING method's `this` — the object-method rule. - if (!isJsSourceFile(expr.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) { rejectThisInObjectAccessor(lowerer, prop.body ?? prop); } } @@ -993,7 +996,7 @@ export function lowerObjectLiteral( // `null`, which maps to nothing on its own). if ( tsType.isUnionType() && - ts.constituentTypes(tsType).some((t) => t.getSymbol()?.name === "PromiseLike") + ts.constituentTypes(tsType).some((t) => ts.symbolNameOf(t.getSymbol()) === "PromiseLike") ) { tsType = lowerer.checker.getAwaitedType(tsType) ?? tsType; } @@ -1014,7 +1017,7 @@ export function lowerObjectLiteral( // A JavaScript call can contextually type an object literal as string even // though the literal itself is a record. Keep its own shape so a caller // performing ToString can convert it after the value is built. - if (mapped?.kind === "string" && isJsSourceFile(expr.getSourceFile())) { + if (mapped?.kind === "string" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const own = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (own?.kind === "record") mapped = own; } @@ -1147,7 +1150,7 @@ export function lowerObjectLiteral( // (rest-param function types) narrow away. SEMANTICS.md documents the // enumeration divergence. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (!mapped || expr.properties.nodes.some( (p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p), @@ -1187,7 +1190,7 @@ export function lowerObjectLiteral( // consumers ride the keyed-dyn paths. TypeScript keeps the fence. if ( mapped?.kind === "dyn" || - (!mapped && isJsSourceFile(expr.getSourceFile())) || + (!mapped && isJsSourceFile(ts.getSourceFileOfNode(expr))) || (!expected && mapped && jsOpenObjectType(expr, mapped, lowerer.shapes, lowerer.unions).kind === "dyn") @@ -1203,7 +1206,7 @@ export function lowerObjectLiteral( // extra fields, spreads, accessors, and keyed shapes retain their paths. if ( expected && - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.every( (p) => ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p), ) @@ -1255,7 +1258,7 @@ export function lowerObjectLiteral( // JavaScript Dates carry native object identity in checked storage. An // inferred scalar Date field would erase that identity before boxing. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && shape.fields.some((field) => field.type.kind === "date") ) { return lowerDynObjectLiteral(lowerer, expr); @@ -1265,7 +1268,7 @@ export function lowerObjectLiteral( // so the literal's shape NARROWS to its plain fields (reads resolve // through the lifted accessors; Object.keys over the value omits // accessor names — the documented divergence). - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const accessorNames = new Set( expr.properties.nodes .filter((p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p)) @@ -1301,7 +1304,7 @@ export function lowerObjectLiteral( ); if ( topLevelJsDecl && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && assignedProperties.length === expr.properties.nodes.length ) { const provided = new Set(assignedProperties.map((prop) => propNameText(lowerer, prop.name))); @@ -1328,7 +1331,7 @@ export function lowerObjectLiteral( // multiple spreads, spread-after-explicit order, and shape-changing width // copies retain their existing lowering and diagnostics. if ( - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && !shape.indexValue && !shape.tuple && !shapeHasAccessorSlots(shape) && @@ -2219,7 +2222,7 @@ export function lowerObjectLiteral( // closures is side-effect-free, so their position among the data // fields' source-order evaluation is unobservable — exactly JS, // where accessor definitions evaluate nothing. - if (isJsSourceFile(expr.getSourceFile())) continue; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) continue; const name = propNameText(lowerer, prop.name); // key-form-checked above const slotName = `${ts.isGetAccessorDeclaration(prop) ? "%get" : "%set"}:${name}`; const slotT = fieldTypes.get(slotName); @@ -2341,7 +2344,7 @@ export function lowerObjectLiteral( if ( !(err instanceof PoisonError) || !pureMember || - !isJsSourceFile(expr.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(expr)) || lowerer.diagSink !== null || lowerer.diags.length <= propDiagsBefore || lowerer.diags.slice(propDiagsBefore).some((d) => d.code === "SC9001") @@ -2436,7 +2439,7 @@ export function lowerObjectLiteral( * their identity. Changing a discriminator or adding variant-specific * fields needs a different result layout and stays on the ordinary path. */ function lowerUnionRecordClone(lowerer: Lowerer, expr: ts.ObjectLiteralExpression): IrExpr | null { - if (isJsSourceFile(expr.getSourceFile())) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; const spread = expr.properties.nodes[0]; if (!spread || !ts.isSpreadAssignment(spread)) return null; let sourceNode = spread.expression; diff --git a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts index 995514a4d..5dc512a6c 100644 --- a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts +++ b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts @@ -92,7 +92,7 @@ export function isRequireMainFilename(lowerer: Lowerer, expr: ts.Expression): bo const main = expr.expression; if (!ts.isPropertyAccessExpression(main) || main.name.text !== "main") return false; if (!lowerer.isStdlibGlobal(main.expression, "require")) return false; - return !isNodeEsmFile(expr.getSourceFile(), lowerer.program); + return !isNodeEsmFile(ts.getSourceFileOfNode(expr), lowerer.program); } export function isOptionalChainTail(lowerer: Lowerer, expr: ts.Expression): boolean { @@ -257,14 +257,14 @@ export function lowerOptionalChain( const spreads: { arg: number; what: string }[] = []; const args = expr.arguments.nodes.map((arg, index) => { if (!ts.isSpreadElement(arg)) return lowerer.lowerExprExpecting(arg, DYN); - spreads.push({ arg: index, what: arg.expression.getText() }); + spreads.push({ arg: index, what: ts.getTextOfNode(arg.expression) }); return lowerer.lowerExprExpecting(arg.expression, DYN); }); const body: IrExpr = { kind: "dynCall", callee, ...(thisValue ? { receiver: thisValue } : {}), - calleeName: recvNode.getText(), + calleeName: ts.getTextOfNode(recvNode), args, ...(spreads.length ? { spreads } : {}), type: DYN, diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 22bcb6609..36ea0e8c2 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -205,7 +205,7 @@ function isParseArgsDynProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpress const sym = lowerer.checker.getSymbolAtLocation(expr.name); if (!sym) return false; return sym.declarations.some((d) => { - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; let inParseArgsType = false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if ( @@ -233,7 +233,7 @@ function isStatFsDynProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpression return ( !!symbol && symbol.declarations.some((declaration) => { - if (!lowerer.isStdlibFile(declaration.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration))) return false; const parent = declaration.parent; if (!ts.isInterfaceDeclaration(parent)) return false; if ( @@ -536,7 +536,7 @@ export function lowerPropertyAccessExpr( if ( represented && isMatchSliceType(lowerer, represented.type) && - (lowerer.isStdlibMember(expr) || isJsSourceFile(expr.getSourceFile())) + (lowerer.isStdlibMember(expr) || isJsSourceFile(ts.getSourceFileOfNode(expr))) ) { const recv = represented; const key: IrExpr = { kind: "strLit", value: expr.name.text, type: STRING, loc }; @@ -569,7 +569,7 @@ export function lowerPropertyAccessExpr( ts.isIdentifier(expr.expression) && expr.expression.text === "arguments" && !lowerer.ctx.argumentsLocal && - lowerer.typeOf(expr.expression).getSymbol()?.name === "IArguments" && + ts.symbolNameOf(lowerer.typeOf(expr.expression).getSymbol()) === "IArguments" && lowerer.isStdlibSymbol(lowerer.typeOf(expr.expression).getSymbol()) ) { let fn: ts.Node | undefined = expr.parent; @@ -640,9 +640,9 @@ export function lowerPropertyAccessExpr( if ( !expr.questionDotToken && isModuleExportsAccess(expr) && - isCjsJsFile(expr.getSourceFile(), lowerer.program) + isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program) ) { - const whole = cjsClassExprWholeExportOf(expr.getSourceFile()); + const whole = cjsClassExprWholeExportOf(ts.getSourceFileOfNode(expr)); if (whole) { let inBody = false; for ( @@ -655,7 +655,7 @@ export function lowerPropertyAccessExpr( break; } } - if (inBody || expr.getStart() > whole.stmt.getEnd()) { + if (inBody || ts.tokenStartOfNode(expr) > whole.stmt.end) { return lowerer.classValueRef(lowerer.lowerClassExpressionInfo(whole.classExpr), expr); } } @@ -802,7 +802,7 @@ export function lowerPropertyAccessExpr( if ( !expr.questionDotToken && stdlibGlobalNameOf(lowerer, expr.expression) === "globalThis" && - isJsSourceFile(expr.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(expr)) ) { const canonical = stdlibGlobalNameOf(lowerer, expr); if (canonical !== null) { @@ -850,7 +850,7 @@ export function lowerPropertyAccessExpr( // them, and calls dispatch through the dyn method machinery // instead. if ( - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && (lowerer.mapTypeOf(lowerer.typeOf(expr.expression)) === null || (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Never) !== 0 || // A never-tainted receiver type maps (never rides as f64) but @@ -891,7 +891,7 @@ export function lowerPropertyAccessExpr( : { kind: "strIntrinsic", method: "length", receiver: recv, args: [], type: F64, loc }; } } - if (isJsSourceFile(expr.getSourceFile()) && expr.name.text === "constructor") { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.name.text === "constructor") { const receiver = tryLowerExpression(lowerer, expr.expression); if ( receiver?.type.kind === "dyn" || @@ -955,7 +955,7 @@ export function lowerPropertyAccessExpr( // storage can expose its own name and length in both source languages. if ( !["apply", "bind", "call", "toString", "caller", "arguments"].includes(expr.name.text) && - (isJsSourceFile(expr.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || expr.name.text === "name" || expr.name.text === "length" || expr.name.text === "constructor") @@ -968,7 +968,9 @@ export function lowerPropertyAccessExpr( const receiverSymbol = receiverType.getSymbol(); const checkedFunction = lowerer.checker.getCallSignatures(receiverType).length > 0 || - (receiverSymbol?.name === "Function" && lowerer.isStdlibSymbol(receiverSymbol)); + (receiverSymbol !== undefined && + ts.symbolName(receiverSymbol) === "Function" && + lowerer.isStdlibSymbol(receiverSymbol)); if (probed?.type.kind === "dyn" && checkedFunction) { const key: IrExpr = { kind: "strLit", @@ -990,7 +992,7 @@ export function lowerPropertyAccessExpr( ); } if ( - (isJsSourceFile(expr.getSourceFile()) || expr.name.text === "constructor") && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || expr.name.text === "constructor") && probed?.type.kind === "func" && canBoxFuncIntoDyn( probed.type, @@ -1024,17 +1026,17 @@ export function lowerPropertyAccessExpr( if (expr.name.text === "done" || expr.name.text === "value") { const receiverType = lowerer.typeOf(expr.expression); const recvSym = - receiverType.getAliasSymbol()?.name === "IteratorResult" + ts.symbolNameOf(receiverType.getAliasSymbol()) === "IteratorResult" ? receiverType.getAliasSymbol() : receiverType.getSymbol(); if ( - (recvSym?.name === "IteratorResult" || - recvSym?.name === "IteratorYieldResult" || - recvSym?.name === "IteratorReturnResult") && + ((recvSym !== undefined && ts.symbolName(recvSym) === "IteratorResult") || + (recvSym !== undefined && ts.symbolName(recvSym) === "IteratorYieldResult") || + (recvSym !== undefined && ts.symbolName(recvSym) === "IteratorReturnResult")) && recvSym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d)) && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const recv = lowerer.lowerExpr(expr.expression); @@ -1085,7 +1087,8 @@ export function lowerPropertyAccessExpr( // members owned by these implemented families bypass the stdlib fence. const segmentDataSymbol = lowerer.typeOf(expr.expression).getSymbol(); const segmentDataProperty = - segmentDataSymbol?.name === "SegmentData" && + segmentDataSymbol !== undefined && + ts.symbolName(segmentDataSymbol) === "SegmentData" && lowerer.isStdlibSymbol(segmentDataSymbol) && ["segment", "index", "input", "isWordLike"].includes(expr.name.text); // os.constants members vary by platform (the declared groups list @@ -1192,7 +1195,7 @@ export function lowerPropertyAccessExpr( // validated dynCheck as usual. if ( recvLowered.type.kind === "dyn" || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && (["f64", "bool", "bigint", "string", "symbol"].includes(recvLowered.type.kind) || (recvLowered.type.kind === "union" && lowerer.dynConvertible(recvLowered.type)))) ) { @@ -1260,8 +1263,8 @@ export function lowerPropertyAccessExpr( recvShape && !recvShape.tuple && lowerer.implicitParamTypes !== null && - isJsSourceFile(expr.getSourceFile()) && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null ) { return { kind: "seqExpr", @@ -1352,6 +1355,27 @@ export function lowerPropertyAccessExpr( } const asserted = assertedMemberRead(lowerer, expr, recvLowered, loc); if (asserted) return asserted; + // A checker-`any` receiver whose value is a FRESH typed result of an + // earlier step in the chain (`out.split(" ").length`, where split + // extracted the string and answered a native string[]): the member read + // goes through the boxed checked-dynamic value, as the method-call twin + // does. The box copies; a call result has no other holder to observe it. + if ( + (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Any) !== 0 && + isFreshCallResult(expr.expression) && + lowerer.dynConvertible(recvLowered.type) + ) { + return lowerer.maybeNarrow( + { + kind: "dynKeyGet", + value: lowerer.coerceToExpected(recvLowered, DYN), + key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, + type: DYN, + loc, + }, + expr, + ); + } lowerer.unsupported( "SC1090", expr, @@ -1359,6 +1383,13 @@ export function lowerPropertyAccessExpr( ); } +/** A call expression, through parentheses: its value has no other holder. */ +function isFreshCallResult(node: ts.Expression): boolean { + let e = node; + while (ts.isParenthesizedExpression(e)) e = e.expression; + return ts.isCallExpression(e); +} + /** `(source as { repoName: string }).repoName` where the assertion names a * member some arms of the stored union of records do not declare: Node * reads the property off whichever object it is, so arms that declare it @@ -1480,7 +1511,12 @@ export function lowerIntrinsicProperty( | "BigInt" )[]) { if (!lowerer.chainBlocked(expr) && lowerer.isStdlibGlobal(expr, name)) { - return primitiveCtorClosure(lowerer, name, locOf(expr), isJsSourceFile(expr.getSourceFile())); + return primitiveCtorClosure( + lowerer, + name, + locOf(expr), + isJsSourceFile(ts.getSourceFileOfNode(expr)), + ); } } if (lowerer.stdlibGlobalMember(expr, "Reflect") === "ownKeys") { @@ -1658,7 +1694,7 @@ export function lowerIntrinsicProperty( ) return null; const name = expr.name.text; - if (kind === "array" && name === "constructor" && isJsSourceFile(expr.getSourceFile())) { + if (kind === "array" && name === "constructor" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const receiver = lowerer.lowerExpr(expr.expression); if (receiver.type.kind === "dyn") return { @@ -1686,7 +1722,7 @@ export function lowerIntrinsicProperty( const strictReceiver = (expected: IrType): IrExpr => { const value = lowerer.lowerExpr(expr.expression); if (value.type.kind === "dyn") { - if (isJsSourceFile(expr.getSourceFile()) && name === "length") return value; + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && name === "length") return value; return lowerer.coerceInto(expr.expression, value, expected); } return lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, expected, name) ?? value; @@ -2206,7 +2242,7 @@ function lowerReplacedExportsRead( if (expr.questionDotToken) return null; const recv = expr.expression; if (!ts.isIdentifier(recv) || recv.text !== "exports") return null; - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); if (!isJsSourceFile(sf) || isNodeEsmFile(sf, lowerer.program)) return null; if (lowerer.peekLocal(recv) || lowerer.globalOf(recv)) return null; // a user binding shadows // Write position is the export-assignment machinery's territory. @@ -2243,7 +2279,7 @@ function lowerCjsExportTableThisMember( expr: ts.PropertyAccessExpression, ): IrExpr | null { if (expr.expression.kind !== ts.SyntaxKind.ThisKeyword || expr.questionDotToken) return null; - if (!isJsSourceFile(expr.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; // The enclosing accessor, crossing only arrow boundaries (arrows // inherit `this`; any other function form rebinds it). let n: ts.Node | undefined = expr.parent; @@ -2443,7 +2479,8 @@ function lowerMatchGroupsRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressio const groups = matchResultNamedGroupsOf(lowerer, expr.expression); // An untraced regex: TypeScript reaches the member fence; JavaScript // would otherwise read the slice's absent expando as undefined. - if (groups === null && isJsSourceFile(expr.getSourceFile())) lowerer.stdlibMemberFence(expr); + if (groups === null && isJsSourceFile(ts.getSourceFileOfNode(expr))) + lowerer.stdlibMemberFence(expr); if (groups === null) return null; if (groups.length === 0) { // Node: a match result of a group-less regex answers undefined — @@ -2460,7 +2497,7 @@ function lowerMatchGroupsRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressio } const recv = lowerer.lowerExpr(expr.expression); // A JavaScript callback parameter holds the row as a dynamic array. - const dynRow = recv.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()); + const dynRow = recv.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)); if (!dynRow && !isMatchSliceType(lowerer, recv.type)) return null; return lowerGroupsProjection(lowerer, recv, groups, loc); } @@ -3037,7 +3074,7 @@ export function lowerUnionProperty( // instance argument (e.g. Matrix4.makeTranslation). Preserve primitive // missing-property semantics and dispatch class data through its native // capsule instead of demanding a field shared by every union arm. - if (isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(value.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(value.type)) { return { kind: "dynKeyGet", value: lowerer.coerceToExpected(value, DYN), @@ -3189,7 +3226,7 @@ export function classFieldTarget( const represented = saved ?? lowerer.lowerExpr(receiverNode); if ( represented.type.kind === "dyn" && - isJsSourceFile(receiverNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(receiverNode)) && !fieldName.startsWith("#") ) { return { container: "dynamic", obj: represented, field: fieldName, fieldType: DYN }; @@ -3236,7 +3273,7 @@ export function classFieldTarget( if (fieldType) { const obj = lowerObjectReceiver(); if ( - isJsSourceFile(receiverNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(receiverNode)) && !fieldName.startsWith("#") && [...lowerer.classes.values()].some( (candidate) => @@ -3274,7 +3311,7 @@ export function classFieldTarget( fieldType: getF ? getF.sig.ret : setF!.sig.params[0]!.type, }; } - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { const property = lowerer.checker.getPropertyOfType( lowerer.checker.getTypeAtLocation(receiverNode), fieldName, @@ -3493,7 +3530,7 @@ export function fieldTarget( : [declared]; return declaredArms.every((a) => ivArms.some((b) => typeEquals(a, b))); }; - if (!nameSym || nameSym.name === ts.InternalSymbolName.Index || canonicalized()) { + if (!nameSym || ts.symbolName(nameSym) === ts.InternalSymbolName.Index || canonicalized()) { let obj = lowerer.lowerExpr(access.expression); if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) { const present = lowerer.stripUndefinedArm(obj.type); @@ -3605,7 +3642,7 @@ export function fieldGetExpr( } if ( !target.field.startsWith("#") && - isJsSourceFile(blame.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(blame)) && lowerer.dynConvertible(getF.sig.ret) ) { const value: IrExpr = { @@ -3887,7 +3924,9 @@ export function builtinGlobalValueAt( )); if (reflects) { const name = - owner !== null ? `${owner}.${callee.name.text}` : callee.getText().replace(/\s+/g, ""); + owner !== null + ? `${owner}.${callee.name.text}` + : ts.getTextOfNode(callee).replace(/\s+/g, ""); lowerer.noLowering( `${name}(${canonical}, ...)`, expr, @@ -3930,8 +3969,8 @@ function fenceBuiltinConstructorStaticRead( const symbol = lowerer.typeOf(access.expression).getSymbol(); if ( symbol !== undefined && - symbol.name.endsWith("Constructor") && - GLOBAL_CONSTRUCTOR_VALUES.has(symbol.name.slice(0, -"Constructor".length)) && + ts.symbolName(symbol).endsWith("Constructor") && + GLOBAL_CONSTRUCTOR_VALUES.has(ts.symbolName(symbol).slice(0, -"Constructor".length)) && lowerer.isStdlibSymbol(symbol) ) lowerer.stdlibMemberFence(access); @@ -4008,7 +4047,7 @@ function jsFunctionExpandoMember(lowerer: Lowerer, access: ts.PropertyAccessExpr return ( decls.length > 0 && decls.every((d) => { - if (!isJsSourceFile(d.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(d))) return false; const assign = ts.isBinaryExpression(d) ? d : d.parent; return ( assign !== undefined && diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 91d1468c2..f8b8c7604 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -470,7 +470,7 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc ); } const constructor = tryLowerExpression(lowerer, expr.right); - if (constructor?.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + if (constructor?.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); return checkedClassInstanceOf(lowerer, value, constructor, loc); } @@ -478,7 +478,7 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc // function's prototype against the value's prototype chain. if ( constructor?.type.kind === "func" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(constructor.type) ) { const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); @@ -1080,7 +1080,11 @@ function lowerPrivateIn( // when the value is a class instance at or below the declarer. A typed // reference carries the class it was boxed under: the declarer or a // descendant answers true; an ancestor answers per instance. - if (recv.type.kind === "dyn" && recv.kind === "varRef" && isJsSourceFile(expr.getSourceFile())) { + if ( + recv.type.kind === "dyn" && + recv.kind === "varRef" && + isJsSourceFile(ts.getSourceFileOfNode(expr)) + ) { // A function-local class has one brand per evaluation. if (info.localClass) { const owner = localPrivateBrandClass(lowerer, priv); @@ -1381,7 +1385,7 @@ export function lowerInExpression( recv.type.kind === "classval" ? lowerer.classes.get(recv.type.className) : undefined; if ( (classInfo && (classInfo.callableBase || hasRuntimeStatics(classInfo))) || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv.type.kind === "func" && lowerer.dynConvertible(recv.type)) ) { diff --git a/packages/compiler/src/frontend/lowering/expressions/typeof.ts b/packages/compiler/src/frontend/lowering/expressions/typeof.ts index 41a05ac2b..46005e228 100644 --- a/packages/compiler/src/frontend/lowering/expressions/typeof.ts +++ b/packages/compiler/src/frontend/lowering/expressions/typeof.ts @@ -71,7 +71,7 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc (operand.text === "__dirname" || operand.text === "__filename") && lowerer.isStdlibGlobal(operand, operand.text) ) { - const sf = operand.getSourceFile(); + const sf = ts.getSourceFileOfNode(operand); if (isNodeEsmFile(sf, lowerer.program) && !/\.tsx?$/.test(sf.fileName)) return { kind: "strLit", value: "undefined", type: STRING, loc }; } @@ -204,7 +204,7 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc // its own lowering as an identity token (a string), so the operand's // representation cannot answer. Node's answer follows the export's // kind, and the binding read has no effects. - const imported = isJsSourceFile(tested.getSourceFile()) + const imported = isJsSourceFile(ts.getSourceFileOfNode(tested)) ? lowerer.builtinImportOf(tested) : null; if (imported && builtinModuleConstOf(lowerer, imported.module, imported.member) === undefined) { diff --git a/packages/compiler/src/frontend/lowering/expressions/union-fields.ts b/packages/compiler/src/frontend/lowering/expressions/union-fields.ts index f2dd0d0f0..6454bc1fd 100644 --- a/packages/compiler/src/frontend/lowering/expressions/union-fields.ts +++ b/packages/compiler/src/frontend/lowering/expressions/union-fields.ts @@ -117,7 +117,7 @@ export function lowerUnionFieldRead( access: ts.PropertyAccessExpression, receiver: IrExpr, ): IrExpr | null { - if (receiver.type.kind !== "union" || isJsSourceFile(access.getSourceFile())) return null; + if (receiver.type.kind !== "union" || isJsSourceFile(ts.getSourceFileOfNode(access))) return null; const unionId = receiver.type.unionId; const arms = lowerer.unions.get(unionId)?.arms; if (!arms || arms.length < 2) return null; diff --git a/packages/compiler/src/frontend/lowering/expressions/updates.ts b/packages/compiler/src/frontend/lowering/expressions/updates.ts index 41d8f6332..104057906 100644 --- a/packages/compiler/src/frontend/lowering/expressions/updates.ts +++ b/packages/compiler/src/frontend/lowering/expressions/updates.ts @@ -110,7 +110,7 @@ export function lowerPrefixUnary(lowerer: Lowerer, expr: ts.PrefixUnaryExpressio } // Match the checked numeric boundary used by JS binary arithmetic. const operand = - raw.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()) + raw.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)) ? { kind: "dynCheck" as const, value: raw, type: F64, loc } : lowerOptionalNumber(lowerer, raw, loc, expr.operand); if (operand.type.kind !== "f64") operatorUnsupported(lowerer, expr, "-", [raw.type]); @@ -263,7 +263,7 @@ export function lowerIncDec( const receiver = save(raw, "%incrementArray"); const rawIndex = lowerer.lowerExpr(access.argumentExpression); const index = save( - rawIndex.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()) + rawIndex.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)) ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [rawIndex], type: F64, loc } : rawIndex, "%incrementIndex", @@ -308,10 +308,13 @@ export function lowerIncDec( // class-backed protocol view): the expanded form `o.v = o.v + 1`, its // read checked into number like any other read of the member. const checkedNumber = - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && raw.type.kind === "dyn" && lowerer.mapTypeOf(lowerer.typeOf(access))?.kind === "f64"; - if ((isJsSourceFile(expr.getSourceFile()) || checkedNumber) && raw.type.kind === "dyn") { + if ( + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || checkedNumber) && + raw.type.kind === "dyn" + ) { const receiver = save(raw, "%incrementReceiver"); const key = save( ts.isElementAccessExpression(access) @@ -352,7 +355,7 @@ export function lowerIncDec( ), ); } - if (isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(raw.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(raw.type)) { const receiver = save(lowerer.coerceInto(access.expression, raw, DYN), "%incrementReceiver"); const key = save( ts.isElementAccessExpression(access) @@ -818,7 +821,7 @@ export function lowerFieldCompoundValue( rhsNode ? lowerer.lowerExpr(rhsNode) : { kind: "numLit", value: 1, type: F64, loc }, "%compoundRhs", ); - if (isJsSourceFile(access.getSourceFile()) && lowerer.dynConvertible(rhs.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(access)) && lowerer.dynConvertible(rhs.type)) { const numeric = ["-", "*", "/", "%", "**"].includes(op); const right = lowerer.coerceToExpected(rhs, DYN); const value = save( @@ -912,7 +915,7 @@ export function lowerFieldCompoundValue( "%compoundRhs", ); const numericRhs: IrExpr = - rhs.type.kind === "dyn" && isJsSourceFile(access.getSourceFile()) + rhs.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(access)) ? { kind: "dynCheck", value: rhs, type: F64, loc: rhs.loc } : lowerOptionalNumber(lowerer, rhs, loc); let value: IrExpr; @@ -927,7 +930,7 @@ export function lowerFieldCompoundValue( } else if (bigintValue) { value = bigintValue; } else if ( - (target.fieldType.kind === "dyn" && isJsSourceFile(access.getSourceFile())) || + (target.fieldType.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(access))) || // An untyped right operand: the expanded `o.f = o.f op rhs`; the field // write below checks the result into the declared field type. (rhs.type.kind === "dyn" && diff --git a/packages/compiler/src/frontend/lowering/function-returns.ts b/packages/compiler/src/frontend/lowering/function-returns.ts index c0e70013c..9500d6d2d 100644 --- a/packages/compiler/src/frontend/lowering/function-returns.ts +++ b/packages/compiler/src/frontend/lowering/function-returns.ts @@ -98,8 +98,8 @@ export function appendImplicitUndefinedReturn( /** Whether the source explicitly documents a return contract. */ export function hasExplicitJsDocReturn(decl: ts.SignatureDeclaration): boolean { - const sourceFile = decl.getSourceFile(); - return /@returns?\b/.test(sourceFile.text.slice(decl.pos, decl.getStart(sourceFile))); + const sourceFile = ts.getSourceFileOfNode(decl); + return /@returns?\b/.test(sourceFile.text.slice(decl.pos, ts.tokenStartOfNode(decl))); } export function promiseCarriesDyn(lowerer: Lowerer, type: IrType): boolean { @@ -130,14 +130,14 @@ export function declaredReturnType( decl: ts.SignatureDeclaration, blame: ts.Node, ): IrType { - if (isJsSourceFile(decl.getSourceFile()) && ts.isGetAccessorDeclaration(decl)) return DYN; + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && ts.isGetAccessorDeclaration(decl)) return DYN; const sig = lowerer.checker.getSignatureFromDeclaration(decl); if (!sig) lowerer.unsupported("SC1090", decl, "this function form"); const retTsType = lowerer.checker.getReturnTypeOfSignature(sig); // An inferred null/undefined return in JS can read a mutable checked-value // field. Keep the actual value instead of restricting it to the initializer. if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && (retTsType.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 ) return DYN; @@ -154,7 +154,7 @@ export function declaredReturnType( ) mappedReturn = mapAsyncGeneratorAnnotation(retTsType, lowerer.typeCtx); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && decl.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) && !decl.asteriskToken && mappedReturn?.kind !== "promise" @@ -162,7 +162,11 @@ export function declaredReturnType( const fallback = dynFallbackType(lowerer, decl, retTsType); return fallback?.kind === "promise" ? fallback : { kind: "promise", inner: DYN }; } - if (mappedReturn && isJsSourceFile(decl.getSourceFile()) && functionCanReturnUndefined(decl)) + if ( + mappedReturn && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && + functionCanReturnUndefined(decl) + ) mappedReturn = lowerer.withUndefinedArmOf(mappedReturn) ?? mappedReturn; // A generic-class method body that can return an indexed read that // misses returns undefined there, whatever the declared result says. @@ -171,33 +175,33 @@ export function declaredReturnType( mappedReturn.kind === "promise" ? { kind: "promise", inner: lowerer.runtimeOptionalType(mappedReturn.inner) } : lowerer.runtimeOptionalType(mappedReturn); - if (isJsSourceFile(decl.getSourceFile()) && mappedReturn?.kind === "void") { + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && mappedReturn?.kind === "void") { let returnsValue = false; const scan = (node: ts.Node): void => { if (node !== decl && ts.isFunctionLike(node)) return; if (ts.isReturnStatement(node) && node.expression) returnsValue = true; - node.forEachChild(scan); + ts.forEachChild(node, scan); }; scan(decl); if (returnsValue) return DYN; } - if (isJsSourceFile(decl.getSourceFile()) && mappedReturn?.kind === "array") return DYN; + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && mappedReturn?.kind === "array") return DYN; if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "record" ) return DYN; if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "date" ) return DYN; if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "union" && @@ -207,7 +211,7 @@ export function declaredReturnType( // JS inference assigns Number to arithmetic over any, although the // runtime operands can both be BigInts. Preserve that native result. if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "f64" @@ -237,13 +241,13 @@ export function declaredReturnType( ) ) numericAnyReturn = true; - node.forEachChild(check); + ts.forEachChild(node, check); }; check(decl); if (numericAnyReturn) return DYN; } if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !hasExplicitJsDocReturn(decl) && mappedReturn !== null && promiseCarriesDyn(lowerer, mappedReturn) && @@ -264,7 +268,7 @@ export function declaredReturnType( // value rides its own box, calls go through the boxed thunk (JS // arity), and typed slots re-check with dynCheck as usual. if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && decl.type === undefined && lowerer.mapTypeOf(retTsType)?.kind === "func" ) { @@ -304,7 +308,7 @@ export function declaredReturnType( (arm.kind === "object" && !hasExplicitJsDocReturn(decl)), ); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && decl.type === undefined && recordReturn !== null && recordReturn !== undefined && @@ -468,5 +472,5 @@ export function blockBodyOf(decl: ts.FunctionLikeDeclaration): ts.Block | null { const body = decl.body; if (body === undefined) return null; if (ts.isBlock(body)) return body; - return decl.forEachChild((c) => (ts.isBlock(c) ? c : undefined)) ?? null; + return ts.forEachChild(decl, (c) => (ts.isBlock(c) ? c : undefined)) ?? null; } diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index b586884c5..880cccc8b 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -225,7 +225,7 @@ export function collectGenericSignature(lowerer: Lowerer, decl: ts.FunctionDecla lowerer.genericFnsBySymbol.set(symbol, { decl, baseName: nameText, - qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(decl) + nameText), + qualifiedName: lowerer.qualify(ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + nameText), typeParams, instances: new Map(), }); @@ -306,7 +306,7 @@ export function genericCallInstance( // untyped value, such as an untyped forwarding call) is checked-dynamic. const mappedReturn = lowerer.mapTypeOf(retTs) ?? - ((retTs.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(info.decl.getSourceFile()) + ((retTs.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(ts.getSourceFileOfNode(info.decl)) ? DYN : null); if (!mappedReturn) { @@ -552,7 +552,7 @@ function internGenericInstance( return JSON.stringify(tsBound.value); } const bound = bindings.get(tp); - return bound ? lowerer.fmt(bound) : tp.name; + return bound ? lowerer.fmt(bound) : ts.symbolName(tp); }) .join(", "); // Deep polymorphic recursion renders unbounded types — keep messages sane. @@ -699,7 +699,8 @@ function unifySignatureBindings( if (!bindings.has(sym)) { const mapped = lowerer.mapTypeOf(inst) ?? - ((inst.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(info.decl.getSourceFile()) + ((inst.flags & ts.TypeFlags.Any) !== 0 && + !isJsSourceFile(ts.getSourceFileOfNode(info.decl)) ? DYN : null); // A visitor's inferred T can be void. It still binds the @@ -811,7 +812,7 @@ function unifySignatureBindings( } if (declared.flags & ts.TypeFlags.Object) { for (const dp of lowerer.checker.getPropertiesOfType(declared)) { - const ip = lowerer.checker.getPropertyOfType(inst, dp.name); + const ip = lowerer.checker.getPropertyOfType(inst, ts.symbolName(dp)); if (ip) unify( lowerer.checker.getTypeOfSymbol(dp), @@ -1171,7 +1172,7 @@ function genericValueInstance( const rendered = info.typeParams .map((tp) => { const bound = bindings.get(tp); - return bound ? lowerer.fmt(bound) : tp.name; + return bound ? lowerer.fmt(bound) : ts.symbolName(tp); }) .join(", "); lowerer.typeParamBindings = bindings; @@ -1270,7 +1271,7 @@ function paramWrittenInBody( hit = true; return; } - m.forEachChild(scan); + ts.forEachChild(m, scan); }; scan(n); return hit; @@ -1305,7 +1306,7 @@ function paramWrittenInBody( written = true; return; } - n.forEachChild(walk); + ts.forEachChild(n, walk); }; walk(body); return written; @@ -1480,7 +1481,7 @@ export function implicitAnyParamSymbolsOf( }); // Destructured object parameters monomorphize too (implicitPatternsOf); // their slots stay null here. - if (!hasImplicitParams && isJsSourceFile(decl.getSourceFile())) + if (!hasImplicitParams && isJsSourceFile(ts.getSourceFileOfNode(decl))) hasImplicitParams = implicitPatternsOf(lowerer, decl).some((pattern) => pattern !== null); return hasImplicitParams ? out : null; } @@ -1630,7 +1631,7 @@ function implicitDeclaredReturn(lowerer: Lowerer, info: GenericFnInfo): IrType | // declaredReturnType widens non-generic JavaScript functions. if ( mapped !== null && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && functionCanReturnUndefined(info.decl) ) return lowerer.withUndefinedArmOf(mapped) ?? mapped; @@ -1678,7 +1679,7 @@ function bindableImplicitIr(t: IrType | null): t is IrType { function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrType | null { while (ts.isParenthesizedExpression(arg)) arg = arg.expression; if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) ) { const saved = lowerer.diagSink; @@ -1701,7 +1702,7 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp } if ( ts.isObjectLiteralExpression(arg) && - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (arg.properties.nodes.length === 0 || arg.properties.nodes.some( (property) => @@ -1716,7 +1717,7 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp if (ts.isIdentifier(arg)) return lowerer.peekLocal(arg)?.type ?? lowerer.globalOf(arg)?.type ?? null; if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isPropertyAccessExpression(arg) || ts.isElementAccessExpression(arg)) ) { // Mutable JS members can retain an initializer-only checker type (for @@ -1726,7 +1727,7 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp if (represented) return represented.type; } if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isCallExpression(arg) || ts.isConditionalExpression(arg) || (ts.isBinaryExpression(arg) && @@ -1843,7 +1844,7 @@ export function implicitCallInstance( (neverTaintedJsType(lowerer, arg, t) ? null : lowerer.mapTypeOf(t)) ?? dynFallbackType(lowerer, arg, t); if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && ts.isArrayLiteralExpression(arg) && mapped?.kind === "array" && (mapped.elem.kind === "classval" || @@ -1868,7 +1869,7 @@ export function implicitCallInstance( // preserve represented arms through the checked boundary instead // of specializing a call to a union that would discard them. if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && mapped?.kind === "func" && !lowerer.dynConvertible(mapped) ) { @@ -2066,7 +2067,7 @@ export function implicitLocalFnNodeOf( if (cached !== undefined) return cached ? (cached.decl as ts.FunctionExpression | ts.ArrowFunction) : null; const probe = (): ts.FunctionExpression | ts.ArrowFunction | null => { - if (!implicitMonoFile(decl.getSourceFile())) return null; + if (!implicitMonoFile(ts.getSourceFileOfNode(decl))) return null; if (!ts.isIdentifier(decl.name) || decl.initializer === undefined) return null; let init: ts.Expression = decl.initializer; while (ts.isParenthesizedExpression(init)) init = init.expression; @@ -2097,7 +2098,7 @@ export function implicitLocalFnNodeOf( const d = s ? s.valueDeclaration : undefined; if ( d && - d.getSourceFile() === decl.getSourceFile() && + ts.getSourceFileOfNode(d) === ts.getSourceFileOfNode(decl) && !(d.pos >= init.pos && d.end <= init.end) ) { // Declared outside the initializer, in this file: a capture @@ -2116,7 +2117,7 @@ export function implicitLocalFnNodeOf( } } } - n.forEachChild(scan); + ts.forEachChild(n, scan); }; scan(init.body); return captures ? null : init; @@ -2154,8 +2155,8 @@ export function implicitLocalFnInfoOf( decl: fnNode, baseName: name, qualifiedName: lowerer.qualify( - decl.getSourceFile(), - nsPathPrefix(stmt ?? decl, decl) + `${name}%l${decl.getStart()}`, + ts.getSourceFileOfNode(decl), + nsPathPrefix(stmt ?? decl, decl) + `${name}%l${ts.tokenStartOfNode(decl)}`, ), typeParams: [], instances: new Map(), @@ -2249,7 +2250,10 @@ export function objLitGenericFnInfoOf( const info: GenericFnInfo = { decl: fnNode, baseName: name, - qualifiedName: lowerer.qualify(fnNode.getSourceFile(), `%ol${fnNode.getStart()}.${name}`), + qualifiedName: lowerer.qualify( + ts.getSourceFileOfNode(fnNode), + `%ol${ts.tokenStartOfNode(fnNode)}.${name}`, + ), typeParams, instances: new Map(), objectLiteral: true, @@ -2381,7 +2385,10 @@ export function bindingGenericFnInfoOf( const info: GenericFnInfo = { decl: fnNode, baseName: name, - qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(stmt ?? decl, decl) + name), + qualifiedName: lowerer.qualify( + ts.getSourceFileOfNode(decl), + nsPathPrefix(stmt ?? decl, decl) + name, + ), typeParams, instances: new Map(), }; diff --git a/packages/compiler/src/frontend/lowering/import-meta.ts b/packages/compiler/src/frontend/lowering/import-meta.ts index baf550e6d..796e64b69 100644 --- a/packages/compiler/src/frontend/lowering/import-meta.ts +++ b/packages/compiler/src/frontend/lowering/import-meta.ts @@ -20,7 +20,7 @@ export function isImportMeta(expr: ts.Expression): boolean { /** File-backed metadata is fixed by the compiled module graph. Reads share * one implementation across dot access, folded keys and selected bindings. */ export function importMetaField(lowerer: Lowerer, node: ts.Node, name: string): IrExpr { - const sf = node.getSourceFile(); + const sf = ts.getSourceFileOfNode(node); const fileName = lowerer.targetPlatform === "wasi" ? (wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")) diff --git a/packages/compiler/src/frontend/lowering/intern-tables.ts b/packages/compiler/src/frontend/lowering/intern-tables.ts index 32bb71858..fc69237db 100644 --- a/packages/compiler/src/frontend/lowering/intern-tables.ts +++ b/packages/compiler/src/frontend/lowering/intern-tables.ts @@ -142,7 +142,7 @@ interface TableTypes { /** The key and value types of an InternTable checker type. */ function tableTypes(lowerer: Lowerer, type: ts.Type, blame: ts.Node): TableTypes { const parts = type.isUnionType() ? ts.constituentTypes(type) : [type]; - const table = parts.find((p) => p.getSymbol()?.name === "InternTable"); + const table = parts.find((p) => ts.symbolNameOf(p.getSymbol()) === "InternTable"); const args = table ? lowerer.checker.getTypeArguments(table as ts.TypeReference) : []; if (args.length !== 2) lowerer.noLowering("InternTable without type arguments", blame); const key = lowerer.mapTypeOf(args[0]!); diff --git a/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts b/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts index d39e5cceb..b9bbf6216 100644 --- a/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts +++ b/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts @@ -81,10 +81,10 @@ export function isValueReturningConstructorClass(decl: ts.ClassDeclaration): boo if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; if (ts.isReturnStatement(node) && node.expression !== undefined && !insideArrow(node, ctor!)) returnsValue = true; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - ctor.body.forEachChild(visit); - for (const parameter of ctor.parameters.nodes) parameter.forEachChild(visit); + ts.forEachChild(ctor.body, visit); + for (const parameter of ctor.parameters.nodes) ts.forEachChild(parameter, visit); return returnsValue && !observesThis; } @@ -215,7 +215,7 @@ export function lowerValueReturningConstructorNew( "spread arguments when constructing a class whose constructor returns a value", ); } - const key = `ctorValue:${decl.getSourceFile().fileName}:${decl.getStart()}`; + const key = `ctorValue:${ts.getSourceFileOfNode(decl).fileName}:${ts.tokenStartOfNode(decl)}`; let name = lowerer.valueHelpers.get(key); if (name === undefined) { name = `%ctorValue.${lowerer.valueHelpers.size}`; @@ -235,7 +235,7 @@ export function lowerValueReturningConstructorNew( { kind: "runtimeFence", code: "SC1090", - message: `constructors that return no object (the bare instance of '${decl.name?.text ?? "class"}' has no representation here) are not supported yet [SC1090 at ${decl.getSourceFile().fileName}:${ts.getLineAndCharacterOfPosition(decl.getSourceFile(), ctor.getStart()).line + 1}]`, + message: `constructors that return no object (the bare instance of '${decl.name?.text ?? "class"}' has no representation here) are not supported yet [SC1090 at ${ts.getSourceFileOfNode(decl).fileName}:${ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(decl), ts.tokenStartOfNode(ctor)).line + 1}]`, loc: locOf(ctor), }, ]; @@ -261,8 +261,8 @@ export function lowerValueReturningConstructorNew( .map((arg) => ({ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc: locOf(arg) })); const result = lowerer.declareHiddenLocal("%constructed", DYN); const value: IrExpr = { kind: "varRef", localId: result.id, type: DYN, loc }; - const sf = expr.getSourceFile(); - const line = ts.getLineAndCharacterOfPosition(sf, expr.getStart()).line + 1; + const sf = ts.getSourceFileOfNode(expr); + const line = ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(expr)).line + 1; const objectLike: IrExpr = { kind: "logical", op: "||", diff --git a/packages/compiler/src/frontend/lowering/json-require.ts b/packages/compiler/src/frontend/lowering/json-require.ts index 7c688dcda..927745253 100644 --- a/packages/compiler/src/frontend/lowering/json-require.ts +++ b/packages/compiler/src/frontend/lowering/json-require.ts @@ -36,7 +36,7 @@ export function jsonRequireTarget( if (call === undefined || !ts.isCallExpression(call)) return null; const spec = requireSpecOf(call); if (spec === null || !ts.isIdentifier(call.expression)) return null; - const sf = call.getSourceFile(); + const sf = ts.getSourceFileOfNode(call); if (!isCjsJsFile(sf, lowerer.program)) return null; if (createRequireCalleeFileOf(lowerer, call.expression) !== null) return null; const target = resolveImport(lowerer.program, sf, spec, "require"); diff --git a/packages/compiler/src/frontend/lowering/lower-assert.ts b/packages/compiler/src/frontend/lowering/lower-assert.ts index cb31ecc79..460f4d53a 100644 --- a/packages/compiler/src/frontend/lowering/lower-assert.ts +++ b/packages/compiler/src/frontend/lowering/lower-assert.ts @@ -887,10 +887,10 @@ export function lowerAssertDirectCall( * call's SOURCE TEXT (Node re-reads the file at runtime — the frontend has * the same bytes at compile time). */ function lowerAssertOk(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr { - requireStatementPosition(lowerer, expr, expr.expression.getText()); + requireStatementPosition(lowerer, expr, ts.getTextOfNode(expr.expression)); if (expr.arguments.nodes.length < 1 || expr.arguments.nodes.length > 2) { lowerer.noLowering( - `${expr.expression.getText()} with ${expr.arguments.nodes.length} arguments`, + `${ts.getTextOfNode(expr.expression)} with ${expr.arguments.nodes.length} arguments`, expr, "the supported forms are assert(value) and assert(value, message)", ); @@ -912,7 +912,7 @@ function lowerAssertOk(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): // Node reads the CALLSITE LINE and slices the expression out of it // (internal/errors/error_source.js) — a call spanning lines truncates // at the first newline, so the compile-time text does too. - const source = expr.getText().split("\n", 1)[0]!; + const source = ts.getTextOfNode(expr).split("\n", 1)[0]!; const text = `The expression evaluated to a falsy value:\n\n ${source}\n`; msg = { kind: "strLit", value: text, type: STRING, loc }; generated = true; diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 2a35d2a81..8fe1f73f7 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -678,7 +678,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const isStorage = parts.length > 0 && parts.every((part) => { - const name = part.getSymbol()?.name; + const name = ts.symbolNameOf(part.getSymbol()); return name === "ArrayBuffer" || name === "SharedArrayBuffer"; }); if (isStorage) { @@ -852,7 +852,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { (source.type.kind === "dyn" || source.type.kind === "func" || (source.type.kind === "union" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(source.type)) || ((source.type.kind === "map" || source.type.kind === "set") && lowerer.dynConvertible(source.type)) || @@ -868,7 +868,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerer.lowerExprExpecting(ts.isSpreadElement(arg) ? arg.expression : arg, DYN), ); const spreads = expr.arguments.nodes.flatMap((arg, index) => - ts.isSpreadElement(arg) ? [{ arg: index, what: arg.expression.getText() }] : [], + ts.isSpreadElement(arg) ? [{ arg: index, what: ts.getTextOfNode(arg.expression) }] : [], ); return { kind: "seqExpr", @@ -879,7 +879,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, ...(spreads.length > 0 ? { spreads } : {}), type: DYN, @@ -1184,7 +1184,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // argument list. Typed .ts spreads keep completeArgs' rest packing. const jsSpreadArgs = expr.arguments.nodes.some((a) => ts.isSpreadElement(a)) && - isJsSourceFile(expr.getSourceFile()); + isJsSourceFile(ts.getSourceFileOfNode(expr)); if (lowerer.isTopLevelFnSymbol(expr.expression) && !lowerer.peekLocal(expr.expression)) { // `import g = N.f; g()` — the alias's own source-order guards // (a no-op for every non-import= binding). @@ -1394,12 +1394,12 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if ( g && (g.type.kind === "func" || g.type.kind === "dyn") && - !cjsExportCallIgnoresThis(lowerer, expr.getSourceFile(), expr.expression.name.text) + !cjsExportCallIgnoresThis(lowerer, ts.getSourceFileOfNode(expr), expr.expression.name.text) ) { lowerer.unsupported( "SC1090", expr, - `calling '${expr.expression.getText()}' through the module's exports object when the function may read 'this' (Node binds it to the exports object, which compiles to a static table — call a local function instead)`, + `calling '${ts.getTextOfNode(expr.expression)}' through the module's exports object when the function may read 'this' (Node binds it to the exports object, which compiles to a static table — call a local function instead)`, ); } if (g && (g.type.kind === "func" || g.type.kind === "dyn")) { @@ -1594,7 +1594,11 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // monomorphized against the defining literal's declaration. lowerObjLitGenericMethodCall(lowerer, expr, expr.expression); if (intrinsic) return intrinsic; - if (isJsSourceFile(expr.getSourceFile())) { + { + const boxed = lowerDynReceiverMethodCall(lowerer, expr, expr.expression, true); + if (boxed) return boxed; + } + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const staticField = lowerStaticFieldRead(lowerer, expr.expression); if (staticField?.type.kind === "dyn" && !expr.arguments.nodes.some(ts.isSpreadElement)) { return { @@ -1662,7 +1666,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerer.unsupported( "SC1090", expr, - `calls through function-with-properties values ('${expr.expression.expression.getText()}' is callable AND carries members — the chalk shape; no hybrid representation exists yet: export the base and the property as separate functions)`, + `calls through function-with-properties values ('${ts.getTextOfNode(expr.expression.expression)}' is callable AND carries members — the chalk shape; no hybrid representation exists yet: export the base and the property as separate functions)`, ); } } @@ -1687,7 +1691,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { ); } } - lowerer.unsupported("SC1090", expr, `method calls like '${expr.expression.getText()}'`); + lowerer.unsupported("SC1090", expr, `method calls like '${ts.getTextOfNode(expr.expression)}'`); } // `s[Symbol.iterator]()` on a string: the string iterator as a value. @@ -1726,7 +1730,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { ) { const access = expr.expression; const value = tryLowerExpression(lowerer, access.expression); - if (value?.type.kind === "array" && !isJsSourceFile(expr.getSourceFile())) { + if (value?.type.kind === "array" && !isJsSourceFile(ts.getSourceFileOfNode(expr))) { const callback = lowerArrayCallbackCall(lowerer, expr, access, value); if (callback) return callback; } @@ -1761,7 +1765,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: expr.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -1880,7 +1884,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const calleeName = ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression) - ? expr.expression.getText() + ? ts.getTextOfNode(expr.expression) : ts.isIdentifier(expr.expression) ? expr.expression.text : "value"; @@ -1942,7 +1946,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // per use like every any-origin value. if ( (expr.arguments.nodes.length > callee.type.params.length || callee.type.rest === true) && - (isJsSourceFile(expr.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || (callee.type.rest === true && callee.type.restAbi === undefined)) && !expr.arguments.nodes.some((a) => ts.isSpreadElement(a)) && canBoxFuncIntoDyn( @@ -1974,7 +1978,7 @@ function lowerCheckedCallableValue( callee.type.kind !== "func" || !( (callee.type.rest === true && callee.type.restAbi === undefined) || - (isJsSourceFile(call.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(call)) && call.arguments.nodes.length > callee.type.params.length) ) || !lowerer.dynConvertible(callee.type) @@ -2590,7 +2594,7 @@ function lowerArrayPrototypeBorrowCall( access: ts.PropertyAccessExpression, ): IrExpr | null { if ( - !isJsSourceFile(call.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(call)) || call.questionDotToken || access.questionDotToken || access.name.text !== "call" || @@ -2802,7 +2806,7 @@ function lowerObjectOwnCall( result = guardNullProto( receiverRef, type.shapeId, - `${methodAccess.getText()} is not a function`, + `${ts.getTextOfNode(methodAccess)} is not a function`, result, loc, ); @@ -3365,6 +3369,13 @@ const ITERATOR_HELPER_METHODS = new Set([ "find", ]); +/** A call expression, through parentheses: its value has no other holder. */ +function isFreshCallResult(node: ts.Expression): boolean { + let e = node; + while (ts.isParenthesizedExpression(e)) e = e.expression; + return ts.isCallExpression(e); +} + /** METHOD calls on dyn receivers (`pkg.name.replace(...)`, `rawName.split`, * `ws.packages.filter(...)` — JSON.parse-derived values): validate the * receiver's dyn kind, extract, and ride the STATIC method machinery — the @@ -3382,6 +3393,7 @@ function lowerDynReceiverMethodCall( lowerer: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression, + boxTypedTemporary = false, ): IrExpr | null { if (lowerer.chainBlocked(call, access)) return null; if (stdlibGlobalNameOf(lowerer, access.expression) !== null) { @@ -3416,7 +3428,7 @@ function lowerDynReceiverMethodCall( const recvSymbol = recvTs.getSymbol(); if ( recvSymbol && - CHECKED_ITERATOR_TYPES.has(recvSymbol.name) && + CHECKED_ITERATOR_TYPES.has(ts.symbolName(recvSymbol)) && lowerer.isStdlibSymbol(recvSymbol) && ITERATOR_HELPER_METHODS.has(access.name.text) && lowerer.isStdlibMember(access) @@ -3484,7 +3496,7 @@ function lowerDynReceiverMethodCall( kind: "dynCall", callee: member, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -3506,7 +3518,7 @@ function lowerDynReceiverMethodCall( return null; if ( probed?.type.kind === "union" && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && lowerer.unions .get(probed.type.unionId) ?.arms.every( @@ -3556,7 +3568,7 @@ function lowerDynReceiverMethodCall( if ( checkerUntyped && recv.type.kind === "union" && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && isStringMethodName(access.name.text) && lowerer.unions .get(recv.type.unionId) @@ -3570,6 +3582,22 @@ function lowerDynReceiverMethodCall( // 'unknown' slot does. if (checkerUntyped && DYN_HANDLE_KINDS.has(recv.type.kind)) recv = lowerer.coerceToExpected(recv, DYN); + // The last-resort retry (boxTypedTemporary — every other lowering + // already declined): a checker-`any` receiver whose value is a FRESH + // typed result of an earlier step in the chain (`out.split("\n")` + // extracted the string and answered a native string[]) has no typed + // home under its `any` spelling, so it boxes into the checked-dynamic + // tree and the call dispatches on the runtime kind — `.map`, `.pop`, + // `.filter` on that array run JS-exact. Only call results qualify: + // the box copies, and a temporary has no other holder to observe it. + if ( + boxTypedTemporary && + checkerUntyped && + recv.type.kind !== "dyn" && + isFreshCallResult(access.expression) && + lowerer.dynConvertible(recv.type) + ) + recv = lowerer.coerceToExpected(recv, DYN); if (recv.type.kind !== "dyn") return null; if ( access.name.text === "normalize" && @@ -3626,7 +3654,7 @@ function lowerDynReceiverMethodCall( kind: "dynInvoke", recv: value, method: "normalize", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: hasArgument ? [argument] : [], type: DYN, loc, @@ -3662,7 +3690,7 @@ function lowerDynReceiverMethodCall( // dynKeyGet cannot stand in for JavaScript's prototype lookup. Only // an authored declaration can justify the own-member path. const authored = prop.declarations.some( - (decl) => !lowerer.isStdlibFile(decl.getSourceFile()), + (decl) => !lowerer.isStdlibFile(ts.getSourceFileOfNode(decl)), ); return ( authored && @@ -3704,7 +3732,7 @@ function lowerDynReceiverMethodCall( lowerer, call, access.name.text, - access.getText(), + ts.getTextOfNode(access), recv, ); if (primitive) return primitive; @@ -3719,7 +3747,7 @@ function lowerDynReceiverMethodCall( args: [ recv, argument ? lowerer.lowerExprExpecting(argument, DYN) : dynUndefinedExpr(locOf(call)), - { kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc: locOf(call) }, ], type: STRING, loc: locOf(call), @@ -3734,7 +3762,10 @@ function lowerDynReceiverMethodCall( return { kind: "libCall", fn: "dyn.valueOf", - args: [recv, { kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) }], + args: [ + recv, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc: locOf(call) }, + ], type: DYN, loc: locOf(call), }; @@ -3822,15 +3853,15 @@ function lowerDynReceiverMethodCall( // statement cannot leave the expression). const stringRefusal = (read: IrExpr): IrExpr => { if (!stringOnlyName) return read; - const sf = call.getSourceFile(); - const line = ts.getLineAndCharacterOfPosition(sf, call.getStart(sf)).line + 1; + const sf = ts.getSourceFileOfNode(call); + const line = ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(call)).line + 1; return { kind: "ternary", cond: { kind: "dynTest", test: "string", value: receiver, type: BOOL, loc }, then: nodeThrowExpr( 0, "SC1090", - `method calls like '${access.getText()}' on a string are not supported yet [SC1090 at ${sf.fileName}:${line}]`, + `method calls like '${ts.getTextOfNode(access)}' on a string are not supported yet [SC1090 at ${sf.fileName}:${line}]`, DYN, loc, ), @@ -3866,7 +3897,7 @@ function lowerDynReceiverMethodCall( kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -4156,7 +4187,7 @@ export function lowerDynDispatchMethodCall( ): IrExpr | null { const method = access.name.text; if ( - (!DYN_DISPATCH_METHODS.has(method) && !isJsSourceFile(call.getSourceFile())) || + (!DYN_DISPATCH_METHODS.has(method) && !isJsSourceFile(ts.getSourceFileOfNode(call))) || lowerer.chainBlocked(call, access) ) return null; @@ -4265,7 +4296,7 @@ export function lowerDynDispatchMethodCall( kind: "dynInvoke", recv: { kind: "varRef", localId: saved.id, type: DYN, loc: locOf(access.expression) }, method, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [dynUndefinedExpr(locOf(call.arguments.nodes[0]!))], type: DYN, loc: locOf(call), @@ -4287,7 +4318,7 @@ export function lowerDynDispatchMethodCall( kind: "dynInvoke", recv, method, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc: locOf(call), @@ -4507,7 +4538,7 @@ function lowerNumberToStringCall( call.arguments.nodes[0] ? lowerer.lowerExprExpecting(call.arguments.nodes[0], DYN) : dynUndefinedExpr(locOf(call)), - { kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc: locOf(call) }, ], type: STRING, loc: locOf(call), @@ -4630,7 +4661,7 @@ function lowerDefaultToStringCall( // A null-prototype record (Object.groupBy) has no toString: Node // throws " is not a function". const nullProtoT = nullProtoRecordShape(lowerer, recvT); - const missing = nullProtoT ? `${access.getText()} is not a function` : null; + const missing = nullProtoT ? `${ts.getTextOfNode(access)} is not a function` : null; if (!missing && pureObjectToStringReceiver(access.expression)) return constant; const recv = lowerer.lowerExpr(access.expression); const key = `objToStr:${typeKey(recv.type)}${missing ? `:${missing}` : ""}`; @@ -4781,7 +4812,7 @@ function lowerPrimitiveProtoCall( (name === "toFixed" || name === "toExponential") && call.arguments.nodes.length <= 1 && !call.arguments.nodes.some(ts.isSpreadElement) && - isJsSourceFile(call.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(call)) ) { const operand = lowerer.lowerExpr(recv); if ( @@ -4794,7 +4825,7 @@ function lowerPrimitiveProtoCall( lowerer, call, name, - `${recv.getText()}.${name}`, + `${ts.getTextOfNode(recv)}.${name}`, lowerer.coerceToExpected(operand, DYN), ); if (dispatched) return dispatched; @@ -4807,7 +4838,7 @@ function lowerPrimitiveProtoCall( kind: "dynInvoke", recv: lowerer.coerceToExpected(operand, DYN), method: name, - calleeName: `${recv.getText()}.${name}`, + calleeName: `${ts.getTextOfNode(recv)}.${name}`, args: call.arguments.nodes.map((argument) => lowerer.lowerExprExpecting(argument, DYN)), type: DYN, loc: locOf(call), @@ -4817,7 +4848,7 @@ function lowerPrimitiveProtoCall( name === "toPrecision" && call.arguments.nodes.length <= 1 && !call.arguments.nodes.some(ts.isSpreadElement) && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && recvKind === "union" ) { const operand = lowerer.lowerExpr(recv); @@ -4826,7 +4857,7 @@ function lowerPrimitiveProtoCall( lowerer, call, name, - `${recv.getText()}.${name}`, + `${ts.getTextOfNode(recv)}.${name}`, lowerer.coerceToExpected(operand, DYN), ); if (dispatched) return dispatched; @@ -5046,9 +5077,10 @@ function isSoleAbstractMethod(decl: ts.FunctionDeclaration | ts.MethodDeclaratio if (!ts.getModifiers(decl)?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword)) return false; const owner = decl.parent; if (!ts.isClassDeclaration(owner) && !ts.isClassExpression(owner)) return false; - const name = decl.name.getText(); + const name = ts.getTextOfNode(decl.name); return !owner.members.nodes.some( - (member) => member !== decl && ts.isMethodDeclaration(member) && member.name.getText() === name, + (member) => + member !== decl && ts.isMethodDeclaration(member) && ts.getTextOfNode(member.name) === name, ); } @@ -5066,7 +5098,7 @@ function reconcileOverloadReturn( call.type.kind === "object" && rdecl && ts.isMethodDeclaration(rdecl) && - (!isJsSourceFile(rdecl.getSourceFile()) || returnsOnlyThis(rdecl)) + (!isJsSourceFile(ts.getSourceFileOfNode(rdecl)) || returnsOnlyThis(rdecl)) ) { call = lowerer.maybeNarrow(call, expr); } @@ -5228,7 +5260,7 @@ export function lowerTaggedTemplate(lowerer: Lowerer, expr: ts.TaggedTemplateExp let callee = lowerer.lowerExpr(expr.tag); if ( callee.type.kind === "func" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(callee.type) ) { callee = { kind: "dynFrom", value: callee, type: DYN, loc }; @@ -5246,7 +5278,7 @@ export function lowerTaggedTemplate(lowerer: Lowerer, expr: ts.TaggedTemplateExp ]; const calleeName = ts.isPropertyAccessExpression(expr.tag) || ts.isElementAccessExpression(expr.tag) - ? expr.tag.getText() + ? ts.getTextOfNode(expr.tag) : ts.isIdentifier(expr.tag) ? expr.tag.text : "value"; @@ -5316,7 +5348,7 @@ function loweredTemplateStrings( loc, }) : null; - const key = `templateStringsGlobal:${loc.file}:${expr.template.getStart()}:${raw ? "checked" : "typed"}`; + const key = `templateStringsGlobal:${loc.file}:${ts.tokenStartOfNode(expr.template)}:${raw ? "checked" : "typed"}`; let getter = lowerer.valueHelpers.get(key); if (!getter) { const site = lowerer.globalsList.length; @@ -6033,7 +6065,7 @@ export function lowerPromiseMethodCall( kind: "dynInvoke", recv: lowerer.coerceToExpected(receiver, DYN), method: "finally", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [lowerer.coerceToExpected(cb, DYN)], type: DYN, loc, @@ -6134,7 +6166,7 @@ export function lowerPromiseMethodCall( if ( member === "catch" && (inner.kind === "dyn" || - (isJsSourceFile(call.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(call)) && (inner.kind === "void" || lowerer.dynConvertible(inner)))) ) { let cb = lowerer.lowerExpr(call.arguments.nodes[0]!); @@ -7528,7 +7560,7 @@ function lowerObjectStaticCall( const prototype = lowerClassPrototypeAssign(lowerer, call); if (prototype) return prototype; if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && call.arguments.nodes.length > 0 && !call.arguments.nodes.some(ts.isSpreadElement) ) { @@ -7800,7 +7832,12 @@ function lowerObjectStaticCall( args: [ pRef(), src, - { kind: "strLit", value: srcNode.getText(), type: STRING, loc: argLoc }, + { + kind: "strLit", + value: ts.getTextOfNode(srcNode), + type: STRING, + loc: argLoc, + }, ], type: VOID, loc: argLoc, @@ -7857,7 +7894,9 @@ function lowerObjectStaticCall( (exactClassOfReceiver(lowerer, targetNode.expression) !== null || storedClassValueType(lowerer, targetNode.expression)?.kind === "classval"); const declaredTarget = - target?.type.kind === "dyn" && !prototypeTarget && !isJsSourceFile(targetNode.getSourceFile()) + target?.type.kind === "dyn" && + !prototypeTarget && + !isJsSourceFile(ts.getSourceFileOfNode(targetNode)) ? lowerer.mapTypeOf(lowerer.typeOf(targetNode)) : null; if (target && declaredTarget && isDynTypedRefType(declaredTarget)) @@ -8056,7 +8095,7 @@ function lowerObjectStaticCall( while (ts.isParenthesizedExpression(inner) || ts.isAsExpression(inner)) inner = inner.expression; const value = - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && (ts.isObjectLiteralExpression(inner) || ts.isArrayLiteralExpression(inner)) ? lowerer.lowerExprExpecting(argNode, DYN) : lowerer.lowerExpr(argNode); @@ -8277,7 +8316,7 @@ function lowerObjectStaticCall( probed.type.kind === "bool"); const isJsUnion = probed?.type.kind === "union" && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && lowerer.dynConvertible(probed.type); if ( isDyn || @@ -8299,7 +8338,7 @@ function lowerObjectStaticCall( // write to the arrays well typed. JavaScript may store anything into // them, so it keeps the checked-dynamic arrays. const resultT = - member !== "values" && !isJsSourceFile(call.getSourceFile()) + member !== "values" && !isJsSourceFile(ts.getSourceFileOfNode(call)) ? lowerer.mapTypeOf(lowerer.typeOf(call)) : null; if (member === "keys" && resultT?.kind === "array" && resultT.elem.kind === "string") @@ -8322,7 +8361,11 @@ function lowerObjectStaticCall( // program only iterates or indexes the result, or passes it to a // read-only array method, a native vector of the checked values skips // building that array and, for for-of, the iterator protocol. - if (member === "values" && !isJsSourceFile(call.getSourceFile()) && consumesValueList(call)) + if ( + member === "values" && + !isJsSourceFile(ts.getSourceFileOfNode(call)) && + consumesValueList(call) + ) return { kind: "libCall", fn: "dyn.objValueList", @@ -8360,7 +8403,7 @@ function lowerObjectStaticCall( if ( (member === "values" || member === "entries") && (enumerationType?.kind === "dyn" || - (enumerationType === null && isJsSourceFile(call.getSourceFile()))) + (enumerationType === null && isJsSourceFile(ts.getSourceFileOfNode(call)))) ) { let receiver = lowerer.coerceToExpected(lowerer.lowerExpr(argNode), DYN); if (receiver.type.kind !== "dyn") lowerer.badType(argNode, lowerer.typeOf(argNode)); @@ -8891,7 +8934,7 @@ export function lowerRecordFieldCall( const target = lowerer.fieldTarget(access); const loc = locOf(call); const receiverLocal = - target && isJsSourceFile(call.getSourceFile()) + target && isJsSourceFile(ts.getSourceFileOfNode(call)) ? lowerer.declareHiddenLocal("%callReceiver", target.obj.type) : null; const init: IrStmt | null = @@ -8911,7 +8954,7 @@ export function lowerRecordFieldCall( // values (util.DebugLogger). if ( callee.type.kind === "dyn" && - (isJsSourceFile(call.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(call)) || lowerer.checker.getCallSignatures(lowerer.typeOf(access)).length > 0) ) { const receiver = @@ -8929,7 +8972,7 @@ export function lowerRecordFieldCall( kind: "dynCall", callee, ...(receiver ? { receiver } : {}), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9000,7 +9043,7 @@ function lowerUrlStaticCall( if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken !== undefined) return null; if (!ts.isIdentifier(callee.expression)) return null; const sym = lowerer.resolveValueSymbol(callee.expression); - if (!sym || !lowerer.isStdlibSymbol(sym) || sym.name !== "URL") return null; + if (!sym || !lowerer.isStdlibSymbol(sym) || ts.symbolName(sym) !== "URL") return null; if (callee.name.text === "canParse" || callee.name.text === "parse") { return lowerUrlFactory(lowerer, call, callee.name.text); } @@ -9358,7 +9401,7 @@ function lowerObjLitGenericMethodCall( kind: "dynInvoke", recv: { kind: "dynFrom", value, type: DYN, loc }, method: name, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9367,7 +9410,7 @@ function lowerObjLitGenericMethodCall( lowerer.unsupported( "SC1090", call, - `Function.prototype.${name} on a function whose signature cannot cross the checked-value boundary (call '${access.expression.getText()}(...)' directly)`, + `Function.prototype.${name} on a function whose signature cannot cross the checked-value boundary (call '${ts.getTextOfNode(access.expression)}(...)' directly)`, ); } // STANDARD-LIBRARY generic members (Promise.then, Object. @@ -9395,7 +9438,7 @@ function lowerObjLitGenericMethodCall( kind: "dynInvoke", recv: receiver, method: name, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9456,7 +9499,7 @@ export function lowerObjectMethodCall( (lowerer.implicitParamTypes !== null || lowerer.classImplementedProtocol(lowerer.typeOf(access.expression)) || ts.isNewExpression(access.expression) || - isJsSourceFile(call.getSourceFile())) + isJsSourceFile(ts.getSourceFileOfNode(call))) ? ts.isNewExpression(access.expression) ? lowerer.lowerExpr(access.expression) : tryLowerExpression(lowerer, access.expression) @@ -9475,7 +9518,7 @@ export function lowerObjectMethodCall( if (dispatched) return dispatched; const arms = lowerer.unions.get(mappedReceiver.unionId)?.arms; if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && arms?.every( (arm) => arm.kind === "object" && !lowerer.classes.get(arm.className)?.def.runtime, ) && @@ -9629,7 +9672,7 @@ export function lowerObjectMethodCall( callee, lowerer.armTag(callee.type.unionId, callableType), callableType, - () => `${access.getText()} is not a function`, + () => `${ts.getTextOfNode(access)} is not a function`, ); const helper = inPlace ? null @@ -9643,7 +9686,7 @@ export function lowerObjectMethodCall( if ( ((callee.type.rest === true && callee.type.restAbi === undefined) || call.arguments.nodes.length > callee.type.params.length) && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && lowerer.dynConvertible(callee.type) ) { const boxed: IrExpr = { kind: "dynFrom", value: callee, type: DYN, loc }; @@ -9654,7 +9697,7 @@ export function lowerObjectMethodCall( kind: "dynCall", callee: boxed, ...receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -9672,7 +9715,7 @@ export function lowerObjectMethodCall( kind: "dynCall", callee, ...receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -9681,7 +9724,7 @@ export function lowerObjectMethodCall( } if ( !fieldType && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && !info.def.runtime && !info.builtinError && !info.builtinEmitter && @@ -9711,7 +9754,7 @@ export function lowerObjectMethodCall( kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9760,7 +9803,7 @@ export function lowerObjectMethodCall( call.arguments.nodes, fallback, loc, - access.getText(), + ts.getTextOfNode(access), ); return { kind: "seqExpr", @@ -9796,7 +9839,7 @@ export function lowerObjectMethodCall( const declarerDecl = found.declarer.decl; if ( adapted && - !(declarerDecl && isJsSourceFile(declarerDecl.getSourceFile())) && + !(declarerDecl && isJsSourceFile(ts.getSourceFileOfNode(declarerDecl))) && !lowerer.overrideBelow(info, method) ) { lowerer.noteEdge(`%${found.declarer.def.name}.${method}`); @@ -9886,7 +9929,7 @@ function lowerUnionObjectMethodCall( kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -10048,7 +10091,7 @@ function lowerUnionObjectDynFieldCall( kind: "dynCall", callee, receiver: lowerer.coerceToExpected(receiverRef, DYN), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -10430,7 +10473,7 @@ function maybePromiseHandlerResult( function lowerObjectAssignOntoError(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { const argNodes = call.arguments.nodes; if ( - isJsSourceFile(call.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(call)) || argNodes.length < 2 || argNodes.some((a) => ts.isSpreadElement(a)) ) diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 182a73f14..49e7ad9fc 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -1,4 +1,5 @@ import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js"; +import { adaptPromiseExecutor } from "./promise-executor-adapter.js"; import { dynUndefinedExpr, nodeThrowExpr, varRef } from "../../ir/build.js"; import { constructedFromValue, @@ -116,6 +117,7 @@ import { import { STREAM_API_MEMBERS, STREAM_PROP_MEMBERS, + STREAM_COMPLETION_CALLBACK_INDEX, UNDERSCORE_METHODS, lowerStreamNew, lowerStreamSuperCall, @@ -181,9 +183,9 @@ export function returnsOnlyThis(member: ts.MethodDeclaration): boolean { while (value && ts.isParenthesizedExpression(value)) value = value.expression; if (!value || value.kind !== ts.SyntaxKind.ThisKeyword) valid = false; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return valid; } @@ -214,9 +216,9 @@ export function returnsOnlyThisValue(member: ts.MethodDeclaration): boolean { } if (!value || value.kind !== ts.SyntaxKind.ThisKeyword) valid = false; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return valid; } @@ -339,7 +341,7 @@ export function storedClassValueType( } if ( (ts.isArrowFunction(callee) || ts.isFunctionExpression(callee)) && - isJsSourceFile(callee.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(callee)) && !callee.typeParameters?.nodes.length && !lowerer.preparingClassFactories.has(callee) ) { @@ -408,7 +410,7 @@ export function storedClassValueType( } if ( !factory || - !isJsSourceFile(factory.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(factory)) || factory.typeParameters?.nodes.length || !ts.isVariableDeclaration(factory.parent) || factory.parent.initializer !== factory || @@ -806,7 +808,7 @@ function symbolSlotReturnType( ): IrType | null { if (symbolFields.size === 0) return null; if (fnLike.type !== undefined || !fnLike.body) return null; - if (!isJsSourceFile(fnLike.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(fnLike))) return null; if (fnLike.asteriskToken !== undefined) return null; if (fnLike.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) return null; const stmts = fnLike.body.statements.nodes; @@ -816,9 +818,9 @@ function symbolSlotReturnType( const visit = (n: ts.Node): void => { if (ts.isFunctionLike(n)) return; if (ts.isReturnStatement(n)) returns.push(n); - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - fnLike.body.forEachChild(visit); + ts.forEachChild(fnLike.body, visit); let out: IrType | null = null; for (const r of returns) { let e = r.expression; @@ -1053,7 +1055,7 @@ export function builtinStreamInfoOf( } let irName: string | null = null; for (const [name, rec] of RUNTIME_STREAM_CLASSES) { - if (rec.lib === symbol.name) irName = name; + if (rec.lib === ts.symbolName(symbol)) irName = name; } if (!irName) return null; // Both the bundled declarations and @types/node declare the classes @@ -1094,7 +1096,7 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E // JavaScript documentation cannot guarantee that a call or an indexed // read returns a present number. Numeric fields receiving those values // must retain undefined, so later arithmetic performs JS coercion (NaN). - if (type.kind === "f64" && isJsSourceFile(expression.getSourceFile())) { + if (type.kind === "f64" && isJsSourceFile(ts.getSourceFileOfNode(expression))) { const assignment = expression.parent; const left = ts.isBinaryExpression(assignment) ? assignment.left : null; const field = @@ -1126,7 +1128,7 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E if (ts.isCallExpression(rhs) || ts.isElementAccessExpression(rhs)) acceptsUnchecked = true; } - node.forEachChild(scan); + ts.forEachChild(node, scan); }; scan(owner); if (acceptsUnchecked) return DYN; @@ -1163,7 +1165,7 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E owner = owner.parent; if (owner && (ts.isClassDeclaration(owner) || ts.isClassExpression(owner)) && owner.name) { const className = owner.name.text; - for (const statement of owner.getSourceFile().statements.nodes) { + for (const statement of ts.getSourceFileOfNode(owner).statements.nodes) { if ( !ts.isExpressionStatement(statement) || !ts.isBinaryExpression(statement.expression) @@ -1266,7 +1268,7 @@ function returnsWidenedJsField( ret: IrType, ): boolean { if ( - !isJsSourceFile(member.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(member)) || member.type || member.asteriskToken || hasExplicitJsDocReturn(member) @@ -1329,7 +1331,7 @@ function returnsWidenedJsField( const scan = (node: ts.Node): void => { if (result || ts.isFunctionLike(node)) return; if (ts.isReturnStatement(node) && node.expression && widened(node.expression)) result = true; - node.forEachChild(scan); + ts.forEachChild(node, scan); }; if (member.body) scan(member.body); return result; @@ -1408,7 +1410,7 @@ export function builtinEmitterInfoOf( } return null; } - if (symbol.name !== "EventEmitter") return null; + if (ts.symbolName(symbol) !== "EventEmitter") return null; const declared = symbol.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; let node: ts.Node | undefined = d.parent; @@ -1438,7 +1440,7 @@ export function builtinErrorInfoOf( if (alias !== null) return alias.kind === "error" ? alias.info : null; if (!lowerer.isStdlibSymbol(symbol)) return null; for (const [irName, rec] of RUNTIME_ERROR_CLASSES) { - if (rec.lib === symbol.name) return lowerer.classes.get(irName) ?? null; + if (rec.lib === ts.symbolName(symbol)) return lowerer.classes.get(irName) ?? null; } return null; } @@ -1583,9 +1585,9 @@ function collectClassShapeDeferring(lowerer: Lowerer, decl: ts.ClassDeclaration) if ( baseDeclaration && !lowerer.isStdlibSymbol(baseSymbol ?? undefined) && - !baseDeclaration.getSourceFile().isDeclarationFile && - (baseDeclaration.getSourceFile() !== decl.getSourceFile() || - baseDeclaration.getStart() < decl.getStart()) + !ts.getSourceFileOfNode(baseDeclaration).isDeclarationFile && + (ts.getSourceFileOfNode(baseDeclaration) !== ts.getSourceFileOfNode(decl) || + ts.tokenStartOfNode(baseDeclaration) < ts.tokenStartOfNode(decl)) ) lowerer.collectClassShape(baseDeclaration); lowerer.collectClassShapeInner(decl); @@ -1790,7 +1792,7 @@ export function collectClassShapeInner( // source (V8 has not shipped the proposal; the type-stripping // loaders leave `@dec` in place) — there is no runtime behavior // to be exact against. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { lowerer.unsupported( "SC1090", (classDecoratorNodes[0] ?? decoratorNodesOf(decoratedMembers[0]!)[0])!, @@ -1928,7 +1930,7 @@ export function collectClassShapeInner( const source = signatures.length === 1 ? signatures[0]!.declaration : undefined; if ( !source || - !isJsSourceFile(source.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(source)) || !(ts.isFunctionDeclaration(source) || ts.isFunctionExpression(source)) ) return false; @@ -2044,13 +2046,13 @@ export function collectClassShapeInner( const baseDecl = propBase.decl; if ( baseDecl != null && - baseDecl.getSourceFile() === decl.getSourceFile() && - baseDecl.getStart() > decl.getStart() + ts.getSourceFileOfNode(baseDecl) === ts.getSourceFileOfNode(decl) && + ts.tokenStartOfNode(baseDecl) > ts.tokenStartOfNode(decl) ) { lowerer.unsupported( "SC1090", t, - `extending '${t.expression.getText()}' above the statement that assigns it (the property is still undefined when this class evaluates — Node throws here; assign the base first)`, + `extending '${ts.getTextOfNode(t.expression)}' above the statement that assigns it (the property is still undefined when this class evaluates — Node throws here; assign the base first)`, ); } base = propBase; @@ -2068,7 +2070,7 @@ export function collectClassShapeInner( lowerer.unsupported( "SC1090", t, - `extending the reassigned property '${t.expression.getText()}' (the runtime base is whichever assignment ran last — bind the class exactly once)`, + `extending the reassigned property '${ts.getTextOfNode(t.expression)}' (the runtime base is whichever assignment ran last — bind the class exactly once)`, ); } } @@ -2287,7 +2289,7 @@ export function collectClassShapeInner( // Public JS accessors need live constructor descriptors, including // receiver-sensitive getters and setters inherited by subclasses. const checkedStatics = - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && ((base && hasRuntimeStatics(base)) || decl.members.nodes.some( (member) => @@ -2361,7 +2363,7 @@ export function collectClassShapeInner( for (const member of decl.members.nodes) { if ( (functionLocalClass(decl) || checkedStatics) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && (ts.isClassStaticBlockDeclaration(member) || ts .getModifiers(member) @@ -2372,7 +2374,7 @@ export function collectClassShapeInner( } if ( functionLocalClass(decl) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && (ts.isMethodDeclaration(member) || ts.isAccessor(member)) && member.body && !ts.isPrivateIdentifier(member.name) @@ -2444,7 +2446,7 @@ export function collectClassShapeInner( if (type.kind === "void") lowerer.badType(member.name, lowerer.typeOf(member.name)); if ( type.kind === "dyn" && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) && (member.type !== undefined || (lowerer.typeOf(member.name).flags & ts.TypeFlags.Any) === 0) @@ -2478,7 +2480,7 @@ export function collectClassShapeInner( } staticMethods.set(member.name.text, { params: shapes, ret: ft.ret, member }); if ( - implicitMonoFile(decl.getSourceFile()) && + implicitMonoFile(ts.getSourceFileOfNode(decl)) && !functionLocalClass(decl) && inst === undefined && decl.typeParameters === undefined @@ -2535,7 +2537,7 @@ export function collectClassShapeInner( // read of an Error-rooted class is the checked-dynamic property // (fieldTarget's errorCause container), so only types that read // back unchanged from it are accepted. - if (!isJsSourceFile(member.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(member))) { if ( modifiers?.some( (m) => @@ -2698,6 +2700,24 @@ export function collectClassShapeInner( ); } } + // A completion callback declared with the broad `Function` type + // (`cb: Function`) takes the checked-dynamic callable the runtime + // passes implicitly typed callbacks; calls through it are + // dynamic calls of that callable. + const callbackIndex = STREAM_COMPLETION_CALLBACK_INDEX.get(name); + const callbackParam = + ts.isMethodDeclaration(member) && callbackIndex !== undefined + ? member.parameters.nodes[callbackIndex] + : undefined; + if ( + callbackParam !== undefined && + ts.isIdentifier(callbackParam.name) && + !callbackParam.dotDotDotToken && + !callbackParam.questionToken && + !callbackParam.initializer && + lowerer.checker.typeToString(lowerer.typeOf(callbackParam.name)) === "Function" + ) + lowerer.checkedCallbackParams.add(callbackParam); } } if (ts.isPropertyDeclaration(member)) { @@ -2736,10 +2756,12 @@ export function collectClassShapeInner( // unconditional `this[key] = ...` at the constructor's top // level), checked with the unguarded fields below. const declaredOnly = - !member.initializer && !isJsSourceFile(member.getSourceFile()) && !!member.type; + !member.initializer && !isJsSourceFile(ts.getSourceFileOfNode(member)) && !!member.type; if ( fieldName === null || - (!member.initializer && !isJsSourceFile(member.getSourceFile()) && !declaredOnly) + (!member.initializer && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && + !declaredOnly) ) { lowerer.unsupported( "SC1090", @@ -2757,7 +2779,9 @@ export function collectClassShapeInner( ? (lowerer.mapTypeOf(inferred) ?? dynFallbackType(lowerer, member, inferred)) : DYN; let type = - mapped?.kind === "record" && isJsSourceFile(member.getSourceFile()) && !member.type + mapped?.kind === "record" && + isJsSourceFile(ts.getSourceFileOfNode(member)) && + !member.type ? DYN : mapped ? jsOpenObjectType(member, mapped, lowerer.shapes, lowerer.unions) @@ -2766,11 +2790,11 @@ export function collectClassShapeInner( !type || type.kind === "void" || (type.kind === "dyn" && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(inferred)) ) lowerer.badType(member, inferred); - if (isJsSourceFile(member.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(member))) { type = inheritedJsFieldType(lowerer, decl, fieldName, type); if (base?.fields.get(fieldName)?.kind === "dyn") type = DYN; if (base && !fields.has(fieldName) && lowerer.dynConvertible(type)) type = DYN; @@ -2884,7 +2908,7 @@ export function collectClassShapeInner( // as undefined on construction, verified), and reads/writes ride // the ordinary undefined-armed union machinery. let type = lowerer.irTypeOf(member.name); - if (isJsSourceFile(member.getSourceFile()) && !member.initializer) { + if (isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.initializer) { const constructor = decl.members.nodes.find( (part): part is ts.ConstructorDeclaration => ts.isConstructorDeclaration(part) && !!part.body, @@ -2912,19 +2936,25 @@ export function collectClassShapeInner( // Unannotated JavaScript callback slots retain the original // callable and its receiver instead of adapting its signature. if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.type && - !/@type\b/.test(member.getSourceFile().text.slice(member.pos, member.getStart())) && + !/@type\b/.test( + ts.getSourceFileOfNode(member).text.slice(member.pos, ts.tokenStartOfNode(member)), + ) && (type.kind === "func" || type.kind === "record") && lowerer.dynConvertible(type) ) type = DYN; // JS permits reads before a constructor's first assignment even // when its inferred field type omits undefined. - if (isJsSourceFile(member.getSourceFile()) && !member.initializer && type.kind !== "dyn") { + if ( + isJsSourceFile(ts.getSourceFileOfNode(member)) && + !member.initializer && + type.kind !== "dyn" + ) { type = lowerer.withUndefinedArmOf(type) ?? DYN; } - if (isJsSourceFile(member.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(member))) { type = inheritedJsFieldType(lowerer, decl, member.name.text, type); if (base?.fields.get(member.name.text)?.kind === "dyn") type = DYN; if (base && !fields.has(member.name.text) && lowerer.dynConvertible(type)) type = DYN; @@ -2935,7 +2965,7 @@ export function collectClassShapeInner( // storage as implicit constructor-assigned JS fields. if ( type.kind === "dyn" && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) ) { lowerer.unsupported( @@ -3207,7 +3237,7 @@ export function collectClassShapeInner( // name (the override stays on the vtable), a shadowed field, or // a generic-class instantiation's member. if ( - implicitMonoFile(decl.getSourceFile()) && + implicitMonoFile(ts.getSourceFileOfNode(decl)) && !functionLocalClass(decl) && ts.isIdentifier(member.name) && !lowerer.virtualJsMethods.has(member) && @@ -3248,14 +3278,14 @@ export function collectClassShapeInner( // JS inference may retain only a symbol sentinel while a method // also returns values from an untyped mutable field or local. if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.type && !hasExplicitJsDocReturn(member) && ft.ret.kind === "symbol" ) ft.ret = DYN; if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && lowerer.prototypeMethodAccesses.has(mName) && !lowerer.findMethodOn(base, mName)?.declarer.def.runtime && shapes.every((p) => p.mode === "required" || p.mode === "omittable") @@ -3309,7 +3339,7 @@ export function collectClassShapeInner( // against the STATIC receiver's shape, so `m(x?: number)` and // `m(x: number | undefined)` interchange soundly in overrides. const overridden = lowerer.findMethodOn(base, mName); - if (overridden && isJsSourceFile(member.getSourceFile())) { + if (overridden && isJsSourceFile(ts.getSourceFileOfNode(member))) { // An unannotated JS override can return a checked value into // the inherited native ABI, just as its untyped parameters do. // Body lowering inserts the conversion at each return. @@ -3357,7 +3387,7 @@ export function collectClassShapeInner( // readers keep the exact-signature fence. if ( overridden && - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && shapes.length < overridden.sig.params.length && !bodyReadsArguments(member) && shapes.every( @@ -3385,9 +3415,9 @@ export function collectClassShapeInner( // a statically typed receiver see the same ABI. if ( overridden && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && overridden.declarer.decl !== null && - isJsSourceFile(overridden.declarer.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(overridden.declarer.decl)) && member.asteriskToken === undefined && !overridden.sig.async && !overridden.sig.gen && @@ -3438,9 +3468,9 @@ export function collectClassShapeInner( )) ) { const fencedJsOverride = - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && overridden.declarer.decl !== null && - isJsSourceFile(overridden.declarer.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(overridden.declarer.decl)) && overridden.sig.gen === undefined; // A TypeScript override that only changes the result, into a // type that converts into the inherited one (`Promise` over @@ -3490,7 +3520,7 @@ export function collectClassShapeInner( // into the inherited type (`toString() { return // this.message }` over Error's). (ft.ret.kind === "dyn" && - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && overridden.sig.ret.kind !== "void" && canDynCheckTo( overridden.sig.ret, @@ -3564,7 +3594,7 @@ export function collectClassShapeInner( overridden && !returnAdapters.has(mName) && (asyncMember || overridden.sig.async === true) && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !functionLocalClass(decl) && member.asteriskToken === undefined && overridden.sig.gen === undefined && @@ -3586,7 +3616,7 @@ export function collectClassShapeInner( (asyncMember || overridden.sig.async === true) ) { const message = `overriding ${overridden.sig.async === true ? "the async method" : "a method with an async method"} '${mName}' (async methods dispatch statically — the vtable slot machinery has no fiber-spawn story)`; - if (isJsSourceFile(member.getSourceFile())) + if (isJsSourceFile(ts.getSourceFileOfNode(member))) methodEntryFences.set(mName, unsupportedDiag("SC1090", locOf(member.name), message)); else lowerer.unsupported("SC1090", member.name, message); } @@ -3595,7 +3625,7 @@ export function collectClassShapeInner( (member.asteriskToken !== undefined || overridden.sig.gen !== undefined) ) { const message = `overriding the generator method '${mName}' (generator methods dispatch statically — a virtual slot cannot enter their spawn wrapper)`; - if (isJsSourceFile(member.getSourceFile())) + if (isJsSourceFile(ts.getSourceFileOfNode(member))) methodEntryFences.set(mName, unsupportedDiag("SC1090", locOf(member.name), message)); else lowerer.unsupported("SC1090", member.name, message); } @@ -3667,7 +3697,7 @@ export function collectClassShapeInner( const mName = `${isGet ? "get" : "set"}:${prop}`; if ( fields.has(prop) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && base?.fields.has(prop) && !fieldOrder.some((field) => field.name === prop) ) { @@ -3982,7 +4012,7 @@ export function collectClassShapeInner( // assignment ran — the zeroed-memory trap a TS declaration order // forbids via strictPropertyInitialization — so they keep a named // fence instead of reading undefined without a diagnostic. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { const hasAccessor = (name: string): boolean => methods.has(`get:${name}`) || methods.has(`set:${name}`) || @@ -4006,7 +4036,7 @@ export function collectClassShapeInner( // goes through the key. const lateBoundByKey = new Map(); for (const p of instType ? lowerer.checker.getPropertiesOfType(instType) : []) { - if (!p.name.startsWith("__@")) continue; + if (!ts.symbolName(p).startsWith("__@")) continue; const keySym = lateBoundKeySymOf(lowerer, p); if (keySym) lateBoundByKey.set(keySym, p); } @@ -4062,7 +4092,7 @@ export function collectClassShapeInner( if ( t && (t.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) type = DYN; if (type) type = jsOpenObjectType(assign, type, lowerer.shapes, lowerer.unions); @@ -4180,10 +4210,14 @@ export function collectClassShapeInner( // static layouts cannot represent. Named fence, at the first // assignment site. for (const p of instType ? lowerer.checker.getPropertiesOfType(instType) : []) { - if (errorRooted && p.name === "cause") continue; - if (fields.has(p.name) || methods.has(p.name)) continue; - if (hasAccessor(p.name)) continue; - if (base && (base.fields.has(p.name) || lowerer.findMethodOn(base, p.name))) continue; + if (errorRooted && ts.symbolName(p) === "cause") continue; + if (fields.has(ts.symbolName(p)) || methods.has(ts.symbolName(p))) continue; + if (hasAccessor(ts.symbolName(p))) continue; + if ( + base && + (base.fields.has(ts.symbolName(p)) || lowerer.findMethodOn(base, ts.symbolName(p))) + ) + continue; const site = p.declarations.find( (d) => ts.isPropertyAccessExpression(d) || @@ -4197,7 +4231,7 @@ export function collectClassShapeInner( // identities (symbol parameters, mutable bindings, computed // descriptions) or assignments outside the constructor's top // level. - if (p.name.startsWith("__@")) { + if (ts.symbolName(p).startsWith("__@")) { const keySym = lateBoundKeySymOf(lowerer, p); const key = keySym ? classSymbolKeyOfSymbol(lowerer, keySym) : null; if (key && symbolFields.has(key.identity)) continue; @@ -4217,24 +4251,26 @@ export function collectClassShapeInner( // Unmappable inferences and arm-less kinds keep the fence. // TypeScript classes keep the compile-time fence too: an annotated // program can spell `T | undefined` itself. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { const original = undefArmedFieldType(lowerer, p); const armed = - original === null ? null : inheritedJsFieldType(lowerer, decl, p.name, original); + original === null + ? null + : inheritedJsFieldType(lowerer, decl, ts.symbolName(p), original); if (armed !== null) { // Unknown JS properties use the instance bag: they do not // exist until assigned, and a subclass may declare the same // name as an ordinary layout field. if (armed.kind === "dyn") continue; - fields.set(p.name, armed); - fieldOrder.push({ name: p.name, type: armed, initializer: undefined }); + fields.set(ts.symbolName(p), armed); + fieldOrder.push({ name: ts.symbolName(p), type: armed, initializer: undefined }); continue; } } lowerer.unsupported( "SC1090", site, - `fields assigned outside the constructor's top level ('this.${p.name}' would be undefined until the first assignment runs — assign it unconditionally at the top of the constructor)`, + `fields assigned outside the constructor's top level ('this.${ts.symbolName(p)}' would be undefined until the first assignment runs — assign it unconditionally at the top of the constructor)`, ); } } @@ -4277,7 +4313,7 @@ export function collectClassShapeInner( if ( ctor && !mixinForwarding && - isJsSourceFile(ctor.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(ctor)) && !ctorParams.some((shape) => shape.mode === "rest" || shape.mode === "dynRest") && bodyReadsArguments(ctor) ) { @@ -4672,7 +4708,7 @@ export function lowerStaticFieldInits(lowerer: Lowerer, info: ClassInfo): IrStmt // Keep their refusal attached to the class declaration instead of // letting a poisoned initializer escape the module lowering pass. if (!(error instanceof PoisonError)) throw error; - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { const fence = lowerer.diagSink === null ? lowerer.deferToRuntimeFence(diagsBefore, info.decl, { kind: "statement" }) @@ -4799,8 +4835,16 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] | { pos: number; kind: "field"; f: ClassInfo["staticFields"][number] } | { pos: number; kind: "block"; b: ts.ClassStaticBlockDeclaration }; const items: Item[] = [ - ...info.staticFields.map((f): Item => ({ pos: f.initializer.getStart(), kind: "field", f })), - ...(info.staticBlocks ?? []).map((b): Item => ({ pos: b.getStart(), kind: "block", b })), + ...info.staticFields.map((f): Item => ({ + pos: ts.tokenStartOfNode(f.initializer), + kind: "field", + f, + })), + ...(info.staticBlocks ?? []).map((b): Item => ({ + pos: ts.tokenStartOfNode(b), + kind: "block", + b, + })), ].sort((a, b) => a.pos - b.pos); for (const item of items) { if (item.kind === "block") { @@ -4847,7 +4891,7 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] } catch (e) { if (!(e instanceof PoisonError)) throw e; lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file); - if (isJsSourceFile(f.initializer.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(f.initializer))) { const fence = lowerer.diagSink === null ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) @@ -4906,7 +4950,7 @@ export function analyzeClassDecoration(lowerer: Lowerer, info: ClassInfo): void ts.isVariableDeclaration(vdecl) && vdecl.initializer === undefined && (ts.getCombinedModifierFlags(vdecl) & ts.ModifierFlags.Ambient) !== 0 && - !vdecl.getSourceFile().isDeclarationFile; + !ts.getSourceFileOfNode(vdecl).isDeclarationFile; if (ambientVar || ambientUndefinedFnSymbolOf(lowerer, ambientCallee) !== null) { shapes.push({ kind: "ambientThrow", name: ambientCallee.text }); continue; @@ -5529,7 +5573,7 @@ export function lowerStaticFieldRead( // JavaScript often assigns public statics after the class declaration. // Their storage stays mutable and checked, while reads retain the // checker's concrete type instead of losing the method receiver. - const type = isJsSourceFile(expr.getSourceFile()) + const type = isJsSourceFile(ts.getSourceFileOfNode(expr)) ? null : lowerer.mapTypeOf(lowerer.typeOf(expr)); return type ? lowerer.coerceToExpected(value, type) : value; @@ -5570,7 +5614,7 @@ function namedEvaluationName(expr: ts.ClassLikeDeclaration): string { * (private-brands.ts), so the class must be a named declaration (undici's * webidl FastIterableIterator). The checker rejects writes to that binding. */ function localPrivateFieldsAgree(expr: ts.ClassLikeDeclaration): boolean { - if (!isJsSourceFile(expr.getSourceFile()) || !ts.isClassDeclaration(expr) || !expr.name) + if (!isJsSourceFile(ts.getSourceFileOfNode(expr)) || !ts.isClassDeclaration(expr) || !expr.name) return false; if ( expr.members.nodes.some( @@ -5678,7 +5722,7 @@ export function lowerClassExpressionInfo( expr.members.nodes.some( (member) => decoratorNodesOf(member).length || - (!isJsSourceFile(expr.getSourceFile()) && + (!isJsSourceFile(ts.getSourceFileOfNode(expr)) && (ts.isClassStaticBlockDeclaration(member) || ts .getModifiers(member) @@ -5802,7 +5846,7 @@ export function lowerClassExpression(lowerer: Lowerer, expr: ts.ClassLikeDeclara decoratorNodesOf(expr).length > 0 || expr.members.nodes.some((m) => decoratorNodesOf(m).length > 0) ) { - if (!isJsSourceFile(expr.getSourceFile()) && expr.typeParameters === undefined) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.typeParameters === undefined) { const thrown = guaranteedDecorationThrow(lowerer, expr); if (thrown) { // The expression's static type never materializes — the read @@ -6088,12 +6132,12 @@ function enclosingTopLevelClassAssignment(expr: ts.ClassExpression): ts.BinaryEx * `Common.I`): a second assignment makes the binding dynamic, so * callers refuse to pin the first one's class. */ function countAssignmentsTo(assign: ts.BinaryExpression): number { - const sf = assign.getSourceFile(); + const sf = ts.getSourceFileOfNode(assign); // `exports.I` and `module.exports.I` are the SAME binding in Node // (exports aliases module.exports until a table replaces it) — fold // the member spellings together before comparing. const canon = (lhs: ts.Expression): string => { - const text = lhs.getText().replace(/\s+/g, ""); + const text = ts.getTextOfNode(lhs).replace(/\s+/g, ""); return text.startsWith("module.exports.") ? text.slice("module.".length) : text; }; const target = canon(assign.left); @@ -6151,10 +6195,10 @@ export function lowerStaticMethodCall( if (!ts.isIdentifier(access.expression)) { if ( !isModuleExportsAccess(access.expression) || - !isCjsJsFile(access.getSourceFile(), lowerer.program) + !isCjsJsFile(ts.getSourceFileOfNode(access), lowerer.program) ) { let receiverType = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); - if (receiverType?.kind !== "classval" && isJsSourceFile(call.getSourceFile())) { + if (receiverType?.kind !== "classval" && isJsSourceFile(ts.getSourceFileOfNode(call))) { const receiver = tryLowerExpression(lowerer, access.expression); if (receiver?.type.kind === "classval") receiverType = receiver.type; } @@ -6175,7 +6219,7 @@ export function lowerStaticMethodCall( loc: locOf(call), }; } - const whole = cjsClassExprWholeExportOf(access.getSourceFile()); + const whole = cjsClassExprWholeExportOf(ts.getSourceFileOfNode(access)); if (!whole) return null; return staticCallOn( lowerer, @@ -6472,9 +6516,9 @@ function hasLexicalSuper(root: ts.Node): boolean { ts.isClassExpression(node) ) return; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - root.forEachChild(visit); + ts.forEachChild(root, visit); return found; } @@ -6503,9 +6547,9 @@ function capturesEnclosingScope( if (decl && decl !== scope && inside(decl, scope) && !inside(decl, member)) captured = true; return; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - member.forEachChild(visit); + ts.forEachChild(member, visit); return captured; } @@ -6628,7 +6672,10 @@ export function lowerClassValueProperty( // `module.exports.label` in a class-replaced CJS module: the // receiver is the exact exported class, and the read is // side-effect-free — as bindable as an identifier. - !(isModuleExportsAccess(expr.expression) && isCjsJsFile(expr.getSourceFile(), lowerer.program)) + !( + isModuleExportsAccess(expr.expression) && + isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program) + ) ) { // Devirtualized reads DISCARD the receiver value, so only // side-effect-free receivers are claimed (the instanceOf fold @@ -6892,7 +6939,7 @@ export function collectInheritedJsFieldWrites( if (name !== null) addWrite(name, part.right); } } - part.forEachChild(scan); + ts.forEachChild(part, scan); }; for (const member of node.members.nodes) { if ( @@ -6933,7 +6980,7 @@ export function collectInheritedJsFieldWrites( if ( !owner || (!ts.isClassDeclaration(owner) && !ts.isClassExpression(owner)) || - !isJsSourceFile(owner.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(owner)) ) continue; let fields = lowerer.inheritedJsFieldWrites.get(owner); @@ -6971,7 +7018,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if ( node.heritageClauses?.nodes.length && accessors.length && - isJsSourceFile(node.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(node)) ) { const symbol = lowerer.typeOf(node).getSymbol(); const instance = symbol ? lowerer.checker.getDeclaredTypeOfSymbol(symbol) : null; @@ -6988,7 +7035,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if ( (ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration)) && - isJsSourceFile(declaration.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(declaration)) ) { lowerer.checkedVirtualJsAccessors.add(declaration); lowerer.checkedVirtualJsAccessors.add(member); @@ -7030,12 +7077,12 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou for (const declaration of property ? property.declarations : []) { if ( ts.isMethodDeclaration(declaration) && - isJsSourceFile(declaration.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(declaration)) && !checkedJsOverride(lowerer, declaration, member) ) { lowerer.virtualJsMethods.add(declaration); if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.typeParameters?.nodes && !member.asteriskToken && !member.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) && @@ -7081,7 +7128,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if (node !== member && ts.isFunctionLike(node)) return; if (ts.isReturnStatement(node) && node.expression) lowerer.checkedVirtualJsValueReturns.add(member); - node.forEachChild(scan); + ts.forEachChild(node, scan); }; scan(member); } @@ -7114,7 +7161,7 @@ function checkedJsOverride( ): boolean { return [base, member].every( (method) => - lowerer.checkedJsFile(method.getSourceFile()) && + lowerer.checkedJsFile(ts.getSourceFileOfNode(method)) && method.parameters.nodes.every(documentedParameter), ); } @@ -7262,7 +7309,7 @@ export function lowerClassGenericMethodCall( result.type.kind === "object" && !ts.isExpressionStatement(call.parent) && ts.isMethodDeclaration(info.decl) && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && returnsOnlyThisValue(info.decl) ) { return lowerer.maybeNarrow(result, call); @@ -7367,7 +7414,7 @@ export function lowerClassMembers(lowerer: Lowerer, info: ClassInfo): IrFunction out.push(lowerer.lowerClassCtor(info)); } catch (e) { if (!(e instanceof PoisonError)) throw e; - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { const types: IrType[] = [ { kind: "object", className }, ...info.ctorParams.map((param) => param.type), @@ -8280,7 +8327,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL } catch (e) { if (!(e instanceof PoisonError)) throw e; lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file); - if (isJsSourceFile(f.initializer.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(f.initializer))) { const fence = lowerer.diagSink === null ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) @@ -8779,7 +8826,7 @@ export function lowerSuperMethodCall( kind: "dynCall", callee, receiver: lowerer.coerceToExpected(thisRef, DYN), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -8954,7 +9001,7 @@ export function inheritedBuiltinCtorFence( lowerer.runtimeFences.push(diag); const line = info.decl !== null - ? ts.getLineAndCharacterOfPosition(info.decl.getSourceFile(), at.start).line + 1 + ? ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(info.decl), at.start).line + 1 : null; const where = line !== null ? ` [${diag.code} at ${at.file}:${line}]` : ""; return { kind: "runtimeFence", code: diag.code, message: `${diag.message}${where}`, loc }; @@ -9402,7 +9449,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // lowering ahead of the assignment statement). if ( !expr.expression.questionDotToken && - ((isCjsJsFile(expr.getSourceFile(), lowerer.program) && + ((isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program) && (isModuleExportsAccess(expr.expression.expression) || (ts.isIdentifier(expr.expression.expression) && expr.expression.expression.text === "exports" && @@ -9418,7 +9465,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { memberSym, resolved, lowerer.resolveValueSymbol(expr.expression.name) ?? undefined, - lowerer.cjsModuleExportSymbol(expr.getSourceFile(), expr.expression.name.text), + lowerer.cjsModuleExportSymbol(ts.getSourceFileOfNode(expr), expr.expression.name.text), ]; const exportSym = candidates.find((s) => s !== undefined); const g = candidates @@ -9633,7 +9680,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // libregexp engine the literals ride. The pattern compiles EAGERLY, // so bad input throws Node's catchable SyntaxError at construction. // Checked arguments retain native regexes and perform runtime coercion. - if (symbol && symbol.name === "RegExp" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "RegExp" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments?.nodes ?? []; if (args.length > 2) { lowerer.noLowering(`new RegExp with ${args.length} arguments`, expr); @@ -9701,19 +9748,19 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { loc, }; } - if (symbol && symbol.name === "URL" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "URL" && lowerer.isStdlibSymbol(symbol)) { return lowerUrlNew(lowerer, expr); } // `new URLSearchParams(init?)`: the WHATWG list (stdlib provenance — // see lowerSearchParamsNew for the lowered init shapes). - if (symbol && symbol.name === "URLSearchParams" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "URLSearchParams" && lowerer.isStdlibSymbol(symbol)) { return lowerSearchParamsNew(lowerer, expr, loc); } // Date's read-only value slice: store the constructor's TimeClip'd // epoch milliseconds as the scalar date kind. That is sufficient // for every getter and toISOString; identity and setters stay // fenced, so copying the scalar cannot create an observable lie. - if (symbol && symbol.name === "Date" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "Date" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments?.nodes ?? []; if (args.some(ts.isSpreadElement)) { lowerer.noLowering( @@ -9730,7 +9777,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // pass numbers, completed with the spec's defaults (date 1, time // parts 0) for the seven-f64 ABI. if (args.length > 1) { - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "libCall", fn: "date.nativeNewFields", @@ -9761,7 +9808,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } return { kind: "libCall", fn: "date.newLocal", args: fields, type: DATE_T, loc }; } - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "libCall", // One argument: a string parses with the full date parser. @@ -9806,7 +9853,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // normalized at run time (Node's normalizeEncoding, which throws // ERR_UNKNOWN_ENCODING for an unknown name). Omitted means utf8, // Node's default. - if (symbol && symbol.name === "StringDecoder" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "StringDecoder" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments?.nodes ?? []; if (args.length > 1) { lowerer.noLowering("new StringDecoder with 2 arguments", expr, undefined, symbol); @@ -9840,12 +9887,13 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { loc, }; } + const codec = symbol ? ts.symbolName(symbol) : undefined; if ( symbol && - (symbol.name === "TextDecoder" || symbol.name === "TextEncoder") && + (codec === "TextDecoder" || codec === "TextEncoder") && lowerer.isStdlibSymbol(symbol) ) { - return lowerTextCodecNew(lowerer, expr, symbol.name); + return lowerTextCodecNew(lowerer, expr, codec); } // `new Uint8Array(...)` / `new Uint32Array(...)` / `new // Float32Array(...)` / `new Float64Array(...)`: the typed-array @@ -9879,11 +9927,11 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // types get their half named specifically instead of the component // fence (SC2009, which names Map slots at value positions elsewhere). // Both Array() and new Array() share the elements/count lowering. - if (symbol?.name === "Object" && lowerer.isStdlibSymbol(symbol)) { + if (symbol !== null && ts.symbolName(symbol) === "Object" && lowerer.isStdlibSymbol(symbol)) { const object = lowerObjectConstructor(lowerer, expr.arguments?.nodes ?? [], loc); if (object) return object; } - if (symbol?.name === "Array" && lowerer.isStdlibSymbol(symbol)) { + if (symbol !== null && ts.symbolName(symbol) === "Array" && lowerer.isStdlibSymbol(symbol)) { return lowerArrayConstructor(lowerer, expr, expr.arguments?.nodes ?? []); } // A const alias (`const M = Map`) constructs the same Map. @@ -9899,7 +9947,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { : null; let tsType = lowerer.typeOf(expr); let mapped = lowerer.mapTypeOf(tsType); - if (mapped === null && isJsSourceFile(expr.getSourceFile())) mapped = lowerer.irTypeOf(expr); + if (mapped === null && isJsSourceFile(ts.getSourceFileOfNode(expr))) + mapped = lowerer.irTypeOf(expr); const fieldType = assignedThisFieldType(lowerer, expr); if (mapped?.kind !== "map" && fieldType?.kind === "map") mapped = fieldType; // A fresh map built from pair literals can use its destination's @@ -9961,7 +10010,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // unconditionally; the format path never touches them. TypeScript // keeps the compile fence. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.arguments?.nodes.length ?? 0) === 0 && targs.length > 0 && targs.every((t) => (t.flags & ts.TypeFlags.Any) !== 0) @@ -9997,7 +10046,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } // Weak containers share checked native key identities and lifetime hooks. if ( - (symbol?.name === "WeakMap" || symbol?.name === "WeakSet") && + ((symbol !== null && ts.symbolName(symbol) === "WeakMap") || + (symbol !== null && ts.symbolName(symbol) === "WeakSet")) && lowerer.isStdlibSymbol(symbol) ) { const args = expr.arguments?.nodes ?? []; @@ -10005,13 +10055,13 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { lowerer.unsupported( "SC1090", expr, - `${symbol.name} construction with surplus or spread arguments`, + `${ts.symbolName(symbol)} construction with surplus or spread arguments`, ); } return { kind: "libCall", - fn: symbol.name === "WeakMap" ? "weakMap.new" : "weakSet.new", + fn: ts.symbolName(symbol) === "WeakMap" ? "weakMap.new" : "weakSet.new", args: [args[0] ? lowerer.lowerExprExpecting(args[0], DYN) : dynUndefinedExpr(loc)], type: DYN, loc, @@ -10030,7 +10080,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } if ( !mapped && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.arguments?.nodes.length ?? 0) <= 1 && !expr.arguments?.nodes.some(ts.isSpreadElement) ) { @@ -10053,7 +10103,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // the honest construction is a Set of those scalars. if ( !mapped && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.arguments?.nodes.length ?? 0) === 1 && ts.isArrayLiteralExpression(expr.arguments!.nodes[0]!) && !(expr.arguments!.nodes[0] as ts.ArrayLiteralExpression).elements.nodes.some( @@ -10096,7 +10146,11 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // `new AsyncLocalStorage()` (node:async_hooks): a fresh store id — // an f64 handle (type-mapper.ts), the Channel story. Construction options // ({ defaultValue, name }) have no lowering yet. - if (symbol?.name === "AsyncLocalStorage" && lowerer.isStdlibSymbol(symbol)) { + if ( + symbol !== null && + ts.symbolName(symbol) === "AsyncLocalStorage" && + lowerer.isStdlibSymbol(symbol) + ) { if ((expr.arguments?.nodes.length ?? 0) > 0) { lowerer.noLowering( "new AsyncLocalStorage(options)", @@ -10107,7 +10161,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { return { kind: "libCall", fn: "als.new", args: [], type: F64, loc }; } // `new Promise((resolve) => ...)`: the ambient Promise constructor. - if (symbol?.name === "Promise" && lowerer.isStdlibSymbol(symbol)) { + if (symbol !== null && ts.symbolName(symbol) === "Promise" && lowerer.isStdlibSymbol(symbol)) { const type = lowerer.irTypeOf(expr); if (type.kind !== "promise") lowerer.badType(expr, lowerer.typeOf(expr)); const args = expr.arguments?.nodes ?? []; @@ -10123,7 +10177,10 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { if (ts.isIdentifier(a0) && a0.text === "setImmediate") { const sym = lowerer.checker.getSymbolAtLocation(a0); const decls = sym ? sym.declarations : []; - if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(d.getSourceFile()))) { + if ( + decls.length > 0 && + decls.every((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) + ) { // The settled value is the undefined dyn value — the result // is promise whatever T the checker inferred for the // unusual executor (Promise in the suite's shape). @@ -10144,7 +10201,9 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // catch-side instanceof and the uncaught printer working). First // settle wins, exactly JS: reject-after-resolve and double-reject // are no-ops, and an executor throw after any settle is swallowed. - const executor = lowerer.lowerExpr(args[0]!); + let executor = lowerer.lowerExpr(args[0]!); + if (executor.type.kind !== "func") lowerer.badType(args[0]!, lowerer.typeOf(args[0]!)); + executor = adaptPromiseExecutor(lowerer, executor, type.inner, loc) ?? executor; if (executor.type.kind !== "func") lowerer.badType(args[0]!, lowerer.typeOf(args[0]!)); if (executor.type.params.length > 1) { const rj = executor.type.params[1]!; @@ -10452,7 +10511,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { if ( def.arms.every( (arm) => - arm.kind === "classval" || (isJsSourceFile(expr.getSourceFile()) && isUnitType(arm)), + arm.kind === "classval" || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && isUnitType(arm)), ) && lowerer.dynConvertible(calleeT) ) { @@ -10485,7 +10545,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const sym = raw && raw.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(raw) : raw; const hint = - sym && lowerer.isStdlibSymbol(sym) ? own(STDLIB_CTOR_HINTS, sym.name) : undefined; + sym && lowerer.isStdlibSymbol(sym) ? own(STDLIB_CTOR_HINTS, ts.symbolName(sym)) : undefined; if (hint !== undefined) { lowerer.unsupported("SC1090", expr, hint); } diff --git a/packages/compiler/src/frontend/lowering/lower-comptime.ts b/packages/compiler/src/frontend/lowering/lower-comptime.ts index f239eea82..e442b34a5 100644 --- a/packages/compiler/src/frontend/lowering/lower-comptime.ts +++ b/packages/compiler/src/frontend/lowering/lower-comptime.ts @@ -86,7 +86,7 @@ export function lowerComptime(lowerer: Lowerer, expr: ts.CallExpression): IrExpr try { if (lowerer.frontendServices === undefined) throw new Error("compile-time evaluation requires frontend services"); - result = lowerer.frontendServices.evaluateComptime(cb.getText(), COMPTIME_TIMEOUT_MS); + result = lowerer.frontendServices.evaluateComptime(ts.getTextOfNode(cb), COMPTIME_TIMEOUT_MS); } catch (e) { const detail = comptimeFailureDetail(e); lowerer.pushDiag(comptimeFailedDiag(detail, locOf(cb))); @@ -226,7 +226,7 @@ export function rejectComptimeCaptures( lowerer: Lowerer, cb: ts.ArrowFunction | ts.FunctionExpression, ): void { - const sf = cb.getSourceFile(); + const sf = ts.getSourceFileOfNode(cb); const inTypePosition = (node: ts.Node): boolean => { for (let p: ts.Node | undefined = node.parent; p && p !== cb; p = p.parent) { if (ts.isTypeNode(p)) return true; @@ -249,7 +249,7 @@ export function rejectComptimeCaptures( const decls = symbol ? symbol.declarations : undefined; if (symbol && decls && decls.length > 0) { const ownLocal = decls.every( - (d) => d.getSourceFile() === sf && d.pos >= cb.pos && d.end <= cb.end, + (d) => ts.getSourceFileOfNode(d) === sf && d.pos >= cb.pos && d.end <= cb.end, ); if (!ownLocal) { if (node.text === "comptime" && lowerer.isStdlibSymbol(symbol)) { diff --git a/packages/compiler/src/frontend/lowering/lower-console.ts b/packages/compiler/src/frontend/lowering/lower-console.ts index a7fac4f51..eec308342 100644 --- a/packages/compiler/src/frontend/lowering/lower-console.ts +++ b/packages/compiler/src/frontend/lowering/lower-console.ts @@ -209,7 +209,7 @@ export function lowerConsoleInstanceCall( if (access.questionDotToken) return null; const recvType = lowerer.typeOf(access.expression); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Console" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Console" || !lowerer.isStdlibSymbol(sym)) return null; const toStderr = OUTPUT_MEMBERS.get(access.name.text); if (toStderr === undefined) { lowerer.noLowering( diff --git a/packages/compiler/src/frontend/lowering/lower-dgram.ts b/packages/compiler/src/frontend/lowering/lower-dgram.ts index 17522971d..4cb238352 100644 --- a/packages/compiler/src/frontend/lowering/lower-dgram.ts +++ b/packages/compiler/src/frontend/lowering/lower-dgram.ts @@ -195,7 +195,7 @@ export function lowerDgramDnsModuleCall( sawType = true; } else if (name === "reuseAddr") { reuseAddr = lowerer.lowerExprExpecting(prop.initializer, BOOL); - } else if (name === "signal" && isJsSourceFile(expr.getSourceFile())) { + } else if (name === "signal" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { // A provably-non-AbortSignal signal (the invalid-input probes: // strings, numbers, plain records) throws Node's // validateAbortSignal ladder; plausible signal values keep the @@ -578,7 +578,7 @@ export function lowerDgramMethodCall( return { kind: "libCall", fn, args: [receiver, data, port, host], type: VOID, loc }; } if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && args.length <= 5 && !args.some(ts.isSpreadElement) ) { diff --git a/packages/compiler/src/frontend/lowering/lower-enums.ts b/packages/compiler/src/frontend/lowering/lower-enums.ts index cc2797fb9..e72dd2780 100644 --- a/packages/compiler/src/frontend/lowering/lower-enums.ts +++ b/packages/compiler/src/frontend/lowering/lower-enums.ts @@ -36,14 +36,14 @@ import { nodeThrowExpr } from "../../ir/build.js"; * the only name forms tsc allows on enum members). */ function memberNameText(member: ts.EnumMember): string { const n = member.name; - return ts.isIdentifier(n) || ts.isStringLiteralLike(n) ? n.text : n.getText(); + return ts.isIdentifier(n) || ts.isStringLiteralLike(n) ? n.text : ts.getTextOfNode(n); } /** Ambient: a `declare`d declaration, or one living in a .d.ts. */ function isAmbientEnumDecl(decl: ts.EnumDeclaration): boolean { return ( (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Ambient) !== 0 || - decl.getSourceFile().isDeclarationFile + ts.getSourceFileOfNode(decl).isDeclarationFile ); } @@ -126,7 +126,7 @@ function lowerEnumMemberRead( // runtime values, so SC2020-family member fences own those. A user's // `declare enum` follows the // `declare const __VERSION__` stance: ReferenceError at the access. - const sf = enumDecl.getSourceFile(); + const sf = ts.getSourceFileOfNode(enumDecl); if (lowerer.isStdlibFile(sf)) return null; const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (!declared || declared.kind === "void") return null; @@ -165,10 +165,10 @@ function lowerEnumReverseRead( key: ts.Expression, ): IrExpr | null { const loc = locOf(expr); - const enumName = recvSym.name; + const enumName = ts.symbolName(recvSym); // Ambient: no object exists — the member-read stance. if (decls.every((d) => isAmbientEnumDecl(d))) { - if (decls.some((d) => lowerer.isStdlibFile(d.getSourceFile()))) { + if (decls.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d)))) { return null; } return { diff --git a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts index 4b8b9ea14..f8588fe48 100644 --- a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts +++ b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts @@ -72,6 +72,8 @@ import { STREAM_FORCED_EVENT_NAMES, streamForcedTuple, streamSidesOf } from "./l import { boolLit, strLit, varRef } from "../../ir/build.js"; import { tryLowerExpression } from "./expressions/try-lower-expression.js"; import { lowerAddAbortListener } from "./builtin-calls.js"; +import { requestGeneralStreamSurface } from "./general-streams.js"; +import { nodeStreamInterfaceSide } from "./dyn-streams.js"; /** True when the class descends from (or is) the runtime emitter. */ export function emitterRooted(lowerer: Lowerer, info: ClassInfo | undefined | null): boolean { @@ -412,7 +414,11 @@ function isErrorMonitorRef(lowerer: Lowerer, arg: ts.Expression): boolean { const isMonitorType = (t: ts.Type): boolean => { if (!(t.flags & ts.TypeFlags.UniqueESSymbol)) return false; const symbol = t.getSymbol(); - return symbol?.name === "errorMonitor" && lowerer.isStdlibSymbol(symbol); + return ( + symbol !== undefined && + ts.symbolName(symbol) === "errorMonitor" && + lowerer.isStdlibSymbol(symbol) + ); }; // The unique symbol TYPE names its declaration through any alias. if (isMonitorType(lowerer.typeOf(arg))) return true; @@ -1695,7 +1701,7 @@ export function emitOverrideShapeReason( const p0T = lowerer.mapTypeOf(lowerer.typeOf(p0.name)); const jsUnannotated = p0.type === undefined && - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && (p0T === null || p0T.kind === "dyn"); if (p0T?.kind !== "string" && !jsUnannotated) { return "the event parameter must be typed 'string' (symbol event names have no lowering)"; @@ -1729,9 +1735,9 @@ export function emitOverrideShapeReason( "the event parameter cannot be reassigned (the forward's event name must stay pinned)"; } } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - member.body.forEachChild(visit); + ts.forEachChild(member.body, visit); return reason; } @@ -2204,36 +2210,90 @@ function eventsOnceBoxer( return { kind: "closure", fnName: name, captures: [], type: fnType, loc }; } -/** events.once(emitter, name): a promise of the next `name` event's - * arguments as an array, rejected by an earlier 'error' event (unless the - * awaited event is 'error'). The emitter's listeners are typed by the - * program-wide event table, so the runtime registers one-shot typed - * listeners whose compiler-built boxers turn the event's tuple into the - * array. EventTarget and stream receivers, computed names and options are - * refused. */ +/** The `signal` of an events.once options object literal (a checked- + * dynamic AbortSignal or undefined), or null when the literal names none. + * Other options forms are refused. */ +function eventsOnceSignal(lowerer: Lowerer, node: ts.Expression): IrExpr | null { + if (!ts.isObjectLiteralExpression(node)) { + lowerer.noLowering( + "events.once options that are not an object literal", + node, + "pass the options as an object literal: { signal }", + ); + return null; + } + let signal: IrExpr | null = null; + for (const prop of node.properties.nodes) { + const name = prop.name && ts.isIdentifier(prop.name) ? prop.name.text : null; + if ( + name === "signal" && + signal === null && + (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) + ) { + signal = lowerer.lowerExprExpecting( + ts.isPropertyAssignment(prop) ? prop.initializer : (prop.name as ts.Identifier), + DYN, + ); + } else { + lowerer.noLowering( + "events.once options other than one signal", + prop, + "the lowered options object takes signal: AbortSignal", + ); + } + } + return signal; +} + +/** events.once(emitter, name[, { signal }]): a promise of the next `name` + * event's arguments as an array, rejected by an earlier 'error' event + * (unless the awaited event is 'error') or by the signal's abort with an + * AbortError. The emitter's listeners are typed by the program-wide event + * table (a stream's runtime-emitted events by their fixed tuples), so the + * runtime registers one-shot typed listeners whose compiler-built boxers + * turn the event's tuple into the array. EventTarget receivers, a stream's + * 'data' event, computed names and options other than a literal { signal } + * are refused. */ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: SrcLoc): IrExpr { const args = call.arguments.nodes; - if (args.length < 2 || args.some(ts.isSpreadElement)) { - lowerer.noLowering("events.once without an emitter and an event name", call); - } - if (args.length > 2) { - lowerer.noLowering( - "events.once with options", - args[2]!, - "the lowered form is events.once(emitter, name); AbortSignal options are not supported yet", - ); + if (args.length < 2 || args.length > 3 || args.some(ts.isSpreadElement)) { + lowerer.noLowering("events.once without an emitter, an event name and options", call); } // Node's Promise: the arguments array of checked-dynamic values. const arrayT = arrayOf(DYN); const resultT: IrType = { kind: "promise", inner: arrayT }; - const receiver = lowerer.lowerExpr(args[0]!); + let receiver = lowerer.lowerExpr(args[0]!); + // A checked-dynamic NodeJS stream slot (dyn-streams.ts): a native stream + // unboxes by identity; any other value throws the not-supported Error. + if ( + receiver.type.kind === "dyn" && + lowerer.dynNodeStreams && + nodeStreamInterfaceSide(lowerer, lowerer.typeOf(args[0]!)) !== null + ) { + receiver = { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + receiver, + strLit( + "events.once over stream objects other than native streams is not supported yet", + loc, + ), + ], + type: { kind: "object", className: "%Duplex" }, + loc, + }; + } const info = receiver.type.kind === "object" ? lowerer.classes.get(receiver.type.className) : undefined; - if (!emitterRooted(lowerer, info) || streamSidesOf(lowerer, info) !== null) { + if (!emitterRooted(lowerer, info)) { + // A NodeJS stream interface slot that holds a stdio handle here: the + // program lowers again with the runtime stream classes. + requestGeneralStreamSurface(lowerer, args[0]!); lowerer.noLowering( "events.once over this value", args[0]!, - "the emitter must be an EventEmitter instance; EventTarget and stream receivers are not supported yet", + "the emitter must be an EventEmitter or stream instance; EventTarget receivers are not supported yet", ); } const name = literalEventName(lowerer, args[1]!); @@ -2245,6 +2305,14 @@ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: ); } const event = name ?? ""; + const sides = streamSidesOf(lowerer, info); + if ((sides === "r" || sides === "rw") && (event === "data" || event === "keypress")) { + lowerer.noLowering( + `events.once of a stream's '${event}' event`, + args[1]!, + `the chunk has no checked-dynamic boxing here; register stream.once('${event}', listener) instead`, + ); + } const table = emitterEvents(lowerer); if (event !== "error" && flexibleJsEvent(event, table.get(event))) { lowerer.noLowering( @@ -2252,14 +2320,22 @@ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: args[1]!, ); } - const tuple = tupleOf(lowerer, table, event, call); + const tuple = streamForcedTuple(lowerer, info, event) ?? tupleOf(lowerer, table, event, call); const errorTuple: IrType[] = [{ kind: "object", className: "%Error" }]; const boxer = eventsOnceBoxer(lowerer, call, tuple, arrayT, loc); const errorBoxer = eventsOnceBoxer(lowerer, call, errorTuple, arrayT, loc); + const signal = args.length > 2 ? eventsOnceSignal(lowerer, args[2]!) : null; + const common = [ + receiver, + eventNameExpr(event, loc), + boxer, + errorBoxer, + boolLit(event !== "error", loc), + ]; return { kind: "libCall", - fn: "events.once", - args: [receiver, eventNameExpr(event, loc), boxer, errorBoxer, boolLit(event !== "error", loc)], + fn: signal === null ? "events.once" : "events.onceSignal", + args: signal === null ? common : [...common, signal], type: resultT, loc, }; diff --git a/packages/compiler/src/frontend/lowering/lower-expando.ts b/packages/compiler/src/frontend/lowering/lower-expando.ts index 5ed97d4d7..6adce8f67 100644 --- a/packages/compiler/src/frontend/lowering/lower-expando.ts +++ b/packages/compiler/src/frontend/lowering/lower-expando.ts @@ -98,8 +98,8 @@ function expandoFnSymbolOf(lowerer: Lowerer, recv: ts.Expression): ts.Symbol | n const sym = lowerer.resolveValueSymbol(recv); if (!sym) return null; const decl = sym.valueDeclaration; - if (!decl || decl.getSourceFile().isDeclarationFile) return null; - if (isJsSourceFile(decl.getSourceFile())) return null; + if (!decl || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) return null; if (ts.isFunctionDeclaration(decl)) { return ts.isSourceFile(decl.parent) ? sym : null; } @@ -177,7 +177,7 @@ export function collectExpandoMembers(lowerer: Lowerer, sf: ts.SourceFile): void } const existing = memberOf(members, w.key); if (existing) { - existing.firstWriteStart = Math.min(existing.firstWriteStart, node.getStart()); + existing.firstWriteStart = Math.min(existing.firstWriteStart, ts.tokenStartOfNode(node)); } else if ( !(typeof w.key === "string" && READONLY_FN_MEMBERS.has(w.key)) && !nsOwnedMember(lowerer, w.access) @@ -198,18 +198,22 @@ export function collectExpandoMembers(lowerer: Lowerer, sf: ts.SourceFile): void } if (type?.kind === "void" && isUnitOnlyTsType(tsType)) type = unitOnlyUnion(lowerer.unions); if (type && type.kind !== "void") { - const fnName = w.fnSym.name; + const fnName = ts.symbolName(w.fnSym); const memberName = typeof w.key === "string" ? w.key - : `sym%${w.key.name}%${w.key.declarations[0]?.getStart() ?? 0}`; + : `sym%${ts.symbolName(w.key)}%${(w.key.declarations.length === 0 ? undefined : ts.tokenStartOfNode(w.key.declarations[0]!)) ?? 0}`; const g: IrGlobal = { id: `%g.${tag}${fnName}%.${memberName}`, name: `${fnName}.${memberName}`, type, mutable: true, }; - const entry: ExpandoMember = { global: g, firstWriteStart: node.getStart(), file: sf }; + const entry: ExpandoMember = { + global: g, + firstWriteStart: ts.tokenStartOfNode(node), + file: sf, + }; if (typeof w.key === "string") members.names.set(w.key, entry); else members.symbols.set(w.key, entry); lowerer.globalsList.push(g); @@ -248,7 +252,7 @@ function nsOwnedMember( if (ts.isModuleDeclaration(p)) { return !( p.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword) || - p.getSourceFile().isDeclarationFile + ts.getSourceFileOfNode(p).isDeclarationFile ); } } @@ -308,11 +312,11 @@ export function expandoMemberRead( const member = memberOf(lowerer.expandoMembers.get(fnSym), key); if (!member) return null; if ( - access.getSourceFile() === member.file && + ts.getSourceFileOfNode(access) === member.file && initExecutingPosition(access) && - access.getStart() < member.firstWriteStart + ts.tokenStartOfNode(access) < member.firstWriteStart ) { - const name = typeof key === "string" ? key : key.name; + const name = typeof key === "string" ? key : ts.symbolName(key); lowerer.unsupported( "SC1090", access, diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index e770b8d58..7789f8a86 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -193,9 +193,9 @@ export function templateRawTextOf( ): string { const own = (node as { rawText?: string }).rawText; if (own !== undefined) return own; - const sf = node.getSourceFile(); - const start = node.getStart(sf); - const end = node.getEnd(); + const sf = ts.getSourceFileOfNode(node); + const start = ts.tokenStartOfNode(node); + const end = node.end; const tailTrim = node.kind === ts.SyntaxKind.TemplateHead || node.kind === ts.SyntaxKind.TemplateMiddle ? 2 : 1; return sf.text.slice(start + 1, end - tailTrim); @@ -296,14 +296,14 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // integer within ±(2^53−1)) and non-integer spellings carry // nothing, so the IR is unchanged for programs that held their // numbers. - const spelled = expr.getText().replace(/_/g, ""); + const spelled = ts.getTextOfNode(expr).replace(/_/g, ""); if (/^\d+$/.test(spelled) && String(Number(spelled)) !== spelled) { return { kind: "numLit", value: Number(spelled), spelling: spelled, type: F64, loc }; } return { kind: "numLit", value, type: F64, loc }; } if (ts.isBigIntLiteral(expr)) { - const spelling = expr.getText().replace(/_/g, "").replace(/n$/i, ""); + const spelling = ts.getTextOfNode(expr).replace(/_/g, "").replace(/n$/i, ""); return { kind: "libCall", fn: "bigint.parse", @@ -549,7 +549,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // the read answers the strict-mode plain-call undefined, the old // constant. TypeScript keeps the fence (noImplicitThis makes it a // compile-time story there). - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { // Module scope of a CommonJS file: `this` is module.exports. const exportsThis = lowerCjsTopLevelThis(lowerer, expr); if (exportsThis) return exportsThis; @@ -1548,7 +1548,7 @@ export function lowerAbsenceProbe(lowerer: Lowerer, node: ts.Expression): IrExpr // statement ruled non-null without narrowing it (jose's secs()). if ( receiverT?.kind === "union" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && !expr.questionDotToken && !hasOptionalChainGuard(expr.expression) ) { @@ -1738,7 +1738,8 @@ export function stringWrapperToString(lowerer: Lowerer, node: ts.Expression): Ir while (ts.isParenthesizedExpression(e)) e = e.expression; if (!ts.isNewExpression(e) || !ts.isIdentifier(e.expression)) return null; const symbol = lowerer.resolveValueSymbol(e.expression); - if (symbol?.name !== "String" || !lowerer.isStdlibSymbol(symbol)) return null; + if (symbol === null || ts.symbolName(symbol) !== "String" || !lowerer.isStdlibSymbol(symbol)) + return null; const args = e.arguments?.nodes ?? []; if (args.length > 1 || args.some(ts.isSpreadElement)) return null; if (args.length === 0) return { kind: "strLit", value: "", type: STRING, loc: locOf(e) }; @@ -2027,7 +2028,7 @@ export function aliasTypeofNarrows( const leaf = aliasTypeofArmOf(lowerer, e, strip); if (leaf) out.push(leaf); }; - if (implicitMonoFile(cond.getSourceFile())) walk(cond, polarity); + if (implicitMonoFile(ts.getSourceFileOfNode(cond))) walk(cond, polarity); return out; } @@ -2127,7 +2128,7 @@ export function cjsAccessorSnapshotOf( ): { id: string; type: IrType } | null { const hit = lowerer.cjsAccessorSnapshots.get(getter); if (hit !== undefined) return hit; - if (!lexerVisibleAccessors(getter.getSourceFile()).includes(getter)) return null; + if (!lexerVisibleAccessors(ts.getSourceFileOfNode(getter)).includes(getter)) return null; const call = cjsAccessorCall(lowerer, getter, locOf(getter)); if (call === null) return null; const snapshot = { id: `%g.cjsSnapshot.${lowerer.cjsAccessorSnapshots.size}`, type: call.type }; @@ -2576,7 +2577,7 @@ export function lowerOptionalArrayIndexRead( key: IrExpr, ): IrExpr | null { if ( - !isJsSourceFile(expr.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(expr)) || expr.questionDotToken || hasOptionalChainGuard(expr.expression) ) diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index f0f2c45ea..bec8528c4 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -130,7 +130,7 @@ function declareFunctionArguments( lowerer.ctx.argumentsLocal = local; } if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !ts.isArrowFunction(node) && shapes.length === 1 && shapes[0]?.mode === "dynRest" && @@ -204,7 +204,7 @@ export function lowerLambda( // Fluent methods must return that receiver without a class cast. if ( ts.isMethodDeclaration(node) && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type && !node.asteriskToken && !hasExplicitJsDocReturn(node) && @@ -215,6 +215,19 @@ export function lowerLambda( ret: funcType.ret.kind === "promise" ? { ...funcType.ret, inner: DYN } : DYN, }; } + // An ASYNC lambda whose signature answered a bare checked-dynamic result + // (`retry(async () => loose())` against a library slot typed + // `(...) => A | Promise` with A = any — the union collapses to `any`): + // calling it still produces a promise, so the value's result is + // Promise, the payload the dyn the body returns. + if ( + funcType.ret.kind === "dyn" && + !ts.isAccessor(node) && + node.asteriskToken === undefined && + node.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true + ) { + funcType = { ...funcType, ret: { kind: "promise", inner: DYN } }; + } // A lambda IS a value: the completed-ABI rule applies at birth. The // contextual (target) type decides — `(x?: number) => void` may flow // into a slot annotated `(x: number | undefined) => void` (same ABI @@ -223,7 +236,7 @@ export function lowerLambda( if ( !( ts.isMethodDeclaration(node) && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && canBoxFuncIntoDyn( funcType, (id) => lowerer.shapes.get(id), @@ -274,14 +287,14 @@ export function lowerLambda( if (lowerer.methodLambdasInProgress.has(node)) lowerer.methodLambdasInProgress.set(node, { fnName, type: funcType, selfReads: 0 }); const inferredSignature = - isJsSourceFile(node.getSourceFile()) && !node.type + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type ? lowerer.checker.getSignatureFromDeclaration(node) : undefined; const returnsConstructor = inferredSignature && producesConstructor(lowerer, lowerer.checker.getReturnTypeOfSignature(inferredSignature)); if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type && !isAsync && !isGenerator && @@ -303,7 +316,7 @@ export function lowerLambda( try { const receiver = (checkedReceiver || - isJsSourceFile(node.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(node)) || node.parameters.nodes.some(isThisParameter)) && !ts.isArrowFunction(node) ? declareCallReceiver(lowerer, node, isGenerator) @@ -448,7 +461,7 @@ export function lowerLambda( // position. ICEs (SC9001) stay compile errors, exactly like // lowerStmts; probe mode (diagSink) keeps the poison. if (!(e instanceof PoisonError)) throw e; - if (!isJsSourceFile(node.getSourceFile())) throw e; + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) throw e; const params: IrParam[] = funcType.params.map((t, i) => ({ localId: `%pf${i}`, name: `%pf${i}`, @@ -497,7 +510,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I lowerer.fnStack.push(ctx); try { const receiver = - decl.parameters.nodes.some(isThisParameter) || isJsSourceFile(decl.getSourceFile()) + decl.parameters.nodes.some(isThisParameter) || isJsSourceFile(ts.getSourceFileOfNode(decl)) ? declareCallReceiver(lowerer, decl, sig.generator !== undefined) : null; const { params, prologue } = lowerer.declareParams(decl.parameters.nodes, sig.params); @@ -550,7 +563,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I // at the declaration's position — so a reachable-but-broken // signature stops the RUN at its own site instead of the build. // ICEs (SC9001) stay compile errors, exactly like lowerStmts. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { // An ABI type naming a class that never REGISTERED (the sentence- // walker idiom's path type — the #private fence) is fine to emit: // callers CAN lower calls to this symbol (a same-typed param diff --git a/packages/compiler/src/frontend/lowering/lower-generators.ts b/packages/compiler/src/frontend/lowering/lower-generators.ts index b1d0a1aa5..19a75a0b4 100644 --- a/packages/compiler/src/frontend/lowering/lower-generators.ts +++ b/packages/compiler/src/frontend/lowering/lower-generators.ts @@ -207,7 +207,7 @@ function isGeneratorMember(lowerer: Lowerer, access: ts.PropertyAccessExpression (d) => ts.isInterfaceDeclaration(d.parent) && (d.parent.name.text === "Generator" || d.parent.name.text === "AsyncGenerator") && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ); } @@ -272,7 +272,7 @@ export function isSettledIteratorValueRead( ts.isInterfaceDeclaration(d.parent) && (d.parent.name.text === "IteratorYieldResult" || d.parent.name.text === "IteratorReturnResult") && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return false; @@ -282,13 +282,13 @@ export function isSettledIteratorValueRead( sym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d)) && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ); const alias = receiver.getAliasSymbol(); const symbol = receiver.getSymbol(); if ( - !(alias?.name === "IteratorResult" && stdlib(alias)) && - !(symbol?.name === "IteratorReturnResult" && stdlib(symbol)) + !(alias !== undefined && ts.symbolName(alias) === "IteratorResult" && stdlib(alias)) && + !(symbol !== undefined && ts.symbolName(symbol) === "IteratorReturnResult" && stdlib(symbol)) ) return false; const checked = lowerer.mapTypeOf(lowerer.typeOf(node)); diff --git a/packages/compiler/src/frontend/lowering/lower-global-value.ts b/packages/compiler/src/frontend/lowering/lower-global-value.ts index d0b251e84..4b8e7b10a 100644 --- a/packages/compiler/src/frontend/lowering/lower-global-value.ts +++ b/packages/compiler/src/frontend/lowering/lower-global-value.ts @@ -14,7 +14,7 @@ export function lowerGlobalValue(lowerer: Lowerer, expr: ts.Expression): IrExpr // lib slice) but absent in Node 24: reads answer undefined. const names = lowerer.checker .getPropertiesOfType(lowerer.typeOf(expr)) - .map((property) => property.name) + .map((property) => ts.symbolName(property)) .filter((name) => name !== TEMPORAL_GLOBAL); return { kind: "libCall", diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 2ac6e779c..4be5d0b7e 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -1106,7 +1106,7 @@ function classInspectsThroughView(lowerer: Lowerer, info: ClassInfo | undefined) if (info.subclasses.length > 0) return true; return ( info.decl != null && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && classDynViewSupported( info.def.fields, (id) => lowerer.shapes.get(id), @@ -3273,7 +3273,7 @@ function lowerUtilCallableCall( * a statically typed callback parameter cannot receive a value its type * excludes. Untyped (JavaScript or unknown) slots keep the wrapper dyn. */ function callbackifyErrorExcludesNull(lowerer: Lowerer, expr: ts.CallExpression): boolean { - if (isJsSourceFile(expr.getSourceFile())) return false; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) return false; const checker = lowerer.checker; const slot = checker.getContextualType(expr) ?? lowerer.typeOf(expr); const wrapper = checker.getCallSignatures(slot)[0]; @@ -3379,7 +3379,7 @@ function lowerGetCallSitesCall( loc: SrcLoc, ): IrExpr | null { if (expr.arguments.nodes.length !== 0) return null; - if (!isJsSourceFile(expr.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; const frame = (): IrExpr => { const field = (name: string, value: IrExpr): { key: IrExpr; value: IrExpr } => ({ key: { kind: "strLit", value: name, type: STRING, loc }, @@ -3391,7 +3391,7 @@ function lowerGetCallSitesCall( field("functionName", { kind: "strLit", value: "", type: STRING, loc }), field("scriptName", { kind: "strLit", - value: expr.getSourceFile().fileName, + value: ts.getSourceFileOfNode(expr).fileName, type: STRING, loc, }), diff --git a/packages/compiler/src/frontend/lowering/lower-mixins.ts b/packages/compiler/src/frontend/lowering/lower-mixins.ts index 90789b4a0..766059dfb 100644 --- a/packages/compiler/src/frontend/lowering/lower-mixins.ts +++ b/packages/compiler/src/frontend/lowering/lower-mixins.ts @@ -174,7 +174,7 @@ function mixinFnShapeInner( if (n === t.expression) return undefined; if ( ts.isIdentifier(n) && - n.text === p.name.getText() && + n.text === ts.getTextOfNode(p.name) && lowerer.checker.getSymbolAtLocation(n) === paramSym ) { extraRef = true; @@ -451,8 +451,8 @@ function instantiateMixinCall( const base = mixinBaseClassOf(lowerer, call.arguments.nodes[0]!); const instName = lowerer.qualify( - call.getSourceFile(), - `%mx${call.getStart()}.${shape.classNode.name?.text ?? ""}`, + ts.getSourceFileOfNode(call), + `%mx${ts.tokenStartOfNode(call)}.${shape.classNode.name?.text ?? ""}`, ); const ordinal = lowerer.mixinOrdinals.get(shape.classNode) ?? 0; lowerer.mixinOrdinals.set(shape.classNode, ordinal + 1); @@ -513,7 +513,10 @@ function instantiateMixinCall( ); } if (info.mixinInstance) { - info.mixinInstance.statics = { sf: call.getSourceFile(), pos: holder.getStart() }; + info.mixinInstance.statics = { + sf: ts.getSourceFileOfNode(call), + pos: ts.tokenStartOfNode(holder), + }; } } return info; diff --git a/packages/compiler/src/frontend/lowering/lower-mock.ts b/packages/compiler/src/frontend/lowering/lower-mock.ts index be68abaca..fcc68c787 100644 --- a/packages/compiler/src/frontend/lowering/lower-mock.ts +++ b/packages/compiler/src/frontend/lowering/lower-mock.ts @@ -797,7 +797,7 @@ export function lowerMockMethod( const objShape = objType?.kind === "record" ? lowerer.shapes.get(objType.shapeId) : undefined; const fn = objShape?.fields.find((f) => f.name === field)?.type; if (!objType || !objShape || fn?.kind !== "func") { - const typeName = lowerer.typeOf(args[0]!).getSymbol()?.name; + const typeName = ts.symbolNameOf(lowerer.typeOf(args[0]!).getSymbol()); lowerer.noLowering( typeName && !typeName.startsWith("__") ? `${what} on '${typeName}'` diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 11db0ec8a..f0199d6a4 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -692,7 +692,10 @@ export function collectJsonImports(lowerer: Lowerer, parts: FileParts[]): void { const nameSym = lowerer.checker.getSymbolAtLocation(clause.name); if (!nameSym || !(nameSym.flags & ts.SymbolFlags.Alias)) continue; const target = lowerer.checker.getAliasedSymbol(nameSym); - const jsonSf = target.declarations[0]?.getSourceFile(); + const jsonSf = + target.declarations.length === 0 + ? undefined + : ts.getSourceFileOfNode(target.declarations[0]!); if (!jsonSf || !jsonSf.fileName.endsWith(".json")) continue; try { const tsType = lowerer.typeOf(clause.name); @@ -1175,7 +1178,7 @@ function jsDynHoldableInitializer(lowerer: Lowerer, init: ts.Expression | undefi if ( (ts.isFunctionExpression(e) || ts.isArrowFunction(e)) && !e.typeParameters?.nodes.length && - isJsSourceFile(e.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(e)) ) return true; if (ts.isIdentifier(e) || ts.isArrowFunction(e) || ts.isFunctionExpression(e)) { @@ -1274,7 +1277,7 @@ export function collectGlobals( const mutableVar = vd !== undefined && ts.isVariableDeclaration(vd) && - !vd.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(vd).isDeclarationFile && (ts.getCombinedNodeFlags(vd) & ts.NodeFlags.Const) === 0; // Immutable targets: alias plumbing — importers resolve to the // target's storage (the alias-resolved read IS the snapshot when @@ -1546,7 +1549,7 @@ export function collectGlobals( const mutableVar = vd !== undefined && ts.isVariableDeclaration(vd) && - !vd.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(vd).isDeclarationFile && (ts.getCombinedNodeFlags(vd) & ts.NodeFlags.Const) === 0; if (!mutableVar) continue; const diagsBefore = lowerer.diags.length; @@ -1557,7 +1560,7 @@ export function collectGlobals( } if (type.kind === "void") lowerer.badType(stmt.name, lowerer.typeOf(stmt.name)); const g: IrGlobal = { - id: `%g.${tag}${nsPathPrefix(stmt)}%alias.${stmt.name.text}%${stmt.getStart()}`, + id: `%g.${tag}${nsPathPrefix(stmt)}%alias.${stmt.name.text}%${ts.tokenStartOfNode(stmt)}`, name: stmt.name.text, type, mutable: false, @@ -2282,12 +2285,12 @@ export function collectGlobals( : undefined; const callDeclaration = callSignature && callSignature.declaration; const callType = - callDeclaration && isJsSourceFile(callDeclaration.getSourceFile()) + callDeclaration && isJsSourceFile(ts.getSourceFileOfNode(callDeclaration)) ? lowerer.mapTypeOf(lowerer.typeOf(nameNode)) : null; const inferredJsCall = callDeclaration && - isJsSourceFile(callDeclaration.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(callDeclaration)) && (!callType || (unannotatedCall && callType.kind === "union" && @@ -2710,7 +2713,7 @@ export function declSymbolOf( ts.canHaveModifiers(decl) && ts.getModifiers(decl)?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) === true; if (!isDefault) return undefined; - const sym = defaultExportSymbolOf(lowerer, decl.getSourceFile()); + const sym = defaultExportSymbolOf(lowerer, ts.getSourceFileOfNode(decl)); return sym ?? undefined; } @@ -2728,7 +2731,7 @@ export function declSymbolOf( * expression so the expression's OWN diagnostic wins when it has one, * then poisons. */ export function lowerDefaultExport(lowerer: Lowerer, stmt: ts.ExportAssignment): IrStmt | null { - const symbol = defaultExportSymbolOf(lowerer, stmt.getSourceFile()); + const symbol = defaultExportSymbolOf(lowerer, ts.getSourceFileOfNode(stmt)); if (symbol && symbol.flags & ts.SymbolFlags.Alias && !lowerer.globalsBySymbol.has(symbol)) return null; const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; @@ -2741,7 +2744,7 @@ export function lowerDefaultExport(lowerer: Lowerer, stmt: ts.ExportAssignment): while (ts.isParenthesizedExpression(inner)) inner = inner.expression; if ( ts.isClassExpression(inner) && - !isJsSourceFile(stmt.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(stmt)) && inner.typeParameters === undefined && (decoratorNodesOf(inner).length > 0 || inner.members.nodes.some((m) => decoratorNodesOf(m).length > 0)) && @@ -2961,7 +2964,7 @@ export function lowerFileInit( c.decl && ts.isClassDeclaration(c.decl) && !c.mixinInstance && - c.decl.getSourceFile() === sf && + ts.getSourceFileOfNode(c.decl) === sf && (lowerer.classInitFlags.has(c.def.name) || c.callableBase !== undefined || c.def.baseValueGlobal !== undefined || @@ -2970,7 +2973,7 @@ export function lowerFileInit( (c.staticBlocks?.length ?? 0) > 0 || c.classDecorators !== undefined), ) - .map((c) => ({ pos: c.decl!.getStart(), info: c })); + .map((c) => ({ pos: ts.tokenStartOfNode(c.decl!), info: c })); // Statics-bearing MIXIN instantiations whose call evaluates in THIS // file: their declaration-time code runs when the call does — the // top-level statement lower-mixins recorded. Insertion order is @@ -2990,7 +2993,7 @@ export function lowerFileInit( // anything the statement's lowering emits. Hoisted class // declarations never share a kept statement's start, so `<=` // changes nothing for them. - while (at < statics.length && statics[at]!.pos <= stmt.getStart()) { + while (at < statics.length && statics[at]!.pos <= ts.tokenStartOfNode(stmt)) { body.push( ...withClassInitFlag( lowerer, diff --git a/packages/compiler/src/frontend/lowering/lower-namespaces.ts b/packages/compiler/src/frontend/lowering/lower-namespaces.ts index 79ac7c278..9997eb7d9 100644 --- a/packages/compiler/src/frontend/lowering/lower-namespaces.ts +++ b/packages/compiler/src/frontend/lowering/lower-namespaces.ts @@ -178,10 +178,10 @@ export function nsPathPrefix(node: ts.Node, modifierDecl?: ts.Node): string { const nested = p.parent !== undefined && ts.isModuleBlock(p.parent); const nonExported = nested && (ts.getCombinedModifierFlags(p) & ts.ModifierFlags.Export) === 0; - segs.unshift(p.name.text + (nonExported ? `%${p.getStart()}` : "")); + segs.unshift(p.name.text + (nonExported ? `%${ts.tokenStartOfNode(p)}` : "")); } } - return `${segs.join("%")}${exported ? "" : `%${parent.getStart()}`}.`; + return `${segs.join("%")}${exported ? "" : `%${ts.tokenStartOfNode(parent)}`}.`; } /** The registered namespace-block kind a symbol's declaration sits in @@ -275,7 +275,11 @@ function moduleNsSourceFileOf(lowerer: Lowerer, e: ts.Expression): ts.SourceFile stmt.moduleSpecifier !== undefined && ts.isStringLiteral(stmt.moduleSpecifier) ) { - const dep = resolveImport(lowerer.program, d.getSourceFile(), stmt.moduleSpecifier.text); + const dep = resolveImport( + lowerer.program, + ts.getSourceFileOfNode(d), + stmt.moduleSpecifier.text, + ); if (dep && !dep.isDeclarationFile && lowerer.fileTag.has(dep)) return dep; } } @@ -334,7 +338,7 @@ export function ambientNsRootOf(lowerer: Lowerer, e: ts.Expression): ts.Identifi return null; } if (ts.isModuleDeclaration(d) && !ts.isIdentifier(d.name)) return null; - if (d.getSourceFile().isDeclarationFile) return null; // lib/@types keep their own fences + if (ts.getSourceFileOfNode(d).isDeclarationFile) return null; // lib/@types keep their own fences if ( ts.isModuleDeclaration(d) ? !isAmbientModuleDecl(d) @@ -432,7 +436,7 @@ export function ambientUndefVarRootOf(lowerer: Lowerer, e: ts.Expression): ts.Id if (decls.length === 0) return null; for (const d of decls) { if (!ts.isVariableDeclaration(d) || d.initializer !== undefined) return null; - if (d.getSourceFile().isDeclarationFile) return null; // lib/@types keep their own fences + if (ts.getSourceFileOfNode(d).isDeclarationFile) return null; // lib/@types keep their own fences if (!(ts.getCombinedModifierFlags(d) & ts.ModifierFlags.Ambient)) return null; } return root; @@ -491,7 +495,7 @@ export function trapDeclRootOf( if (root === null) return null; for (const nameNode of boundIdentifiersOf(decl.name)) { const sym = lowerer.checker.getSymbolAtLocation(nameNode); - if (sym && bindingEverWritten(lowerer, sym, decl.getSourceFile())) { + if (sym && bindingEverWritten(lowerer, sym, ts.getSourceFileOfNode(decl))) { const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(sym)); if (mapped !== null && mapped.kind !== "void") return null; } @@ -516,7 +520,7 @@ export function trapDeclRootOf( * initializer into an ordinary lowering that fences). ambientUndefVarRootOf * carries its own collect-guard, so the extra question stays a probe. */ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): boolean { - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.checker.getSymbolAtLocation(e) === sym; const patternWrites = (target: ts.Node): boolean => { @@ -525,9 +529,9 @@ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile) const walk = (m: ts.Node): void => { if (hit) return; if (namesSym(m)) hit = true; - else m.forEachChild(walk); + else ts.forEachChild(m, walk); }; - target.forEachChild(walk); + ts.forEachChild(target, walk); return hit; }; let written = false; @@ -572,9 +576,9 @@ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile) written = true; } } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - sf.forEachChild(visit); + ts.forEachChild(sf, visit); return written; } @@ -635,7 +639,7 @@ export function ambientUndefinedFnSymbolOf( } else if (!ts.isModuleDeclaration(d) && !ts.isClassDeclaration(d)) { return null; } - if (d.getSourceFile().isDeclarationFile) return null; + if (ts.getSourceFileOfNode(d).isDeclarationFile) return null; if (!(ts.getCombinedModifierFlags(d) & ts.ModifierFlags.Ambient)) return null; } return sawFn ? sym : null; @@ -676,7 +680,7 @@ function initExecutingPosition(node: ts.Node): boolean { function earliestSameFileDeclStart(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): number { let earliest = Infinity; for (const d of sym.declarations) { - if (d.getSourceFile() === sf) earliest = Math.min(earliest, d.getStart()); + if (ts.getSourceFileOfNode(d) === sf) earliest = Math.min(earliest, ts.tokenStartOfNode(d)); } return earliest; } @@ -691,10 +695,10 @@ export function fenceEarlyAliasUse(lowerer: Lowerer, ident: ts.Identifier, refNo const aliasDecl = sym.declarations.find(ts.isImportEqualsDeclaration); if (!aliasDecl || ts.isExternalModuleReference(aliasDecl.moduleReference)) return; if (!initExecutingPosition(refNode)) return; - const sf = refNode.getSourceFile(); - if (aliasDecl.getSourceFile() !== sf) return; // cross-file: module init order applies - const aliasStart = aliasDecl.getStart(); - if (refNode.getStart() < aliasStart) { + const sf = ts.getSourceFileOfNode(refNode); + if (ts.getSourceFileOfNode(aliasDecl) !== sf) return; // cross-file: module init order applies + const aliasStart = ts.tokenStartOfNode(aliasDecl); + if (ts.tokenStartOfNode(refNode) < aliasStart) { lowerer.unsupported( "SC1090", refNode, @@ -730,8 +734,8 @@ export function fenceEarlyNsMemberRef( memberSym: ts.Symbol, ): void { if (!initExecutingPosition(access)) return; - const sf = access.getSourceFile(); - const refStart = access.getStart(); + const sf = ts.getSourceFileOfNode(access); + const refStart = ts.tokenStartOfNode(access); const memberStart = earliestSameFileDeclStart(lowerer, memberSym, sf); if (memberStart !== Infinity && refStart < memberStart) { lowerer.unsupported( diff --git a/packages/compiler/src/frontend/lowering/lower-node-module.ts b/packages/compiler/src/frontend/lowering/lower-node-module.ts index fee25b10f..37675777c 100644 --- a/packages/compiler/src/frontend/lowering/lower-node-module.ts +++ b/packages/compiler/src/frontend/lowering/lower-node-module.ts @@ -45,7 +45,7 @@ function cjsGlobal(lowerer: Lowerer, node: ts.Expression, name: "module" | "requ const decls = symbol ? symbol.declarations : []; const sourceShadow = decls.some( (decl) => - !decl.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(decl).isDeclarationFile && (ts.isVariableDeclaration(decl) || ts.isParameter(decl) || ts.isBindingElement(decl) || @@ -55,7 +55,7 @@ function cjsGlobal(lowerer: Lowerer, node: ts.Expression, name: "module" | "requ ts.isNamespaceImport(decl) || ts.isImportEqualsDeclaration(decl)), ); - return !sourceShadow && !isNodeEsmFile(node.getSourceFile(), lowerer.program); + return !sourceShadow && !isNodeEsmFile(ts.getSourceFileOfNode(node), lowerer.program); } function graphSyntax(lowerer: Lowerer, sf: ts.SourceFile): boolean { @@ -174,7 +174,7 @@ export function cjsModuleRegistryPrelude(lowerer: Lowerer, loc: IrExpr["loc"]): } export function cjsModuleRef(lowerer: Lowerer, node: ts.Node): IrExpr | null { - const id = lowerer.cjsModuleIdOf.get(node.getSourceFile()); + const id = lowerer.cjsModuleIdOf.get(ts.getSourceFileOfNode(node)); return id === undefined ? null : numLit(id, locOf(node)); } @@ -203,8 +203,8 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { const symbol = type.getSymbol(); if ( symbol && - (symbol.name === "Module" || symbol.name === "ScriptcModule") && - symbol.declarations.some((decl) => lowerer.isStdlibFile(decl.getSourceFile())) + (ts.symbolName(symbol) === "Module" || ts.symbolName(symbol) === "ScriptcModule") && + symbol.declarations.some((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) ) { return true; } @@ -217,7 +217,7 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { (ts.isInterfaceDeclaration(parent) || ts.isClassDeclaration(parent)) && parent.name && (parent.name.text === "ScriptcModule" || parent.name.text === "Module") && - lowerer.isStdlibFile(parent.getSourceFile()) + lowerer.isStdlibFile(ts.getSourceFileOfNode(parent)) ) { return true; } @@ -237,7 +237,7 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { * file assigns an object or function. */ function moduleExportsAlwaysTruthy(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean { if (!truthinessOnly(expr)) return false; - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); if (isNodeEsmFile(sf, lowerer.program)) return false; let truthy = true; ts.walkPreorder(sf, (node) => { @@ -307,7 +307,7 @@ export function isCjsTopLevelThis(lowerer: Lowerer, node: ts.Node): boolean { ) return false; } - return isCjsJsFile(node.getSourceFile(), lowerer.program); + return isCjsJsFile(ts.getSourceFileOfNode(node), lowerer.program); } export function usesCjsTopLevelThis(lowerer: Lowerer, sf: ts.SourceFile): boolean { @@ -341,7 +341,7 @@ export function usesCjsTopLevelThis(lowerer: Lowerer, sf: ts.SourceFile): boolea * behave as in Node. */ export function lowerCjsTopLevelThis(lowerer: Lowerer, expr: ts.Expression): IrExpr | null { if (!isCjsTopLevelThis(lowerer, expr)) return null; - const global = lowerer.cjsExportsThis.get(expr.getSourceFile()); + const global = lowerer.cjsExportsThis.get(ts.getSourceFileOfNode(expr)); if (!global) return null; let receiver: ts.Node = expr; while (ts.isParenthesizedExpression(receiver.parent)) receiver = receiver.parent; @@ -433,7 +433,7 @@ export function lowerNodeModuleIdentifier(lowerer: Lowerer, expr: ts.Identifier) if (!isCjsModuleGlobal(lowerer, expr)) return null; const ref = cjsModuleRef(lowerer, expr); if (ref) return ref; - if (isCjsJsFile(expr.getSourceFile(), lowerer.program)) { + if (isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program)) { lowerer.unsupported( "SC1090", expr, diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index b4e64c9d0..afae7dd7b 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -719,7 +719,7 @@ export function lowerNetModuleCall( // ERR_INVALID_ARG_TYPE on 'options' before any server exists. DYN // result — the checked-dynamic lane's concise arrows box it, and // the throw means it never materializes. - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const t = lowerer.mapTypeOf(lowerer.typeOf(args[0]!)); if (t !== null && (t.kind === "string" || t.kind === "f64" || t.kind === "bool")) { const raw = lowerer.lowerExpr(args[0]!); @@ -770,7 +770,7 @@ export function lowerNetModuleCall( // invalid-input probes build theirs with computed keys): the // checked-dynamic walk validates Node-order and the compiler-rendered // fence is the post-validation tail. - if (args.length === 1 && isJsSourceFile(expr.getSourceFile())) { + if (args.length === 1 && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const t = lowerer.mapTypeOf(lowerer.typeOf(args[0]!)); if (t !== null && (t.kind === "dyn" || t.kind === "record")) { const raw = lowerer.lowerExpr(args[0]!); @@ -885,7 +885,7 @@ function lowerNetConnectOptions( ): IrExpr { const args = expr.arguments.nodes; const optsNode = args[0] as ts.ObjectLiteralExpression; - const isJs = isJsSourceFile(expr.getSourceFile()); + const isJs = isJsSourceFile(ts.getSourceFileOfNode(expr)); // The checked-dynamic option-bag route (JS sources): a literal with // computed keys (the invalid-input probes' spelling) or an objectMode- // trio member rides WHOLE to the runtime walk — Node's Socket-ctor @@ -1524,7 +1524,7 @@ function lowerNetServerMethodCall( } else if ( key === "signal" && ts.isPropertyAssignment(prop) && - isJsSourceFile(call.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(call)) ) { // A provably-non-AbortSignal signal (the invalid-input probes: // strings, numbers, plain records) throws Node's @@ -2334,7 +2334,7 @@ function lowerNetSocketMethodCall( if ( encT.value === "buffer" && chunkT.kind === "string" && - isJsSourceFile(call.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(call)) ) { lowerer.lowerExpr(args[0]!); // evaluation order (effect-free in practice) return nodeThrowExpr(1, "ERR_UNKNOWN_ENCODING", "Unknown encoding: buffer", VOID, loc); @@ -4851,7 +4851,7 @@ function tlsLiteralNeedsRuntimeWalk( node: ts.ObjectLiteralExpression, includeHttpTimeoutOptions = false, ): boolean { - const jsSource = isJsSourceFile(node.getSourceFile()); + const jsSource = isJsSourceFile(ts.getSourceFileOfNode(node)); return node.properties.nodes.some( (p) => (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) && @@ -5071,7 +5071,7 @@ function lowerTlsModuleCall( bi.member === "getCACertificates" && args.length === 1 && !args.some(ts.isSpreadElement) && - isJsSourceFile(expr.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(expr)) ) { // The type-argument ladder (validateString + the documented name // set); the real CA list has no lowering, so a valid name meets the @@ -5737,7 +5737,7 @@ function lowerHttp2ModuleCall( if (ts.isConditionalExpression(inner)) { const whenTrue = stripParensAndCasts(inner.whenTrue); const whenFalse = stripParensAndCasts(inner.whenFalse); - const condText = inner.condition.getText(); + const condText = ts.getTextOfNode(inner.condition); const ok = ts.isObjectLiteralExpression(whenTrue) && ts.isObjectLiteralExpression(whenFalse) && @@ -5748,7 +5748,7 @@ function lowerHttp2ModuleCall( ts.isStringLiteral(whenTrue.properties.nodes[0]!.name)) && (whenTrue.properties.nodes[0]!.name as ts.Identifier | ts.StringLiteral).text === "SNICallback" && - whenTrue.properties.nodes[0]!.initializer.getText() === condText; + ts.getTextOfNode(whenTrue.properties.nodes[0]!.initializer) === condText; if (!ok) { lowerer.noLowering( "createSecureServer options with a conditional spread", @@ -7533,7 +7533,7 @@ function lowerHttpResMethodCall( } if (name === "writeContinue" || name === "writeProcessing" || name === "writeEarlyHints") { requireStatementPosition(lowerer, call, `res.${name}(...)`); - if (lowerer.typeOf(access.expression).getSymbol()?.name === "Http2ServerResponse") { + if (ts.symbolNameOf(lowerer.typeOf(access.expression).getSymbol()) === "Http2ServerResponse") { lowerer.noLowering( `Http2ServerResponse.${name}`, call, @@ -7642,7 +7642,7 @@ function lowerHttpResMethodCall( } if (name === "addTrailers") { requireStatementPosition(lowerer, call, "res.addTrailers(...)"); - if (lowerer.typeOf(access.expression).getSymbol()?.name === "Http2ServerResponse") { + if (ts.symbolNameOf(lowerer.typeOf(access.expression).getSymbol()) === "Http2ServerResponse") { lowerer.noLowering( "Http2ServerResponse.addTrailers", call, diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index 78d690c6b..bc8955a81 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -667,7 +667,7 @@ export function lowerStaticResponseCall(lowerer: Lowerer, call: ts.CallExpressio } const recvType = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; const recv = lowerer.lowerExpr(access.expression); if (recv.type.kind !== "dyn") return null; return lowerStaticFixedFetchMethodCall(lowerer, call, access, member, recv, [], (receiver) => @@ -753,7 +753,7 @@ function lowerStaticFixedFetchMethodCall( loc, }, then: answer, - else_: nodeThrowExpr(1, "", `${access.getText()} is not a function`, answer.type, loc), + else_: nodeThrowExpr(1, "", `${ts.getTextOfNode(access)} is not a function`, answer.type, loc), type: answer.type, loc, }; @@ -927,7 +927,7 @@ export function fenceStaticResponseMember( ): IrExpr | null { const recvType = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; const members = fetchAccessMemberNames(lowerer, access); if (members === null) return null; const status = (member: string) => @@ -969,7 +969,7 @@ export function fenceStaticHeadersMember( ): IrExpr | null { const recvType = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Headers" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Headers" || !lowerer.isStdlibSymbol(sym)) return null; const members = fetchAccessMemberNames(lowerer, access); if (members === null) return null; const status = (member: string) => @@ -1026,7 +1026,8 @@ function isStdlibFetchInterface(lowerer: Lowerer, node: ts.Node, owner: string): for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) { // NodeJS.ReadableStream shares the WHATWG interface's name. if ( - symbol?.name === owner && + symbol !== undefined && + ts.symbolName(symbol) === owner && lowerer.isStdlibSymbol(symbol) && !symbol.declarations.some( (d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"), @@ -1093,10 +1094,10 @@ function fetchInterfaceInventoryOwner( const symbol = [type.getSymbol(), type.getAliasSymbol()].find( (candidate) => candidate !== undefined && - NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(candidate.name) && + NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(ts.symbolName(candidate)) && lowerer.isStdlibSymbol(candidate), ); - return symbol ? { owner: symbol.name, symbol } : null; + return symbol ? { owner: ts.symbolName(symbol), symbol } : null; } function fenceFetchObjectMembers( @@ -1308,7 +1309,7 @@ export function lowerFetchElementMethodCall( kind: "dynInvoke", recv: receiverRef, method: member, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1321,7 +1322,7 @@ export function lowerFetchElementMethodCall( let result: IrExpr = nodeThrowExpr( 1, "", - `${access.getText()} is not a function`, + `${ts.getTextOfNode(access)} is not a function`, invocationType, loc, ); @@ -1627,9 +1628,10 @@ export function lowerStaticAbortControllerCall( const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( - symbol?.name !== "AbortController" || + symbol === undefined || + ts.symbolName(symbol) !== "AbortController" || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -1652,7 +1654,7 @@ export function lowerStaticAbortControllerCall( kind: "dynInvoke", recv: receiverRef, method: "abort", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1681,9 +1683,10 @@ export function lowerStaticReadableStreamControllerCall( const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( - symbol?.name !== "ReadableStreamDefaultController" || + symbol === undefined || + ts.symbolName(symbol) !== "ReadableStreamDefaultController" || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -1707,7 +1710,7 @@ export function lowerStaticReadableStreamControllerCall( kind: "dynInvoke", recv: receiver, method: member, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1756,7 +1759,7 @@ export function lowerStaticReadableStreamCancelCall( kind: "dynInvoke", recv: receiverRef, method: "cancel", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1789,9 +1792,10 @@ export function lowerStaticAbortSignalListenerCall( const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( - (symbol?.name !== "AbortSignal" && symbol?.name !== "EventTarget") || + ((symbol === undefined || ts.symbolName(symbol) !== "AbortSignal") && + (symbol === undefined || ts.symbolName(symbol) !== "EventTarget")) || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -1814,7 +1818,7 @@ export function lowerStaticAbortSignalListenerCall( kind: "dynInvoke", recv: receiver, method: member, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -2146,9 +2150,10 @@ export function lowerStaticReadableStreamReaderCall( const receiverTs = lowerer.typeOf(access.expression); const symbol = receiverTs.getSymbol(); if ( - symbol?.name !== "ReadableStreamDefaultReader" || + symbol === undefined || + ts.symbolName(symbol) !== "ReadableStreamDefaultReader" || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -2189,7 +2194,7 @@ export function lowerDynamicImportCall(lowerer: Lowerer, call: ts.CallExpression if (call.arguments.nodes.length !== 1) { lowerer.unsupported("SC1090", call, "dynamic import() with import attributes"); } - const res = lowerer.dynImports.get(`${call.getSourceFile().fileName}\u0000${arg.text}`); + const res = lowerer.dynImports.get(`${ts.getSourceFileOfNode(call).fileName}\u0000${arg.text}`); if (res === undefined) lowerer.refuseUnsupportedApi("'import()'", call); if (res.kind === "program-module") { return lowerOwnStaticModuleImport(lowerer, call, arg); @@ -2569,6 +2574,6 @@ export function npmMemberFence(lowerer: Lowerer, access: ts.PropertyAccessExpres export function npmPackageOfSymbol(lowerer: Lowerer, sym: ts.Symbol | undefined): string | null { const decls = sym ? sym.declarations : undefined; if (!decls || decls.length === 0) return null; - if (!decls.every((d) => lowerer.isNpmFile(d.getSourceFile()))) return null; - return npmPackageNameOf(decls[0]!.getSourceFile().fileName); + if (!decls.every((d) => lowerer.isNpmFile(ts.getSourceFileOfNode(d)))) return null; + return npmPackageNameOf(ts.getSourceFileOfNode(decls[0]!).fileName); } diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 80470448a..69f6cc99b 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -256,8 +256,8 @@ function npmStaticReturnedEmptyArrayType( decl.initializer === undefined || !ts.isArrayLiteralExpression(decl.initializer) || decl.initializer.elements.nodes.length !== 0 || - !isJsSourceFile(decl.getSourceFile()) || - npmStaticPackageOfPath(decl.getSourceFile().fileName) === null || + !isJsSourceFile(ts.getSourceFileOfNode(decl)) || + npmStaticPackageOfPath(ts.getSourceFileOfNode(decl).fileName) === null || lowerer.ctx.returnType.kind !== "array" ) { return null; @@ -296,8 +296,8 @@ function npmStaticOptionalSelfWriteType( ): (IrType & { kind: "union" }) | null { if ( !ts.isIdentifier(decl.name) || - !isJsSourceFile(decl.getSourceFile()) || - npmStaticPackageOfPath(decl.getSourceFile().fileName) === null || + !isJsSourceFile(ts.getSourceFileOfNode(decl)) || + npmStaticPackageOfPath(ts.getSourceFileOfNode(decl).fileName) === null || initial.kind === "union" ) { return null; @@ -359,7 +359,9 @@ function stdlibTypeNamed(lowerer: Lowerer, node: ts.Node, name: string): boolean if (t.flags & ts.TypeFlags.Union) return constituentTypes(t).some(visit); const sym = t.getAliasSymbol() ?? t.getSymbol(); return ( - sym?.name === name && sym.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile())) + sym !== undefined && + ts.symbolName(sym) === name && + sym.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) ); }; return visit(lowerer.typeOf(node)); @@ -613,7 +615,7 @@ function pureAnnotatedDeadConst(stmt: ts.Statement, sf: ts.SourceFile): boolean return decls.every((d) => { let init = d.initializer; if (init === undefined) return false; - const trivia = sf.text.slice(init.pos, init.getStart(sf)); + const trivia = sf.text.slice(init.pos, ts.tokenStartOfNode(init)); if (!trivia.includes("@__PURE__") && !trivia.includes("#__PURE__")) return false; // `as` casts are type-world wrappers (`(() => {...})() as unknown as // { new (): any }` — cookie's NullObject shape); the runtime @@ -640,7 +642,7 @@ function pureAnnotatedDeadConst(stmt: ts.Statement, sf: ts.SourceFile): boolean * probes that would otherwise claim (and diagnose) the call-initializer * shape. */ export function provenanceElidedConstDecl(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean { - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (!isProvenanceSourceFile(sf.fileName)) return false; const stmt = decl.parent?.parent; if (!ts.isVariableStatement(stmt) || !pureAnnotatedDeadConst(stmt, sf)) return false; @@ -683,7 +685,7 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir // runtimeFence — a catchable, SC-coded throw at the statement's // position — instead of failing the whole build). TypeScript keeps // compile fences. ICEs (SC9001) always stay compile errors. - const sf = stmts[0]?.getSourceFile(); + const sf = stmts.length === 0 ? undefined : ts.getSourceFileOfNode(stmts[0]!); const deferFences = sf !== undefined && isJsSourceFile(sf); // Register this list as open for the forward-capture machinery: a // nested function lowering mid-list may pre-declare a LATER const of @@ -959,7 +961,7 @@ export function predeclareForwardCapture(lowerer: Lowerer, symbol: ts.Symbol): b return false; let type = lowerer.irTypeOf(decl.name); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && (ts.isObjectLiteralExpression(decl.initializer) || @@ -1107,7 +1109,7 @@ function jsBindingKeepsCheckedObject( nameNode: ts.Identifier, type: IrType, ): boolean { - if (!isJsSourceFile(nameNode.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(nameNode))) return false; const host = hostVariableDeclarationOf(nameNode); if (!host || host.type || hasJsTypeAnnotation(host)) return false; const arms = type.kind === "union" ? (lowerer.unions.get(type.unionId)?.arms ?? []) : [type]; @@ -1122,8 +1124,9 @@ function jsBindingKeepsCheckedObject( function mentionsSymbol(lowerer: Lowerer, node: ts.Node, symbol: ts.Symbol): boolean { if (ts.isIdentifier(node)) return lowerer.checker.getSymbolAtLocation(node) === symbol; return ( - node.forEachChild((child) => (mentionsSymbol(lowerer, child, symbol) ? true : undefined)) === - true + ts.forEachChild(node, (child) => + mentionsSymbol(lowerer, child, symbol) ? true : undefined, + ) === true ); } @@ -1169,7 +1172,8 @@ function lowerJsVarFromExisting( symbol: ts.Symbol, ): IrStmt | null { if (!ts.isIdentifier(decl.name) || !decl.initializer) return null; - if (!isJsSourceFile(decl.getSourceFile()) || lowerer.ctx.hoistedVars.has(symbol)) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl)) || lowerer.ctx.hoistedVars.has(symbol)) + return null; if (lowerer.bindingIn(lowerer.ctx, symbol)) return null; let node: ts.Expression = decl.initializer; while (ts.isParenthesizedExpression(node) || ts.isSatisfiesExpression(node)) @@ -1212,13 +1216,13 @@ function varBindingType(lowerer: Lowerer, nameNode: ts.Identifier): IrType | nul ) return DYN; if ( - isJsSourceFile(nameNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(nameNode)) && ts.isVariableDeclaration(nameNode.parent) && isNativeBuiltinValueInitializer(lowerer, nameNode.parent.initializer) ) return DYN; let type = lowerer.mapTypeOf(lowerer.typeOf(nameNode)); - if (type?.kind === "record" && isJsSourceFile(nameNode.getSourceFile())) { + if (type?.kind === "record" && isJsSourceFile(ts.getSourceFileOfNode(nameNode))) { const shape = lowerer.shapes.get(type.shapeId); if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple) type = DYN; } @@ -1821,7 +1825,7 @@ function lowerVarDeclarator( requireSpecOf(decl.initializer) !== null && !isNativeFfiRequire(lowerer, decl.initializer) && !isJsonRequireDecl(lowerer, decl) && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) { const spec = requireSpecOf(decl.initializer)!; if (lowerer.externalTypes.has(spec)) lowerer.externalHostFence(spec, decl, false); @@ -2070,7 +2074,7 @@ function lowerMatchAllDrainDecl( export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): boolean { const widened = lowerer.checker.getBaseTypeOfLiteralType(type); const declarationBelongs = (d: ts.Node): boolean => { - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; let inParseArgsType = false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if ( @@ -2086,7 +2090,11 @@ export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): bool return false; }; const sym = widened.getAliasSymbol() ?? widened.getSymbol(); - if (sym && isParseArgsDynTypeName(sym.name) && sym.declarations.some(declarationBelongs)) { + if ( + sym && + isParseArgsDynTypeName(ts.symbolName(sym)) && + sym.declarations.some(declarationBelongs) + ) { return true; } // A discriminant check erases the token alias and leaves one anonymous @@ -2097,43 +2105,6 @@ export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): bool .some((p) => p.declarations.some(declarationBelongs)); } -/** True when an `any` value comes from a JavaScript function's result: - * a call (possibly awaited) whose callee is declared in a JavaScript file, - * or a const bound to one. A TypeScript program importing a JavaScript - * package sees the package's inferred types (`jwtVerify` from jose - * answers `any`), and its object destructuring reads the members of that - * checked-dynamic value as JavaScript does — member reads on the same - * value already compile. Other `any` sources keep the fence. */ -function javaScriptAnyOrigin(lowerer: Lowerer, node: ts.Expression, depth: number): boolean { - let expr = node; - while (ts.isParenthesizedExpression(expr) || ts.isAwaitExpression(expr)) expr = expr.expression; - if (ts.isCallExpression(expr)) { - const callee = ts.isPropertyAccessExpression(expr.expression) - ? expr.expression.name - : expr.expression; - if (!ts.isIdentifier(callee)) return false; - const symbol = lowerer.resolveValueSymbol(callee); - const declarations = symbol ? symbol.declarations : []; - return ( - declarations.length > 0 && - declarations.every((d) => isJsSourceFile(d.getSourceFile()) && ts.isFunctionLike(d)) - ); - } - if (ts.isIdentifier(expr) && depth === 0) { - const symbol = lowerer.resolveValueSymbol(expr); - const declaration = symbol ? symbol.valueDeclaration : undefined; - return ( - declaration !== undefined && - ts.isVariableDeclaration(declaration) && - ts.isIdentifier(declaration.name) && - declaration.initializer !== undefined && - (ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const) !== 0 && - javaScriptAnyOrigin(lowerer, declaration.initializer, depth + 1) - ); - } - return false; -} - /** Destructuring declarations, desugared as sugar over indexed/field * reads: the initializer evaluates ONCE into a hidden temp, then each * bound name declares (or assigns its pre-registered module global) from @@ -2172,18 +2143,18 @@ export function lowerDestructuringDecl( const parseArgsDynObject = init.type.kind === "dyn" && isParseArgsDynCheckerType(lowerer, lowerer.typeOf(decl.initializer)); - // An `any`-origin source needs a represented record or array shape for - // destructuring. JS files retain their per-site runtime fences. + // An `any`-origin source destructures through a represented record or + // array shape, or through the checked-dynamic value it lowered to: the + // pattern reads members (or iterates) the dyn value exactly as a + // JavaScript file's pattern does, with the same per-site runtime fences + // (null/undefined sources throw Node's TypeError). Primitive and unit + // sources keep their own fences below; other carriers keep this one. if ( lowerer.anyOrigin(decl.initializer) && - !isJsSourceFile(decl.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && init.type.kind !== "record" && init.type.kind !== "array" && - !( - init.type.kind === "dyn" && - ts.isObjectBindingPattern(decl.name) && - javaScriptAnyOrigin(lowerer, decl.initializer, 0) - ) + init.type.kind !== "dyn" ) { lowerer.anyOpFence("destructuring", decl); } @@ -2196,7 +2167,7 @@ export function lowerDestructuringDecl( // the checker's own type instead of the carrier. if ( init.type.kind === "string" && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !checkerStringSource(lowerer, decl.initializer) ) { lowerer.unsupported( @@ -2214,7 +2185,7 @@ export function lowerDestructuringDecl( init.type.kind === "bool" || init.type.kind === "string" || isUnitType(init.type)) && - !isJsSourceFile(decl.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && !( init.type.kind === "string" && staticStringPattern(lowerer, decl.name as ts.ArrayBindingPattern | ts.ObjectBindingPattern) @@ -2238,7 +2209,7 @@ export function lowerDestructuringDecl( // (the message is the fence's — a unit value here is the failure // path either way). TypeScript keeps the fence: narrowing first is // the annotated fix. - if (isJsSourceFile(decl.getSourceFile()) && init.type.kind === "union") { + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && init.type.kind === "union") { const def = lowerer.unions.get(init.type.unionId); const dataArms = (def?.arms ?? []).filter((a) => !isUnitType(a)); const arm = dataArms[0]; @@ -2288,6 +2259,50 @@ export function lowerDestructuringDecl( if (groupsSource !== null) init = groupsSource; const tmp = lowerer.declareHiddenLocal("%destr", init.type); out.push({ kind: "varDecl", localId: tmp.id, init, loc }); + // A TypeScript `any` source destructured through its checked-dynamic + // value: RequireObjectCoercible with V8's own TypeError ("Cannot + // destructure property 'a' of 'f(...)' as it is null.") for the source + // spellings the assignment form reproduces; other sources are refused, + // as in the assignment form. JavaScript files keep their existing + // per-read errors. + if ( + init.type.kind === "dyn" && + ts.isObjectBindingPattern(decl.name) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && + lowerer.anyOrigin(decl.initializer) + ) { + const spelling = destrSpellingOf(decl.initializer); + if (spelling === null) { + lowerer.unsupported( + "SC1031", + decl.initializer, + "destructuring declaration from an `any` source whose expression spelling is unsupported (use a variable, ordinary property chain, or ordinary call)", + ); + } + const first = decl.name.elements.nodes[0]; + const firstKey = + first === undefined || first.dotDotDotToken !== undefined + ? undefined + : (first.propertyName ?? first.name); + // V8 names the first property when its key is an identifier, a string + // or a number; a computed first key takes the bare (source-only) form. + const firstProp = + firstKey === undefined || ts.isComputedPropertyName(firstKey) + ? undefined + : (patternKeyNameOf(lowerer, firstKey as ts.PropertyName) ?? undefined); + out.push({ + kind: "exprStmt", + expr: { + kind: "dynDestrCheck", + value: { kind: "varRef", localId: tmp.id, type: init.type, loc }, + spelling, + ...(firstProp !== undefined ? { firstProp } : {}), + type: init.type, + loc, + }, + loc, + }); + } // V8's destructuring TypeError spells STATIC sources: an identifier // source reads " is not iterable", an identifier-callee call // " is not a function or its return value is not iterable"; @@ -2334,7 +2349,7 @@ export function matchGroupsPatternSource( !ts.isObjectBindingPattern(pattern) || pattern.elements.nodes.length === 0 || !(row.type.kind === "dyn" - ? isJsSourceFile(pattern.getSourceFile()) + ? isJsSourceFile(ts.getSourceFileOfNode(pattern)) : isMatchSliceType(lowerer, row.type)) || !pattern.elements.nodes.every((el) => { if (el.dotDotDotToken !== undefined || el.name === undefined) return false; @@ -2377,7 +2392,9 @@ export function lowerBindingPattern( const patternType = lowerer.typeOf(pattern); const patternSymbol = patternType.getSymbol(); allowDynObject ||= - patternSymbol?.name === "SegmentData" && lowerer.isStdlibSymbol(patternSymbol); + patternSymbol !== undefined && + ts.symbolName(patternSymbol) === "SegmentData" && + lowerer.isStdlibSymbol(patternSymbol); allowDynObject ||= (patternType.flags & ts.TypeFlags.Object) !== 0 && lowerer.mapTypeOf(patternType)?.kind === "dyn"; @@ -2835,7 +2852,7 @@ export function lowerBindingPattern( // object patterns use the same exact keyed-read desugar. if ( srcType.kind === "union" && - isJsSourceFile(pattern.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(pattern)) && lowerer.dynConvertible(srcType) ) { lowerer.lowerBindingPattern( @@ -3119,6 +3136,11 @@ export function lowerBindingPattern( lowerArrayPositionObjectPattern(lowerer, pattern, srcRef, srcType, isLet, out); return; } + const nsSource = lowerer.sourceFileOfModuleNamespace(srcType); + if (nsSource !== null) { + lowerModuleNamespacePattern(lowerer, pattern, nsSource, isLet, out); + return; + } if (srcType.kind !== "record") { lowerer.unsupported( "SC1031", @@ -3238,7 +3260,7 @@ export function lowerBindingPattern( lowerer.bindPatternTarget(el.name, value, isLet, out); continue; } - if (!fieldType && isJsSourceFile(el.getSourceFile())) { + if (!fieldType && isJsSourceFile(ts.getSourceFileOfNode(el))) { // Inferred JavaScript shapes can omit optional own properties and // inherit values from their prototype. Read the live checked view. const loc = locOf(el); @@ -3372,10 +3394,73 @@ function patternBindingType(lowerer: Lowerer, name: ts.BindingName): IrType | nu const type = lowerer.typeOf(name); return ( lowerer.mapTypeOf(type) ?? - (isJsSourceFile(name.getSourceFile()) ? dynFallbackType(lowerer, name, type) : null) + (isJsSourceFile(ts.getSourceFileOfNode(name)) ? dynFallbackType(lowerer, name, type) : null) ); } +/** `const { a, b: c } = ns` / `const { f } = await import("./m")` over + * a program module's namespace: each element reads the export exactly + * like `ns.a` does — the member resolves to the exporter's own binding + * (nsMemberIdentOf's rule), read once at the pattern's position, so the + * bound name holds the export's value at that moment (destructuring + * copies; it does not alias the live binding). The namespace value + * itself was already evaluated into the pattern's temp, so its side + * effects (the dynamic import) keep their order. Rest elements would + * materialize the namespace object and keep their fence. The exports + * come from the source's module, not from the pattern's type: an + * annotation can name another module with a compatible shape. */ +function lowerModuleNamespacePattern( + lowerer: Lowerer, + pattern: ts.ObjectBindingPattern, + source: ts.SourceFile, + isLet: boolean, + out: IrStmt[], +): void { + const moduleSym = lowerer.checker.getSymbolAtLocation(source); + if (moduleSym === undefined) { + lowerer.unsupported( + "SC1031", + pattern, + "destructuring a module namespace without a module symbol", + ); + } + const nsType = lowerer.checker.getTypeOfSymbol(moduleSym); + for (const el of pattern.elements.nodes) { + if (el.name === undefined) continue; + if (el.dotDotDotToken) { + lowerer.unsupported( + "SC1031", + el, + "rest bindings over module namespaces (the namespace object is not materialized — bind the exports you need by name)", + ); + } + const prop = el.propertyName ?? el.name; + const propName = + ts.isIdentifier(prop) || + ts.isComputedPropertyName(prop) || + ts.isStringLiteralLike(prop) || + ts.isNumericLiteral(prop) + ? patternKeyNameOf(lowerer, prop as ts.PropertyName) + : null; + let exported = + propName === null ? undefined : lowerer.checker.getPropertyOfType(nsType, propName); + if (exported !== undefined && exported.flags & ts.SymbolFlags.Alias) { + exported = lowerer.checker.getAliasedSymbol(exported); + } + const declared = exported?.valueDeclaration?.name as ts.Node | undefined; + if (declared === undefined || !ts.isIdentifier(declared)) { + lowerer.unsupported( + "SC1031", + el, + "destructuring module namespace members that are not named value exports", + ); + } + let value = lowerer.lowerExpr(declared); + if (el.initializer) value = applyBindingDefault(lowerer, el, value); + lowerer.bindPatternTarget(el.name, value, isLet, out); + } +} + function patternKeyNameOf(lowerer: Lowerer, prop: ts.PropertyName): string | null { if (ts.isIdentifier(prop) || ts.isPrivateIdentifier(prop)) return prop.text; if (ts.isComputedPropertyName(prop)) return lowerer.foldedStringKeyOf(prop.expression); @@ -3402,7 +3487,7 @@ function isRegexMatchResultPattern(lowerer: Lowerer, pattern: ts.ObjectBindingPa const symbol = type.getAliasSymbol() ?? type.getSymbol(); return ( symbol !== undefined && - (symbol.name === "RegExpExecArray" || symbol.name === "RegExpMatchArray") && + (ts.symbolName(symbol) === "RegExpExecArray" || ts.symbolName(symbol) === "RegExpMatchArray") && lowerer.isStdlibSymbol(symbol) ); } @@ -3713,7 +3798,7 @@ function stdlibGlobalTokenDestructure( decl: ts.VariableDeclaration, isLet: boolean, ): IrStmt[] | null { - if (decl.initializer === undefined || !isJsSourceFile(decl.getSourceFile())) return null; + if (decl.initializer === undefined || !isJsSourceFile(ts.getSourceFileOfNode(decl))) return null; const globalName = stdlibGlobalNameOf(lowerer, decl.initializer); if (globalName === null) return null; if (!ts.isObjectBindingPattern(decl.name)) { @@ -3789,7 +3874,7 @@ function stdlibGlobalTokenDestructure( `destructuring the non-builtin member '${propName}' of 'globalThis'`, ); } - const canonical = member.name === "global" ? "globalThis" : member.name; + const canonical = ts.symbolName(member) === "global" ? "globalThis" : ts.symbolName(member); token = `[builtin ${canonical}]`; alias = canonical; // Builtin constructors read as their interned function values, the @@ -4393,7 +4478,7 @@ function classInstanceRestValue( const deferOrderCheck = lowerer.instantiationContext !== null && lowerer.onEdge !== null && - !isJsSourceFile(blame.getSourceFile()); + !isJsSourceFile(ts.getSourceFileOfNode(blame)); if (!deferOrderCheck) { if (!orderMatches(emitOrder)) { lowerer.unsupported( @@ -4411,7 +4496,7 @@ function classInstanceRestValue( // matters. const context = lowerer.instantiationContext; const countFailure = !lowerer.suppressStats; - const sf = blame.getSourceFile(); + const sf = ts.getSourceFileOfNode(blame); lowerer.shapeOrderMetadataFinalizers.push(() => { const current = lowerer.shapes.get(restT.shapeId) ?? restShape; const currentOrder = current.declaredOrder ?? current.fields.map((f) => f.name); @@ -4672,7 +4757,7 @@ function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration) if ( ts.isCallExpression(node.right) && (implicitMethodCallInfersReturn(lowerer, node.right) || - isJsSourceFile(node.right.getSourceFile())) + isJsSourceFile(ts.getSourceFileOfNode(node.right))) ) { invalid = true; return "stop"; @@ -4793,9 +4878,9 @@ export function lowerVarDecl( (decl.name.text === "module" || decl.name.text === "exports") && ts.isVariableStatement(decl.parent?.parent) && ts.isSourceFile(decl.parent?.parent?.parent) && - !isJsSourceFile(decl.getSourceFile()) && - !decl - .getSourceFile() + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && + !ts + .getSourceFileOfNode(decl) .statements.nodes.some( (s) => ts.isImportDeclaration(s) || @@ -5000,7 +5085,7 @@ export function lowerVarDecl( : undefined, ); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && raw.type.kind === "dyn" && @@ -5072,7 +5157,7 @@ export function lowerVarDecl( // immediately (the type covers the unassigned state), so they // initialize to the interned undefined arm like an omitted optional. let type = lowerer.irTypeOf(decl.name); - if (type.kind === "dyn" && isLet && isJsSourceFile(decl.getSourceFile())) { + if (type.kind === "dyn" && isLet && isJsSourceFile(ts.getSourceFileOfNode(decl))) { type = inferredEvolvingLetType(lowerer, decl) ?? type; } // `var v: void;` / `let x: undefined;` — a unit-only binding rides @@ -5099,7 +5184,7 @@ export function lowerVarDecl( // function (with its hint), not the unmappable declared type. If it // poisons, still declare the local when its type is representable, so // later references to this name don't produce cascading errors. - const inferredObjectType = isJsSourceFile(decl.getSourceFile()) + const inferredObjectType = isJsSourceFile(ts.getSourceFileOfNode(decl)) ? lowerer.mapTypeOf(lowerer.typeOf(decl.name)) : null; const inferredObjectArms = @@ -5116,7 +5201,7 @@ export function lowerVarDecl( inferredObjectArms.some( (arm) => arm.kind === "record" && jsObjectBindingExpands(lowerer, decl, arm), ) || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && ts.isObjectLiteralExpression(decl.initializer) && @@ -5132,7 +5217,7 @@ export function lowerVarDecl( try { init = expandsObject || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && isNativeBuiltinValueInitializer(lowerer, decl.initializer)) ? lowerer.lowerExprExpecting(decl.initializer, DYN) @@ -5169,7 +5254,7 @@ export function lowerVarDecl( (init.type.kind === "moduleNs" ? init.type : null) ?? (init.type.kind === "dyn" ? DYN : null); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && init.type.kind === "func" @@ -5200,8 +5285,8 @@ export function lowerVarDecl( if ( lowerer.implicitParamTypes !== null && init.type.kind === "dyn" && - isJsSourceFile(decl.getSourceFile()) && - npmStaticPackageOfPath(decl.getSourceFile().fileName) !== null + isJsSourceFile(ts.getSourceFileOfNode(decl)) && + npmStaticPackageOfPath(ts.getSourceFileOfNode(decl).fileName) !== null ) { type = DYN; } @@ -5209,17 +5294,17 @@ export function lowerVarDecl( init.type.kind === "dyn" && (inferredObjectType?.kind === "record" || inferredObjectType?.kind === "array" || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && inferredObjectArms.some((arm) => arm.kind === "record") && inferredObjectArms.every((arm) => isUnitType(arm) || arm.kind === "record")) || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && inferredObjectType !== null && (inferredObjectType.kind === "date" || inferredObjectType.kind === "func" || inferredObjectType.kind === "object" || jsOpenObjectType(decl, inferredObjectType, lowerer.shapes, lowerer.unions).kind === "dyn")) || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && inferredObjectType !== null && (inferredObjectType.kind === "void" || isUnitType(inferredObjectType) || @@ -5237,14 +5322,14 @@ export function lowerVarDecl( expandsObject || preservesObjectIdentity || keepsAssertedObject || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && isNativeBuiltinValueInitializer(lowerer, decl.initializer)) ) { type = DYN; } if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && init.type.kind === "array" && init.type.elem.kind === "dyn" && @@ -5260,14 +5345,14 @@ export function lowerVarDecl( // cannot hold the binding's future. The binding is checked-dynamic // instead (irTypeOf's JS story): writes dynFrom, typed exits dynCheck. // Const keeps the static empty record — no reassignment exists. - if (isLet && type?.kind === "record" && isJsSourceFile(decl.getSourceFile())) { + if (isLet && type?.kind === "record" && isJsSourceFile(ts.getSourceFileOfNode(decl))) { const shape = lowerer.shapes.get(type.shapeId); if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple) type = DYN; } if ( isLet && type !== null && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && (type.kind === "object" || type.kind === "symbol" || jsBindingHasOpenWrites(lowerer, decl)) @@ -5291,7 +5376,7 @@ export function lowerVarDecl( if ( returnedEmptyArrayType === null && (type === null || type.kind === "array") && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) { let initExpr: ts.Expression = decl.initializer; while (ts.isParenthesizedExpression(initExpr)) initExpr = initExpr.expression; @@ -5354,7 +5439,7 @@ export function lowerVarDecl( if ( type?.kind === "func" && init.type.kind === "dyn" && - (isJsSourceFile(decl.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(decl)) || (!decl.type && !hasJsTypeAnnotation(decl) && type.rest === true && @@ -5363,7 +5448,7 @@ export function lowerVarDecl( type = DYN; } if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && init.type.kind === "dyn" && @@ -5390,7 +5475,7 @@ export function lowerVarDecl( // type (a union with a fence-folded arm — the typeof-'bigint' // dual-mode ternary) adopts the initializer's static type, the // same rule genuine `any` consts take. - (type === null && isJsSourceFile(decl.getSourceFile()))) && + (type === null && isJsSourceFile(ts.getSourceFileOfNode(decl)))) && init.type.kind !== "void" && init.type.kind !== "caught" && !isUnitType(init.type) @@ -5411,7 +5496,7 @@ export function lowerVarDecl( let settledType: IrType = type; if ( !isLet && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !hasJsTypeAnnotation(decl) && decl.initializer && ts.isNewExpression(decl.initializer) @@ -5683,7 +5768,7 @@ function jsObjectBindingExpands( decl.type || !decl.initializer || !ts.isIdentifier(decl.name) || - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return false; let initializer = decl.initializer; @@ -5793,7 +5878,10 @@ function jsObjectBindingExpands( export function hasJsTypeAnnotation(decl: ts.VariableDeclaration): boolean { const statement = decl.parent?.parent; return ( - !!statement && /@type\b/.test(decl.getSourceFile().text.slice(statement.pos, decl.getStart())) + !!statement && + /@type\b/.test( + ts.getSourceFileOfNode(decl).text.slice(statement.pos, ts.tokenStartOfNode(decl)), + ) ); } @@ -5801,7 +5889,7 @@ export function hasJsTypeAnnotation(decl: ts.VariableDeclaration): boolean { * an any-valued callback. Keep those bindings open, including captured writes. */ export function jsBindingHasOpenWrites(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean { if ( - !isJsSourceFile(decl.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(decl)) || decl.type || hasJsTypeAnnotation(decl) || !ts.isIdentifier(decl.name) @@ -6303,7 +6391,7 @@ function lowerDeleteStatement(lowerer: Lowerer, expr: ts.DeleteExpression): IrSt } if (accessor && errorBase) return { kind: "exprStmt", expr: obj, loc }; } - if (isJsSourceFile(expr.getSourceFile()) && obj.type.kind === "object") { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && obj.type.kind === "object") { const info = lowerer.classes.get(obj.type.className); const keyType = ts.isElementAccessExpression(target) ? lowerer.typeOf(target.argumentExpression) @@ -6324,7 +6412,7 @@ function lowerDeleteStatement(lowerer: Lowerer, expr: ts.DeleteExpression): IrSt } } if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && obj.type.kind === "func" && lowerer.dynConvertible(obj.type) ) { @@ -6435,7 +6523,7 @@ function lowerCjsExportStatement( const symbol = lowerer.checker.getSymbolAtLocation(nameNode) ?? (ts.isIdentifier(nameNode) || ts.isPrivateIdentifier(nameNode) - ? lowerer.cjsModuleExportSymbol(cjs.expr.getSourceFile(), nameNode.text) + ? lowerer.cjsModuleExportSymbol(ts.getSourceFileOfNode(cjs.expr), nameNode.text) : undefined); const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; if (!g) { @@ -6510,7 +6598,7 @@ function lowerCjsExportStatement( // later real assignment keep the ordinary path (the export IS // undefined then). if (cjsVoidInitPreambleReals(cjs) !== null) return { kind: "block", body: [], loc }; - const sf = cjs.expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(cjs.expr); let root: IrGlobal | undefined; for (const stmt of sf.statements.nodes) { const whole = cjsExportAssignmentOf(stmt); @@ -6573,7 +6661,7 @@ function lowerCjsExportStatement( // were already evaluated by the const's own declaration, so this // statement may not RE-evaluate anything: expression-valued entries // (which the direct-table lowering assigns here) keep a fence. - tableObj = cjsExportTargetLiteral(cjs.expr.right, cjs.expr.getSourceFile()); + tableObj = cjsExportTargetLiteral(cjs.expr.right, ts.getSourceFileOfNode(cjs.expr)); resolvedTarget = tableObj !== null; if (!tableObj) { const single = lowerCjsSingleValueExport(lowerer, cjs.expr, loc); @@ -6635,7 +6723,7 @@ function lowerCjsExportStatement( lowerer.rejectThisInObjectMethod(method.body); const symbol = lowerer.checker.getSymbolAtLocation(method.name) ?? - lowerer.cjsModuleExportSymbol(cjs.expr.getSourceFile(), method.name.text); + lowerer.cjsModuleExportSymbol(ts.getSourceFileOfNode(cjs.expr), method.name.text); const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; if (g) { const fn = lowerer.lowerLambda(method); @@ -6910,7 +6998,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm // global — the expression path owns it (lowerCreateRequireCall). createRequireCalleeFileOf(lowerer, expr.expression) === null ) { - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); const spec = requireSpecOf(expr)!; if (lowerer.externalTypes.has(spec)) lowerer.externalHostFence(spec, expr, false); // A bare require of a package NOTHING INSTALLED resolves, in ANY @@ -6956,7 +7044,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm // environment-dependent branch); the static export table has one shape. if ( !topLevelJs && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.EqualsToken && (isCjsModuleExports(lowerer, expr.left) || isCjsExportsIdentifier(lowerer, expr.left)) @@ -7460,7 +7548,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm if ( recv && (recv.type.kind === "dyn" || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv.type.kind === "union" && lowerer.dynConvertible(recv.type))) ) { @@ -7490,7 +7578,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm // through the checked-dynamic node and keep the dynamic write, as // do JavaScript files, whose reads use that node too. if ( - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv?.type.kind === "object" && lowerer.isErrorRootedClass(recv.type.className) && expr.left.name.text === "code" @@ -7763,8 +7851,8 @@ function lowerDiscardedLogical(lowerer: Lowerer, expr: ts.BinaryExpression): IrS // picks another lowering, exactly as before. if (!(e instanceof PoisonError) || lowerer.diagSink !== null) throw e; const added = lowerer.diags.slice(diagsBefore); - const file = expr.getSourceFile().fileName; - const start = expr.getStart(); + const file = ts.getSourceFileOfNode(expr).fileName; + const start = ts.tokenStartOfNode(expr); const local = added.length > 0 && added.every( @@ -7914,16 +8002,42 @@ export function lowerDestructuringAssign( /** V8's spelling of a destructuring RHS in its TypeError message * ("Cannot destructure 'a' as it is undefined.") — plain identifiers - * spell their name (parens unwrap), and calls append an ellipsis to a - * supported callee spelling. Other CallPrinter forms keep a fence. */ + * spell their name (parens unwrap), `this`, property chains and + * identifier or numeric element reads spell their source, awaited and + * yielded values spell "(intermediate value)", and calls append an + * ellipsis to a supported callee spelling. Other CallPrinter forms keep + * a fence. */ function destrSpellingOf(rhs: ts.Expression): string | null { let e = rhs; - while (ts.isParenthesizedExpression(e)) e = e.expression; + // Parentheses and the type-only wrappers are not part of the emitted + // JavaScript expression V8 prints. + while ( + ts.isParenthesizedExpression(e) || + ts.isAsExpression(e) || + ts.isSatisfiesExpression(e) || + ts.isTypeAssertion(e) || + ts.isNonNullExpression(e) + ) { + e = e.expression; + } if (ts.isIdentifier(e)) return e.text; + if (e.kind === ts.SyntaxKind.ThisKeyword) return "this"; + // V8 prints an awaited or yielded operand as "(intermediate value)". + if (ts.isAwaitExpression(e) || ts.isYieldExpression(e)) return "(intermediate value)"; if (ts.isPropertyAccessExpression(e) && !e.questionDotToken) { const base = destrSpellingOf(e.expression); return base === null ? null : `${base}.${e.name.text}`; } + // `table[key]` and `list[0]`; V8 prints a numeric key in its canonical + // number form (`o[1.50]` reads "o[1.5]"). + if (ts.isElementAccessExpression(e) && !e.questionDotToken) { + const base = destrSpellingOf(e.expression); + const key = e.argumentExpression; + if (base === null) return null; + if (ts.isIdentifier(key)) return `${base}[${key.text}]`; + if (ts.isNumericLiteral(key)) return `${base}[${String(Number(key.text))}]`; + return null; + } if (ts.isCallExpression(e) && !e.questionDotToken) { const callee = destrSpellingOf(e.expression); return callee === null ? null : `${callee}(...)`; @@ -8009,7 +8123,7 @@ function destructuringAssignInto( const patternEmpty = ts.isObjectLiteralExpression(target) ? target.properties.nodes.length === 0 : target.elements.nodes.length === 0; - if (!patternEmpty && rhs !== null && isJsSourceFile(rhs.getSourceFile())) { + if (!patternEmpty && rhs !== null && isJsSourceFile(ts.getSourceFileOfNode(rhs))) { const globalName = stdlibGlobalNameOf(lowerer, rhs); if (globalName !== null) { lowerer.unsupported( @@ -10804,7 +10918,7 @@ function lowerForIn(lowerer: Lowerer, stmt: ts.ForInStatement): IrStmt { while (root?.base) root = root.base; if ( info?.decl && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && !info.def.runtime && !root?.def.runtime && !info.builtinError && diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 44027f65c..b9add95a6 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -171,6 +171,16 @@ export const UNDERSCORE_METHODS: ReadonlyMap = new Map([ ["flush", "_flush"], ]); +/** The completion callback's parameter position in each underscore + * method that takes one (cbTuples' "fn" positions). */ +export const STREAM_COMPLETION_CALLBACK_INDEX: ReadonlyMap = new Map([ + ["_write", 2], + ["_final", 0], + ["_destroy", 1], + ["_transform", 2], + ["_flush", 0], +]); + /** The accepted option-callback names (canonical flag order) and the * duplex-shaped-head answer for one runtime stream class. */ export function streamCtorShape(cls: string): { @@ -1465,7 +1475,7 @@ export function lowerStreamStaticCall( member === "toWeb" && cls === "%Readable" && call.arguments.nodes.length === 2 && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && ts.isObjectLiteralExpression(call.arguments.nodes[1]!) ) { for (const p of call.arguments.nodes[1]!.properties.nodes) { @@ -1499,7 +1509,7 @@ export function lowerStreamStaticCall( ); } const type: IrType = { kind: "object", className: "%Readable" }; - const js = isJsSourceFile(call.getSourceFile()); + const js = isJsSourceFile(ts.getSourceFileOfNode(call)); const srcNode = args[0]; const optsNode = args[1]; const srcT = srcNode ? lowerer.mapTypeOf(lowerer.typeOf(srcNode)) : null; @@ -2117,7 +2127,7 @@ function lowerStreamArg( loc: locOf(node), }); }; - if (isJsSourceFile(node.getSourceFile()) && lowerer.dynConvertible(v.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(node)) && lowerer.dynConvertible(v.type)) { if (what === "pipeline") { // Strings and arrays are iterables — VALID pipeline stages in // Node — and a record carrying a readable/writable member may be @@ -3131,7 +3141,7 @@ function lowerOperatorCallback( if (v.type.kind === "dyn") return v; if ( v.type.kind !== "func" && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && lowerer.dynConvertible(v.type) ) { // JavaScript passes any value; Node's validateFunction throws. diff --git a/packages/compiler/src/frontend/lowering/lower-test.ts b/packages/compiler/src/frontend/lowering/lower-test.ts index 32870c2cf..391a77245 100644 --- a/packages/compiler/src/frontend/lowering/lower-test.ts +++ b/packages/compiler/src/frontend/lowering/lower-test.ts @@ -86,8 +86,8 @@ function requireStatementPosition(lowerer: Lowerer, call: ts.CallExpression, wha * reports the `test` property's column), at the call for plain ones. */ function atStringOf(expr: ts.CallExpression): string { const target = ts.isPropertyAccessExpression(expr.expression) ? expr.expression.name : expr; - const sf = expr.getSourceFile(); - const pos = sf.getLineAndCharacterOfPosition(target.getStart()); + const sf = ts.getSourceFileOfNode(expr); + const pos = sf.getLineAndCharacterOfPosition(ts.tokenStartOfNode(target)); return `${sf.fileName}:${pos.line + 1}:${pos.character + 1}`; } diff --git a/packages/compiler/src/frontend/lowering/lower-timers.ts b/packages/compiler/src/frontend/lowering/lower-timers.ts index 22983fbb4..6b6a1b5d1 100644 --- a/packages/compiler/src/frontend/lowering/lower-timers.ts +++ b/packages/compiler/src/frontend/lowering/lower-timers.ts @@ -420,7 +420,7 @@ function lowerTimerCall( lowerer.noLowering(`clearTimeout with ${expr.arguments.nodes.length} arguments`, expr); } let handle = lowerer.lowerExpr(expr.arguments.nodes[0]!); - if (handle.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + if (handle.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const stored = lowerer.declareHiddenLocal("%timer.clear", DYN); const ref = varRef(stored.id, DYN, loc); handle = { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index a5f376fc0..e2236f592 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1349,7 +1349,7 @@ export function uncheckedOverloadHandleCall( * `never` at the ROOT stays out (`for (const v of [])`'s loop var — the * dead read the f64 mapping is FOR). */ export function neverTaintedJsType(lowerer: Lowerer, node: ts.Node, t: ts.Type): boolean { - if (!isJsSourceFile(node.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) return false; const walk = (x: ts.Type, depth: number): boolean => { if (depth === 0) return false; if (x.isUnionType()) return ts.constituentTypes(x).some((a) => walk(a, depth - 1)); @@ -1379,7 +1379,7 @@ export function ladderFenceExpr( const loc = locOf(node); const d = noLoweringDiag(surface, loc, hint); lowerer.runtimeFences.push(d); - const sf = node.getSourceFile(); + const sf = ts.getSourceFileOfNode(node); const pos = ts.getLineAndCharacterOfPosition(sf, loc.start); return { kind: "strLit", @@ -1391,7 +1391,7 @@ export function ladderFenceExpr( export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): IrType | null { if (t.flags & ts.TypeFlags.Void) return null; - if (!isJsSourceFile(node.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) { if (t.flags & ts.TypeFlags.Any) return DYN; const symbol = t.getSymbol(); const args = @@ -1406,16 +1406,17 @@ export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir ts.TypeFlags.Any) !== 0; if ( - symbol?.name === "Promise" && + symbol !== undefined && + ts.symbolName(symbol) === "Promise" && args[0] && (args[0].flags & ts.TypeFlags.Any || anyArray(args[0])) ) - return symbol.name === "Promise" && anyArray(args[0]) + return ts.symbolName(symbol) === "Promise" && anyArray(args[0]) ? { kind: "promise", inner: DYN } : jsFallbackPromiseType(lowerer, t); if ( symbol && - ["Generator", "AsyncGenerator", "IterableIterator"].includes(symbol.name) && + ["Generator", "AsyncGenerator", "IterableIterator"].includes(ts.symbolName(symbol)) && args.some((arg) => arg.flags & ts.TypeFlags.Any) ) return jsFallbackGeneratorType(lowerer, t); @@ -1437,7 +1438,8 @@ export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir if (generator) return generator; const symbol = t.getSymbol(); if ( - (symbol?.name === "Map" || symbol?.name === "ReadonlyMap") && + ((symbol !== undefined && ts.symbolName(symbol) === "Map") || + (symbol !== undefined && ts.symbolName(symbol) === "ReadonlyMap")) && lowerer.isStdlibSymbol(symbol) ) { const args = lowerer.checker.getTypeArguments(t as ts.TypeReference); @@ -1526,7 +1528,12 @@ export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir * has no fixed shape. The settled value crosses the checked native boundary. */ function jsFallbackPromiseType(lowerer: Lowerer, t: ts.Type): IrType | null { const symbol = t.getSymbol(); - if (symbol?.name !== "Promise" || !lowerer.isStdlibSymbol(symbol)) return null; + if ( + symbol === undefined || + ts.symbolName(symbol) !== "Promise" || + !lowerer.isStdlibSymbol(symbol) + ) + return null; const inner = lowerer.checker.getTypeArguments(t as ts.TypeReference)[0]; return inner ? { kind: "promise", inner: lowerer.mapTypeOf(inner) ?? DYN } : null; } @@ -1537,7 +1544,7 @@ function jsFallbackGeneratorType(lowerer: Lowerer, t: ts.Type): IrType | null { const symbol = t.getSymbol(); if ( !symbol || - !["Generator", "AsyncGenerator", "IterableIterator"].includes(symbol.name) || + !["Generator", "AsyncGenerator", "IterableIterator"].includes(ts.symbolName(symbol)) || !lowerer.isStdlibSymbol(symbol) ) return null; @@ -1545,7 +1552,7 @@ function jsFallbackGeneratorType(lowerer: Lowerer, t: ts.Type): IrType | null { const unit = ts.TypeFlags.Void | ts.TypeFlags.Undefined | ts.TypeFlags.Never; return { kind: "generator", - ...(symbol.name === "AsyncGenerator" ? { async: true as const } : {}), + ...(ts.symbolName(symbol) === "AsyncGenerator" ? { async: true as const } : {}), yieldT: yielded && yielded.flags & ts.TypeFlags.Never ? VOID : DYN, retT: !returned || returned.flags & unit ? VOID : DYN, nextT: !resumed || resumed.flags & unit ? UNDEFINED_T : DYN, @@ -1579,7 +1586,7 @@ function jsFallbackFunctionType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir const sigDecl = sig.declaration; const jsUnitReturn = sigDecl !== undefined && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && (retT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; const ret: IrType = jsUnitReturn ? DYN @@ -1613,7 +1620,7 @@ function jsArgumentsFunctionType(lowerer: Lowerer, t: ts.Type): IrType | null { ts.isFunctionExpression(decl) || ts.isArrowFunction(decl) ) || - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return null; const hasRest = decl.parameters.nodes.some((p) => p.dotDotDotToken !== undefined); @@ -1621,7 +1628,8 @@ function jsArgumentsFunctionType(lowerer: Lowerer, t: ts.Type): IrType | null { !hasRest && (ts.isArrowFunction(decl) || !bodyReadsArguments(decl) || - (decl.parameters.nodes.length > 0 && !isNodeEsmFile(decl.getSourceFile(), lowerer.program))) + (decl.parameters.nodes.length > 0 && + !isNodeEsmFile(ts.getSourceFileOfNode(decl), lowerer.program))) ) return null; const shapes = paramShapes(lowerer, decl.parameters.nodes); @@ -2265,7 +2273,7 @@ export class Lowerer { return ( sym !== undefined && sym.declarations.some((d) => { - if (!isJsSourceFile(d.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(d))) return false; // The pattern itself, or the `= {}` default of a pattern parameter. const pattern = ts.isObjectLiteralExpression(d) && @@ -2381,7 +2389,7 @@ export class Lowerer { // A checked JavaScript indexed read can already be boxed. The checker // calls the element present, but that inference is not a runtime guard. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && value.kind === "dynCheck" && value.value.type.kind === "dyn" ) @@ -3305,7 +3313,8 @@ export class Lowerer { if (this.localClassInstantiations.some((entry) => entry.owner === parent)) break; if ( parent.typeParameters?.nodes.length || - (implicitMonoFile(parent.getSourceFile()) && monomorphizedImplicit(this, parent)) + (implicitMonoFile(ts.getSourceFileOfNode(parent)) && + monomorphizedImplicit(this, parent)) ) return null; } @@ -3331,15 +3340,15 @@ export class Lowerer { // lowering, long after the constructor completes. isProgramFile: (sf) => this.fileTag.has(sf), lifetimeRegionClass: (decl) => - isExperimentalDeclarationFile(decl.getSourceFile(), this.experimentalFiles) + isExperimentalDeclarationFile(ts.getSourceFileOfNode(decl), this.experimentalFiles) ? lifetimeRegionType(this) : null, internTableClass: (decl) => - isExperimentalDeclarationFile(decl.getSourceFile(), this.experimentalFiles) + isExperimentalDeclarationFile(ts.getSourceFileOfNode(decl), this.experimentalFiles) ? internTableType(this) : null, threadSlotsClass: (decl) => - isExperimentalDeclarationFile(decl.getSourceFile(), this.experimentalFiles) + isExperimentalDeclarationFile(ts.getSourceFileOfNode(decl), this.experimentalFiles) ? threadSlotsType(this) : null, moduleNamespaceId: (type) => this.moduleNamespaceIdOfType(type), @@ -3405,9 +3414,10 @@ export class Lowerer { const weak = (part: ts.Type): boolean => { const symbol = part.getSymbol(); return ( - (symbol?.name === "WeakMap" || symbol?.name === "WeakSet") && + ((symbol !== undefined && ts.symbolName(symbol) === "WeakMap") || + (symbol !== undefined && ts.symbolName(symbol) === "WeakSet")) && symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && this.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && this.isStdlibFile(ts.getSourceFileOfNode(d)), ) ); }; @@ -3516,9 +3526,12 @@ export class Lowerer { } return false; })() - ? this.qualify(decl.getSourceFile(), `%cx${decl.getStart()}.${decl.name?.text ?? ""}`) + ? this.qualify( + ts.getSourceFileOfNode(decl), + `%cx${ts.tokenStartOfNode(decl)}.${decl.name?.text ?? ""}`, + ) : this.qualify( - decl.getSourceFile(), + ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + (decl.name ? decl.name.text : "%anon"), ); for (let index = this.localClassInstantiations.length - 1; index >= 0; index--) { @@ -3762,7 +3775,10 @@ export class Lowerer { return null; if (this.externalTypes.has(importDecl.moduleSpecifier.text)) return importDecl.moduleSpecifier.text; - const dep = this.moduleSourceFileOf(importDecl.getSourceFile(), importDecl.moduleSpecifier); + const dep = this.moduleSourceFileOf( + ts.getSourceFileOfNode(importDecl), + importDecl.moduleSpecifier, + ); return dep !== null && !dep.isDeclarationFile ? this.externalTypeSpecifierOfModuleExport(dep, member, new Set(), new Set()) : null; @@ -3810,7 +3826,7 @@ export class Lowerer { // A namespace OBJECT from a project module is not wholly external; // property accesses resolve their selected member separately above. if (importedName === null) return null; - const dep = this.moduleSourceFileOf(decl.getSourceFile(), specNode); + const dep = this.moduleSourceFileOf(ts.getSourceFileOfNode(decl), specNode); return dep !== null && !dep.isDeclarationFile ? this.externalTypeSpecifierOfModuleExport(dep, importedName, seenSymbols, seenExports) : null; @@ -3818,7 +3834,7 @@ export class Lowerer { for (const decl of declarations) { const owners = this.externalTypeSpecifiersByFile.get( - tsgoPath(resolve(decl.getSourceFile().fileName)), + tsgoPath(resolve(ts.getSourceFileOfNode(decl).fileName)), ); if (owners !== undefined && owners.length > 0) return owners[0]!; } @@ -3863,7 +3879,7 @@ export class Lowerer { } if (d === undefined || spec === undefined || !ts.isStringLiteral(spec) || name !== "default") return null; - const dep = resolveImport(this.program, d.getSourceFile(), spec.text); + const dep = resolveImport(this.program, ts.getSourceFileOfNode(d), spec.text); if (!dep) return null; sym = defaultExportSymbolOf(this, dep) ?? undefined; } @@ -3979,8 +3995,8 @@ export class Lowerer { // Project aliases/self-references resolve through the same entry point as // relative imports; an opted-in --npm-static package is the fallback. const dep = - resolveImport(this.program, importDecl.getSourceFile(), spec) ?? - npmStaticDepSf7(this.program, importDecl.getSourceFile(), spec); + resolveImport(this.program, ts.getSourceFileOfNode(importDecl), spec) ?? + npmStaticDepSf7(this.program, ts.getSourceFileOfNode(importDecl), spec); if (!dep || !isJsSourceFile(dep) || isNodeEsmFile(dep, this.program)) return null; return dep; } @@ -4020,8 +4036,8 @@ export class Lowerer { if (directSpec !== null) { if (isJsonRequireDecl(this, decl)) return null; return ( - resolveImport(this.program, decl.getSourceFile(), directSpec, "require") ?? - npmStaticDepSf7(this.program, decl.getSourceFile(), directSpec, "require") + resolveImport(this.program, ts.getSourceFileOfNode(decl), directSpec, "require") ?? + npmStaticDepSf7(this.program, ts.getSourceFileOfNode(decl), directSpec, "require") ); } return createRequireProgramModuleOf(this, decl.initializer)?.dep ?? null; @@ -4361,7 +4377,7 @@ export class Lowerer { * report on the next tick. Later loads are cache hits either way. */ punycodeRequireWarning(node: ts.Node): IrStmt { const loc = locOf(node); - const fromDependency = /[\\/]node_modules[\\/]/.test(node.getSourceFile().fileName); + const fromDependency = /[\\/]node_modules[\\/]/.test(ts.getSourceFileOfNode(node).fileName); return { kind: "exprStmt", expr: { @@ -4432,8 +4448,8 @@ export class Lowerer { // those globals stay uninitialized: the dep's module body would never // run. const dep = - resolveImport(this.program, node.getSourceFile(), spec, "require") ?? - npmStaticDepSf7(this.program, node.getSourceFile(), spec, "require"); + resolveImport(this.program, ts.getSourceFileOfNode(node), spec, "require") ?? + npmStaticDepSf7(this.program, ts.getSourceFileOfNode(node), spec, "require"); if (!dep || dep.fileName.endsWith(".json")) return null; if (this.asyncInitFiles.has(dep)) { this.unsupported( @@ -4450,7 +4466,7 @@ export class Lowerer { expr: { kind: "call", callee: initName, args: [], type: VOID, loc }, loc, }; - const parentModule = this.cjsModuleIdOf.get(node.getSourceFile()); + const parentModule = this.cjsModuleIdOf.get(ts.getSourceFileOfNode(node)); const childModule = this.cjsModuleIdOf.get(dep); if (parentModule === undefined || childModule === undefined) return init; return { @@ -5211,7 +5227,7 @@ export class Lowerer { const decl = symbol.valueDeclaration; if (!decl || !ts.isPropertyDeclaration(decl) || !ts.isIdentifier(decl.name)) return false; if (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Static) return false; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (isJsSourceFile(sf) || sf.isDeclarationFile) return false; const declared = typeOrNull(decl.name); const mapped = declared ? this.mapTypeOf(declared) : null; @@ -5301,7 +5317,8 @@ export class Lowerer { const initSymbol = initType.getAliasSymbol() ?? initType.getSymbol(); const matchResult = initSymbol !== undefined && - (initSymbol.name === "RegExpExecArray" || initSymbol.name === "RegExpMatchArray"); + (ts.symbolName(initSymbol) === "RegExpExecArray" || + ts.symbolName(initSymbol) === "RegExpMatchArray"); if (arrayType?.kind !== "array" || matchResult) return scanRecordPattern(name, init); const positions = name.elements.nodes.filter((el) => { const key = el.propertyName; @@ -5477,7 +5494,7 @@ export class Lowerer { }; for (const [symbol, signature] of signatureBySymbol) { const declaration = functionDeclBySymbol.get(symbol); - if (!declaration || !isJsSourceFile(declaration.getSourceFile())) continue; + if (!declaration || !isJsSourceFile(ts.getSourceFileOfNode(declaration))) continue; const checked = this.checker.getSignatureFromDeclaration(declaration); const checkedReturn = checked ? this.checker.getReturnTypeOfSignature(checked) : null; // Checker-known optional results already narrow at each use. Only @@ -6319,7 +6336,7 @@ export class Lowerer { const callee = functionDeclBySymbol.get(symbol); if ( callee && - isJsSourceFile(callee.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(callee)) && !node.arguments.nodes.some(ts.isSpreadElement) ) { for (let i = 0; i < sig.params.length; i++) { @@ -6535,7 +6552,7 @@ export class Lowerer { for (const [symbol, decl] of functionDeclBySymbol) if (scanReturns(symbol, decl)) changed = true; const noteGenericReturn = (symbol: ts.Symbol, decl: ts.FunctionLikeDeclaration): void => { - if (optionalReturns.has(symbol) || isJsSourceFile(decl.getSourceFile())) return; + if (optionalReturns.has(symbol) || isJsSourceFile(ts.getSourceFileOfNode(decl))) return; if (!returnsOf(decl).some(genericMayBeOptional)) return; optionalReturns.add(symbol); this.runtimeOptionalGenericReturns.add(decl); @@ -6812,7 +6829,7 @@ export class Lowerer { if ( global.type.kind !== "func" || !ts.isBinaryExpression(declaration) || - !isJsSourceFile(declaration.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(declaration)) ) continue; const value = peel(declaration.right); @@ -7288,7 +7305,7 @@ export class Lowerer { if (n === 0) return; this.stats.statementsTotal += n; this.stats.statementsFailed += n; - const file = decl.getSourceFile().fileName; + const file = ts.getSourceFileOfNode(decl).fileName; for (let i = 0; i < n; i++) { this.bumpFileStat(file, "total"); this.bumpFileStat(file, "failed"); @@ -7907,8 +7924,8 @@ export class Lowerer { "this statement uses a construct with no static lowering"; const messageLoc = first?.loc ?? loc; const source = first - ? (this.program.getSourceFile(messageLoc.file) ?? node.getSourceFile()) - : node.getSourceFile(); + ? (this.program.getSourceFile(messageLoc.file) ?? ts.getSourceFileOfNode(node)) + : ts.getSourceFileOfNode(node); const pos = ts.getLineAndCharacterOfPosition(source, messageLoc.start); return { kind: "runtimeFence", @@ -8027,10 +8044,12 @@ export class Lowerer { // here; constructor objects such as Headers still can. if ( typeSym && - (FETCH_AMBIENT_TYPES as readonly string[]).includes(typeSym.name) && + (FETCH_AMBIENT_TYPES as readonly string[]).includes(ts.symbolName(typeSym)) && this.isStdlibSymbol(typeSym) ) { - this.pushDiag(unsupportedNativeApiDiag(`a value of type '${typeSym.name}'`, locOf(node))); + this.pushDiag( + unsupportedNativeApiDiag(`a value of type '${ts.symbolName(typeSym)}'`, locOf(node)), + ); throw new PoisonError(); } // ENUM OBJECTS (`typeof e` — a numeric enum's type carries the @@ -8044,7 +8063,7 @@ export class Lowerer { unsupportedDiag( "SC1090", locOf(node), - `enum objects as values ('${typeSym.name}' — member reads like '${typeSym.name}.X' compile to constants; the object itself has no runtime representation)`, + `enum objects as values ('${ts.symbolName(typeSym)}' — member reads like '${ts.symbolName(typeSym)}.X' compile to constants; the object itself has no runtime representation)`, ), ); throw new PoisonError(); @@ -8074,7 +8093,7 @@ export class Lowerer { ownSym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - this.isStdlibFile(d.getSourceFile()), + this.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { this.pushDiag(noLoweringDiag(this.checker.typeToString(type), locOf(node))); @@ -8120,7 +8139,7 @@ export class Lowerer { stdlibOwnSym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - this.isStdlibFile(d.getSourceFile()), + this.isStdlibFile(ts.getSourceFileOfNode(d)), ); // OVERLOADED call signatures (SC2007) — after the generic branch, so a // generic overload set keeps the monomorphization story: a value of a @@ -8182,7 +8201,7 @@ export class Lowerer { noLoweringDiag( this.checker.typeToString(type), locOf(node), - typeHints[stdlibOwnSym?.name ?? ""], + typeHints[ts.symbolNameOf(stdlibOwnSym) ?? ""], ), ); throw new PoisonError(); @@ -8259,7 +8278,7 @@ export class Lowerer { } typeOf(node: ts.Node): ts.Type { - if (ts.isPropertyAccessExpression(node) && isJsSourceFile(node.getSourceFile())) { + if (ts.isPropertyAccessExpression(node) && isJsSourceFile(ts.getSourceFileOfNode(node))) { const receiver = node.expression.kind === ts.SyntaxKind.ThisKeyword && this.fnStack.length > 0 ? this.resolveThis()?.type @@ -8280,7 +8299,7 @@ export class Lowerer { } // A symbol-keyed field read through a CommonJS key-table key reads as // the field's layout type (tableKeyFieldType). - if (ts.isElementAccessExpression(node) && isJsSourceFile(node.getSourceFile())) { + if (ts.isElementAccessExpression(node) && isJsSourceFile(ts.getSourceFileOfNode(node))) { const field = tableKeyFieldType(this, node); if (field) { if (field.kind === "f64") return this.checker.getNumberType(); @@ -8294,7 +8313,7 @@ export class Lowerer { // into native field loads when the method is borrowed by another object. if ( node.kind === ts.SyntaxKind.ThisKeyword && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && this.fnStack.length > 0 && this.resolveThis()?.type.kind === "dyn" ) @@ -8568,7 +8587,7 @@ export class Lowerer { // locals at null/undefined despite later writes. Keep mutable native // storage instead of a slot restricted to the initializer's unit value. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && (t.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 ) return DYN; @@ -9599,7 +9618,9 @@ export class Lowerer { : []; const errorOptions = parts.some((part) => { const sym = part.getAliasSymbol() ?? part.getSymbol(); - return sym?.name === "ErrorOptions" && this.isStdlibSymbol(sym); + return ( + sym !== undefined && ts.symbolName(sym) === "ErrorOptions" && this.isStdlibSymbol(sym) + ); }); if (errorOptions) { const actual = this.typeOf(node); @@ -9635,7 +9656,7 @@ export class Lowerer { let literal = node; while (ts.isParenthesizedExpression(literal)) literal = literal.expression; if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !ts.isArrayLiteralExpression(literal) && e.kind === "dynFrom" && e.value.type.kind === "array" @@ -9667,7 +9688,7 @@ export class Lowerer { if ( e.type.kind === "union" && !typeEquals(e.type, expected) && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && this.dynConvertible(e.type) ) { const sourceArms = this.unions.get(e.type.unionId)?.arms ?? []; @@ -9722,7 +9743,7 @@ export class Lowerer { !ts.isIdentifier(d.name) || !ts.isVariableDeclarationList(d.parent) || !ts.isVariableStatement(d.parent.parent) || - d.getSourceFile().isDeclarationFile || + ts.getSourceFileOfNode(d).isDeclarationFile || (ts.getCombinedModifierFlags(d) & ts.ModifierFlags.Ambient) !== 0 ) { return null; @@ -9941,11 +9962,11 @@ export class Lowerer { const builtinOption = (name: string): boolean => parts.some((part) => { const sym = part.getAliasSymbol() ?? part.getSymbol(); - return sym?.name === name && this.isStdlibSymbol(sym); + return ts.symbolNameOf(sym) === name && this.isStdlibSymbol(sym); }); if (ts.isObjectLiteralExpression(x)) { if ( - isJsSourceFile(x.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(x)) || builtinOption("RequestInit") || builtinOption("ErrorOptions") || builtinOption("SpawnOptions") || @@ -10028,7 +10049,7 @@ export class Lowerer { coerceExistingInto(node: ts.Expression, e: IrExpr, expected: IrType): IrExpr { if ( (expected.kind === "union" && this.armTag(expected.unionId, UNDEFINED_T) >= 0) || - (expected.kind === "dyn" && isJsSourceFile(node.getSourceFile())) + (expected.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(node))) ) { // A destination that accepts undefined must retain an unchecked // read's storage value even when the checker still calls it present. @@ -10051,7 +10072,8 @@ export class Lowerer { return null; const symbol = this.resolveValueSymbol(value.expression); if (!symbol || !this.isStdlibSymbol(symbol)) return null; - const kind = symbol.name === "Map" ? "map" : symbol.name === "Set" ? "set" : null; + const kind = + ts.symbolName(symbol) === "Map" ? "map" : ts.symbolName(symbol) === "Set" ? "set" : null; if (!kind) return null; const arms = expected.kind === "union" ? (this.unions.get(expected.unionId)?.arms ?? []) : [expected]; @@ -10176,7 +10198,7 @@ export class Lowerer { while (ts.isParenthesizedExpression(literal)) literal = literal.expression; return ( expected.kind === "dyn" && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isObjectLiteralExpression(literal) ); } @@ -10642,7 +10664,7 @@ export class Lowerer { } } } - if (!isJsSourceFile(decl.getSourceFile())) continue; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) continue; for (const member of decl.members.nodes) { if (ts.isConstructorDeclaration(member)) { for (const param of member.parameters.nodes) { @@ -10675,7 +10697,7 @@ export class Lowerer { } // npm-static implicit-any classification resolves body parameter // references while collecting the method's shape. - if (implicitMonoFile(decl.getSourceFile()) && member.body) { + if (implicitMonoFile(ts.getSourceFileOfNode(decl)) && member.body) { symbolRoots.push(member.body); } } @@ -12874,14 +12896,14 @@ export class Lowerer { // node:stream/web's exports are the web stream globals themselves: in // JavaScript sources the same identity token as the global spelling // (`require("stream/web").ReadableStream === globalThis.ReadableStream`). - if (bi.module === "stream/web" && isJsSourceFile(expr.getSourceFile())) { + if (bi.module === "stream/web" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "strLit", value: `[builtin ${bi.member}]`, type: STRING, loc }; } // A bare `fs.constants` read (not one of the baked bits above). if (bi.member === "constants" && bi.module === "fs") return fsConstantsObject(loc); // `stream.Readable` as a JavaScript value: the runtime's untyped // constructor, the base a constructor function inherits. - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const classKind = builtinMemberClassKind(bi.module, bi.member); if (classKind !== null) return builtinClassValue(classKind, loc); } @@ -12889,7 +12911,7 @@ export class Lowerer { this.unsupported( "SC1090", expr, - `library functions as values (call '${expr.getText()}' directly)`, + `library functions as values (call '${ts.getTextOfNode(expr)}' directly)`, ); } this.noLowering( diff --git a/packages/compiler/src/frontend/lowering/module-member-this.ts b/packages/compiler/src/frontend/lowering/module-member-this.ts index 592136bec..8d1592b47 100644 --- a/packages/compiler/src/frontend/lowering/module-member-this.ts +++ b/packages/compiler/src/frontend/lowering/module-member-this.ts @@ -70,9 +70,9 @@ function ignoresOwnThis(lowerer: Lowerer, fn: ts.Node, bindings: Bindings, depth for (const arg of node.arguments.nodes.slice(1)) visit(arg); return; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - fn.forEachChild(visit); + ts.forEachChild(fn, visit); return ignores; } @@ -176,7 +176,7 @@ function mayRebind(lowerer: Lowerer, fn: ts.Node, param: ts.Identifier): boolean found = true; return; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; // The function's own parameters declare the name; their defaults can write it. const params = (fn as { parameters?: ts.NodeArray }).parameters; @@ -195,9 +195,9 @@ function returnedExpressions(fn: ts.Node): ts.Expression[] | null { const visit = (node: ts.Node): void => { if (ts.isFunctionLike(node) || ts.isClassLikeDeclaration(node)) return; if (ts.isReturnStatement(node) && node.expression) result.push(node.expression); - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return result; } diff --git a/packages/compiler/src/frontend/lowering/nan-coded-reads.ts b/packages/compiler/src/frontend/lowering/nan-coded-reads.ts index 4270457d5..65efe5b2a 100644 --- a/packages/compiler/src/frontend/lowering/nan-coded-reads.ts +++ b/packages/compiler/src/frontend/lowering/nan-coded-reads.ts @@ -365,20 +365,22 @@ export class NanCodedReads { private removalReason(slot: Slot): string { const badSource = slot.sources.find((s) => s === null || this.sourceInfo(s) === null); if (badSource !== undefined) - return `source ${badSource ? badSource.getText().slice(0, 60) : ""}`; + return `source ${badSource ? ts.getTextOfNode(badSource).slice(0, 60) : ""}`; const nan = this.genuineNaN.has(slot); const badUse = slot.uses.find((u) => !this.useSafe(u) && (nan || !this.reboxConsumer(u))); - if (badUse) - return `${nan ? "nan-use" : "use"} ${transparentParent(badUse).parent?.getText().slice(0, 80) ?? "?"}`; + if (badUse) { + const parent = transparentParent(badUse).parent; + return `${nan ? "nan-use" : "use"} ${parent === undefined ? "?" : ts.getTextOfNode(parent).slice(0, 80)}`; + } return "?"; } private debugReport(reached: Set): void { const name = (slot: Slot): string => { const decl = this.host.declarationOf(slot.symbol); - const sf = decl?.getSourceFile(); - const where = sf && decl ? `${sf.fileName}:${decl.getStart()}` : "?"; - return `${slot.kind} ${slot.symbol.name} @ ${where}`; + const sf = decl === undefined ? undefined : ts.getSourceFileOfNode(decl); + const where = sf && decl ? `${sf.fileName}:${ts.tokenStartOfNode(decl)}` : "?"; + return `${slot.kind} ${ts.symbolName(slot.symbol)} @ ${where}`; }; for (const slot of reached) { const ok = this.eligible.has(slot); @@ -483,7 +485,11 @@ export class NanCodedReads { return true; } const decl = this.host.declarationOf(symbol); - if (!decl || isJsSourceFile(decl.getSourceFile()) || decl.getSourceFile().isDeclarationFile) + if ( + !decl || + isJsSourceFile(ts.getSourceFileOfNode(decl)) || + ts.getSourceFileOfNode(decl).isDeclarationFile + ) return false; let name: ts.Identifier; if (ts.isParameter(decl)) { @@ -565,7 +571,7 @@ export class NanCodedReads { !nonNegativeIntegerLiteral(decl.initializer) || !ts.isVariableDeclarationList(decl.parent) || !(decl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) || - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return false; const name = decl.name; @@ -685,7 +691,7 @@ export class NanCodedReads { !(decl.parent.flags & ts.NodeFlags.Const) || // A module binding can escape through an export. (ts.isVariableStatement(decl.parent.parent) && ts.isSourceFile(decl.parent.parent.parent)) || - isJsSourceFile(decl.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(decl)) || this.host.bytesElemOf(decl.name) === null ) return null; @@ -755,7 +761,7 @@ export class NanCodedReads { if (!decl || (!ts.isFunctionDeclaration(decl) && !ts.isMethodDeclaration(decl))) return null; if (!decl.body || decl.asteriskToken || decl.typeParameters?.nodes || !decl.name) return null; if (!ts.isIdentifier(decl.name)) return null; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (isJsSourceFile(sf) || sf.isDeclarationFile) return null; const flags = ts.getCombinedModifierFlags(decl); if (flags & (ts.ModifierFlags.Async | ts.ModifierFlags.Abstract | ts.ModifierFlags.Ambient)) @@ -831,7 +837,7 @@ export class NanCodedReads { private scanBindingSlot(symbol: ts.Symbol): BindingSlot | null { const decl = this.host.declarationOf(symbol); if (!decl) return null; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (isJsSourceFile(sf) || sf.isDeclarationFile) return null; let nameNode: ts.Identifier; const slot: BindingSlot = { @@ -1362,7 +1368,7 @@ export class NanCodedReads { decl.initializer && ts.isVariableDeclarationList(decl.parent) && decl.parent.flags & ts.NodeFlags.Const && - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return this.plainNumber(decl.initializer, depth + 1); return null; diff --git a/packages/compiler/src/frontend/lowering/narrowing-stability.ts b/packages/compiler/src/frontend/lowering/narrowing-stability.ts index 01801470d..1c991c6ea 100644 --- a/packages/compiler/src/frontend/lowering/narrowing-stability.ts +++ b/packages/compiler/src/frontend/lowering/narrowing-stability.ts @@ -191,7 +191,8 @@ export class NarrowingStability { const checker = this.lowerer.checker; const signature = checker.getResolvedSignature(call); const declaration = signature ? signature.declaration : undefined; - if (!declaration || !this.lowerer.isStdlibFile(declaration.getSourceFile())) return true; + if (!declaration || !this.lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration))) + return true; const callee = skipOuter(call.expression); if ( ts.isPropertyAccessExpression(callee) && @@ -287,11 +288,11 @@ export class NarrowingStability { const last = chain.links[chain.links.length - 1]; const refKey = last ? `.${last.name}` : (chain.root as ts.Identifier).text; const candidates = facts.refs.get(refKey) ?? []; - const useStart = node.getStart(); + const useStart = ts.tokenStartOfNode(node); let start = -1; for (const candidate of candidates) { if (candidate === node) continue; - const candidateStart = candidate.getStart(); + const candidateStart = ts.tokenStartOfNode(candidate); if (candidateStart >= useStart) break; if (referenceKey(candidate) !== chain.key) continue; start = candidateStart; @@ -313,7 +314,7 @@ export class NarrowingStability { // between. let end = useStart; for (const loop of facts.loops) { - const loopStart = loop.getStart(); + const loopStart = ts.tokenStartOfNode(loop); if (loopStart >= start && loopStart <= useStart && loop.end >= node.end) end = Math.max(end, loop.end); } @@ -376,7 +377,9 @@ export class NarrowingStability { if (decl !== undefined && ts.isVariableDeclaration(decl)) { if (decl.initializer !== undefined && this.mayBeStale(decl.initializer)) result = true; else { - for (const write of this.writesIn(decl.getSourceFile()).get(symbol.name) ?? []) { + for (const write of this.writesIn(ts.getSourceFileOfNode(decl)).get( + ts.symbolName(symbol), + ) ?? []) { const assignment = write.parent; if ( ts.isBinaryExpression(assignment) && @@ -430,7 +433,7 @@ export class NarrowingStability { ts.isParameter(decl)); if (decl && mutableBinding) { const home = flowBoundary(decl); - const writes = this.writesIn(decl.getSourceFile()).get(symbol.name) ?? []; + const writes = this.writesIn(ts.getSourceFileOfNode(decl)).get(ts.symbolName(symbol)) ?? []; for (const write of writes) { if (flowBoundary(write) === home) continue; if (this.lowerer.resolveValueSymbol(write) === symbol) { @@ -497,7 +500,7 @@ export class NarrowingStability { if (name === "toString" || name === "valueOf" || name === "toJSON") facts.conversion = true; if (ts.isComputedPropertyName(node.name)) { - const text = node.name.expression.getText(); + const text = ts.getTextOfNode(node.name.expression); if (text.includes("toPrimitive")) facts.conversion = true; if (text.includes("iterator") || text.includes("asyncIterator")) facts.iteration = true; } @@ -711,7 +714,7 @@ export class NarrowingStability { const symbol = type.getSymbol(); return ( symbol !== undefined && - BUILTIN_COLLECTIONS.has(symbol.name) && + BUILTIN_COLLECTIONS.has(ts.symbolName(symbol)) && this.lowerer.isStdlibSymbol(symbol) ); } @@ -1062,7 +1065,7 @@ function memberUse( function flowBoundary(node: ts.Node): ts.Node { let n: ts.Node | undefined = node.parent; while (n && !isFlowBoundary(n)) n = n.parent; - return n ?? node.getSourceFile(); + return n ?? ts.getSourceFileOfNode(node); } function isFlowBoundary(node: ts.Node): boolean { diff --git a/packages/compiler/src/frontend/lowering/native-ffi.ts b/packages/compiler/src/frontend/lowering/native-ffi.ts index c9ff3300d..0f932ba93 100644 --- a/packages/compiler/src/frontend/lowering/native-ffi.ts +++ b/packages/compiler/src/frontend/lowering/native-ffi.ts @@ -393,7 +393,7 @@ export function lowerFfiCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr (decl) => ts.isFunctionDeclaration(decl) && decl.body === undefined && - libraryCallbackOwnsFile(lowerer, decl.getSourceFile()), + libraryCallbackOwnsFile(lowerer, ts.getSourceFileOfNode(decl)), ); if (callbackShaped) { lowerer.pushDiag( @@ -429,13 +429,13 @@ export function lowerFfiCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr if (lowerer.libraryCallbacks) { const declarations = symbol.declarations; const programDeclarations = declarations.filter((decl) => - libraryCallbackOwnsFile(lowerer, decl.getSourceFile()), + libraryCallbackOwnsFile(lowerer, ts.getSourceFileOfNode(decl)), ); const programAmbient = programDeclarations.length > 0 && programDeclarations.every( (decl) => - decl.getSourceFile().isDeclarationFile || + ts.getSourceFileOfNode(decl).isDeclarationFile || (ts.getCombinedModifierFlags(decl as ts.Declaration) & ts.ModifierFlags.Ambient) !== 0, ); diff --git a/packages/compiler/src/frontend/lowering/object-factory-new.ts b/packages/compiler/src/frontend/lowering/object-factory-new.ts index ddd8d6d56..bcb675692 100644 --- a/packages/compiler/src/frontend/lowering/object-factory-new.ts +++ b/packages/compiler/src/frontend/lowering/object-factory-new.ts @@ -41,7 +41,7 @@ function isObjectFactoryNew(lowerer: Lowerer, expr: ts.NewExpression): boolean { !owner || !declaration?.body || !bindingNeverReassigned(lowerer, symbol, owner) || - !isJsSourceFile(declaration.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(declaration)) || declaration.asteriskToken || declaration.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ) diff --git a/packages/compiler/src/frontend/lowering/private-brands.ts b/packages/compiler/src/frontend/lowering/private-brands.ts index e7b3edbd8..8507cc81a 100644 --- a/packages/compiler/src/frontend/lowering/private-brands.ts +++ b/packages/compiler/src/frontend/lowering/private-brands.ts @@ -30,7 +30,7 @@ export function perEvaluationPrivateNames(info: ClassInfo): boolean { const declaration = info.localClass ? info.decl : null; return ( !!declaration && - isJsSourceFile(declaration.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(declaration)) && declaration.members.nodes.some( (member) => member.name !== undefined && ts.isPrivateIdentifier(member.name), ) diff --git a/packages/compiler/src/frontend/lowering/promise-executor-adapter.ts b/packages/compiler/src/frontend/lowering/promise-executor-adapter.ts new file mode 100644 index 000000000..09d228b05 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/promise-executor-adapter.ts @@ -0,0 +1,102 @@ +import { varRef } from "../../ir/build.js"; +import { + type IrExpr, + type IrLocal, + type IrParam, + type IrType, + type SrcLoc, + VOID, + funcOf, + typeKey, +} from "../../ir/ir.js"; +import { newFnCtx, type Lowerer } from "./lowerer.js"; + +/** An executor VALUE whose parameters are checked-dynamic (a JavaScript + * function declaration: `function run(resolve, reject) {...}; return new + * Promise(run)`): the promise's native executor is an adapter closure that + * receives the native (resolve, reject) pair and calls the original with + * both boxed into the checked-dynamic tree, so the JavaScript body calls + * them like any function value. Null when the executor's shape is not + * adaptable (more than two parameters, a rest parameter, or a parameter + * that neither is dyn nor already the native closure). */ +export function adaptPromiseExecutor( + lowerer: Lowerer, + executor: IrExpr, + inner: IrType, + loc: SrcLoc, +): IrExpr | null { + const execT = executor.type; + if (execT.kind !== "func" || execT.rest || execT.params.length > 2) return null; + if (!execT.params.some((p) => p.kind === "dyn")) return null; + const resolveT = funcOf(inner.kind === "void" ? [] : [inner], VOID); + const rejectT = funcOf([{ kind: "object", className: "%Error" }], VOID); + const native = [resolveT, rejectT]; + if ( + !execT.params.every((p, i) => p.kind === "dyn" || typeKey(p) === typeKey(native[i]!)) || + !native.slice(0, execT.params.length).every((t) => lowerer.dynConvertible(t)) + ) + return null; + const adaptT = funcOf(native, VOID); + const key = `promise.executor:${typeKey(execT)}:${typeKey(inner)}`; + let name = lowerer.arrHofHelpers.get(key); + if (name === undefined) { + name = `%promise.executor.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(key, name); + const impl = `${name}.impl`; + const captured: IrLocal = { id: "f.0", name: "f", type: execT, mutable: false, boxed: true }; + const params: IrParam[] = [ + { localId: "resolve.0", name: "resolve", type: resolveT }, + { localId: "reject.0", name: "reject", type: rejectT }, + ]; + const ctx = newFnCtx(false, null, null, VOID); + lowerer.fnStack.push(ctx); + let args: IrExpr[]; + try { + args = execT.params.map((p, i) => + lowerer.coerceToExpected(varRef(params[i]!.localId, native[i]!, loc), p), + ); + } finally { + lowerer.fnStack.pop(); + } + lowerer.liftedFns.push({ + name: impl, + params, + returnType: VOID, + captures: [{ localId: captured.id, name: captured.name, type: execT }], + locals: [ + captured, + ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + ...ctx.locals, + ], + body: [ + { + kind: "exprStmt", + expr: { + kind: "callValue", + callee: varRef(captured.id, execT, loc), + args, + type: execT.ret, + loc, + }, + loc, + }, + ], + loc, + }); + lowerer.liftedFns.push({ + name, + params: [{ localId: captured.id, name: captured.name, type: execT }], + returnType: adaptT, + locals: [captured], + body: [ + { + kind: "return", + value: { kind: "closure", fnName: impl, captures: [captured.id], type: adaptT, loc }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: name, args: [executor], type: adaptT, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/require-initialization.ts b/packages/compiler/src/frontend/lowering/require-initialization.ts index 2f84e988a..d979423b0 100644 --- a/packages/compiler/src/frontend/lowering/require-initialization.ts +++ b/packages/compiler/src/frontend/lowering/require-initialization.ts @@ -119,7 +119,7 @@ export function markRequireEarlyBindings(lowerer: Lowerer): void { const tag = rawTag === "" ? "e." : rawTag.replace(/^%/, ""); const bySymbol = new Map(); for (const { decl } of declared) { - const flagId = `%g.${tag}%required.${decl.getStart(sf)}`; + const flagId = `%g.${tag}%required.${ts.tokenStartOfNode(decl)}`; lowerer.globalsList.push({ id: flagId, name: "%initialized", type: BOOL, mutable: true }); const flag: RequireInitFlag = { flagId, names: new Map() }; for (const id of boundNames(decl.name)) { @@ -148,7 +148,7 @@ export function markRequireEarlyBindings(lowerer: Lowerer): void { if (owner === undefined) continue; if (boundNames(owner.decl.name).includes(id)) continue; // Code after the require runs after it, except inside functions. - if (id.getStart(sf) >= owner.decl.getEnd() && !inDeferredPosition(id)) continue; + if (ts.tokenStartOfNode(id) >= owner.decl.end && !inDeferredPosition(id)) continue; const body = enclosingBody(id); lowerer.requireEarlyReferences.push({ flag: owner.flag, diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts b/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts index d3acaff6b..f332d0f51 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts @@ -287,7 +287,7 @@ function assertsSlot(node: ts.Node, operands: Operands): boolean { return; } if ((n.flags & ts.NodeFlags.OptionalChain) !== 0) return; - n.forEachChild(visit); + ts.forEachChild(n, visit); }; if (ts.isVariableStatement(node)) { for (const declaration of node.declarationList.declarations.nodes) @@ -323,7 +323,7 @@ function unchanged(nodes: readonly ts.Node[], { array, index }: Operands): boole let hit = false; const walk = (n: ts.Node): void => { if (ts.isIdentifier(n) && roots.includes(n.text)) hit = true; - n.forEachChild(walk); + ts.forEachChild(n, walk); }; walk(e); return hit; @@ -372,7 +372,7 @@ function unchanged(nodes: readonly ts.Node[], { array, index }: Operands): boole ok = false; return; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; for (const node of nodes) visit(node); return ok; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index 2fa8785c9..5ed011ce1 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -611,7 +611,7 @@ export class ArrayElementStates { if (this.host.markIterationBinding(name, known)) changed = true; return; } - name.forEachChild(mark); + ts.forEachChild(name, mark); }; if (ts.isVariableDeclarationList(initializer)) { for (const declaration of initializer.declarations.nodes) mark(declaration.name); diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts b/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts index 768932ad3..3e4605f94 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts @@ -21,7 +21,7 @@ export function reassignedBindings( else if (ts.isArrayLiteralExpression(e) || ts.isObjectLiteralExpression(e)) { const walk = (n: ts.Node): void => { if (ts.isIdentifier(n)) mark(n); - else n.forEachChild(walk); + else ts.forEachChild(n, walk); }; walk(e); } diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts b/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts index adecfa5a9..dd1dce04d 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts @@ -378,9 +378,9 @@ function usesArguments(fn: ts.FunctionExpression): boolean { if (found) return; if (ts.isIdentifier(node) && node.text === "arguments") found = true; else if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node)) - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - fn.body?.forEachChild(visit); + if (fn.body !== undefined) ts.forEachChild(fn.body, visit); return found; } diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts b/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts index f150e314b..3363f197d 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts @@ -33,8 +33,8 @@ export class RuntimeOptionalProvenance { function describe(node: ts.Node | undefined): string { if (!node) return "?"; - const sf = node.getSourceFile(); - const start = node.getStart(); + const sf = ts.getSourceFileOfNode(node); + const start = ts.tokenStartOfNode(node); const pos = ts.getLineAndCharacterOfPosition(sf, start); const text = sf.text.slice(start, node.end).replace(/\s+/g, " "); const short = text.length > 60 ? `${text.slice(0, 57)}...` : text; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts b/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts index f95694624..50b508a52 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts @@ -21,7 +21,7 @@ function classes(source: string): { const symbolFor = (name: string): ts.Symbol => { let symbol = symbols.get(name); if (!symbol) { - symbol = { name, declarations: [] } as unknown as ts.Symbol; + symbol = { escapedName: name, declarations: [] } as unknown as ts.Symbol; symbols.set(name, symbol); } return symbol; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-records.ts b/packages/compiler/src/frontend/lowering/runtime-optional-records.ts index bad2523c8..18e136d69 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-records.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-records.ts @@ -351,7 +351,7 @@ export class RecordFlowClasses { private referencesOf(symbol: ts.Symbol): readonly ts.Identifier[] { let list = this.references.get(symbol); if (!list) { - list = (this.names.get(symbol.name) ?? []).filter( + list = (this.names.get(ts.symbolName(symbol)) ?? []).filter( (node) => this.host.symbolOf(node) === symbol, ); this.references.set(symbol, list); @@ -412,7 +412,7 @@ export class RecordFlowClasses { const symbol = ts.isIdentifier(base) ? this.host.symbolOf(base) : null; const target = symbol ? this.host.declarationOf(symbol) : undefined; if (!target || !ts.isClassDeclaration(target)) return true; - const file = target.getSourceFile(); + const file = ts.getSourceFileOfNode(target); if (file.isDeclarationFile || isJsFile(file)) return true; current = target; } @@ -591,7 +591,7 @@ export class RecordFlowClasses { } else { this.markOpen(slot); } - if (this.shorthandNames.has(symbol.name)) this.markOpen(slot); + if (this.shorthandNames.has(ts.symbolName(symbol))) this.markOpen(slot); for (const reference of this.referencesOf(symbol)) { if (declaration && (declaration as { name?: ts.Node }).name === reference) continue; if (!this.use(reference, slot)) this.markOpen(slot); @@ -824,9 +824,9 @@ export function returnExpressions(fn: ts.FunctionLikeDeclaration): ts.Expression } if (ts.isFunctionLike(node) || ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - fn.body?.forEachChild(visit); + if (fn.body !== undefined) ts.forEachChild(fn.body, visit); return out; } @@ -865,9 +865,9 @@ function usesArguments(body: ts.Node): boolean { if (found) return; if (ts.isIdentifier(node) && node.text === "arguments") found = true; else if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node)) - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return found; } diff --git a/packages/compiler/src/frontend/lowering/split-consumers.ts b/packages/compiler/src/frontend/lowering/split-consumers.ts index 9624ac0aa..8195d2792 100644 --- a/packages/compiler/src/frontend/lowering/split-consumers.ts +++ b/packages/compiler/src/frontend/lowering/split-consumers.ts @@ -45,7 +45,7 @@ export function isPrivateSplitConsumer( ) return false; - for (const use of identifiersNamed(lowerer, decl.getSourceFile(), name.text)) { + for (const use of identifiersNamed(lowerer, ts.getSourceFileOfNode(decl), name.text)) { if (use === decl.name || use === name) continue; if (lowerer.resolveValueSymbol(use) === symbol) return false; } diff --git a/packages/compiler/src/frontend/lowering/static-function-names.ts b/packages/compiler/src/frontend/lowering/static-function-names.ts index d86e55a80..8868d6745 100644 --- a/packages/compiler/src/frontend/lowering/static-function-names.ts +++ b/packages/compiler/src/frontend/lowering/static-function-names.ts @@ -21,7 +21,7 @@ export function nameReadParams(lowerer: Lowerer, info: GenericFnInfo): Map(); // JavaScript generics read function names through the checked-value // box, which carries the runtime name already. - if (isJsSourceFile(info.decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { info.nameReadParams = out; return out; } diff --git a/packages/compiler/src/frontend/lowering/static-this.ts b/packages/compiler/src/frontend/lowering/static-this.ts index 004a72375..5c26ef0c9 100644 --- a/packages/compiler/src/frontend/lowering/static-this.ts +++ b/packages/compiler/src/frontend/lowering/static-this.ts @@ -31,10 +31,10 @@ export function rejectStaticThis( const keyword = node.kind === ts.SyntaxKind.ThisKeyword ? "this" : "super"; lowerer.unsupported("SC1090", node, message(keyword)); } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; if (includeRoot) visit(root); - else root.forEachChild(visit); + else ts.forEachChild(root, visit); } export function hasStaticThis(root: ts.Node): boolean { @@ -47,9 +47,9 @@ export function hasStaticThis(root: ts.Node): boolean { ts.isClassExpression(node) ) return; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - root.forEachChild(visit); + ts.forEachChild(root, visit); return found; } @@ -60,8 +60,8 @@ export function hasLexicalPrivateReference(root: ts.Node): boolean { const visit = (node: ts.Node): void => { if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; if (ts.isPrivateIdentifier(node)) found = true; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - root.forEachChild(visit); + ts.forEachChild(root, visit); return found; } diff --git a/packages/compiler/src/frontend/lowering/string-index-bounds.ts b/packages/compiler/src/frontend/lowering/string-index-bounds.ts index acb41514f..6e422b231 100644 --- a/packages/compiler/src/frontend/lowering/string-index-bounds.ts +++ b/packages/compiler/src/frontend/lowering/string-index-bounds.ts @@ -73,7 +73,7 @@ function isBoundary(node: ts.Node): boolean { function containerOf(node: ts.Node): ts.Node { let n = node.parent; while (n && !isBoundary(n)) n = n.parent; - return n ?? node.getSourceFile(); + return n ?? ts.getSourceFileOfNode(node); } function isLoop(node: ts.Node): boolean { @@ -130,7 +130,7 @@ function writeOf(id: ts.Identifier): Write | null { const k = p.operatorToken.kind; if (p.left !== n || k < ts.SyntaxKind.FirstAssignment || k > ts.SyntaxKind.LastAssignment) return null; - const at = id.getStart(); + const at = ts.tokenStartOfNode(id); if (destructured) return { at, step: null, value: null }; if (k === ts.SyntaxKind.EqualsToken) return { at, step: null, value: p.right }; const amount = integerLiteral(p.right); @@ -146,15 +146,15 @@ function writeOf(id: ts.Identifier): Write | null { (p.operator !== ts.SyntaxKind.PlusPlusToken && p.operator !== ts.SyntaxKind.MinusMinusToken) ) return null; - if (destructured) return { at: id.getStart(), step: null, value: null }; + if (destructured) return { at: ts.tokenStartOfNode(id), step: null, value: null }; return { - at: id.getStart(), + at: ts.tokenStartOfNode(id), step: p.operator === ts.SyntaxKind.PlusPlusToken ? 1 : -1, value: null, }; } if (ts.isForOfStatement(p) || ts.isForInStatement(p)) - return p.initializer === n ? { at: id.getStart(), step: null, value: null } : null; + return p.initializer === n ? { at: ts.tokenStartOfNode(id), step: null, value: null } : null; if (ts.isParenthesizedExpression(p)) { n = p; continue; @@ -199,7 +199,7 @@ export class StringIndexBounds { private prove(read: ts.ElementAccessExpression): boolean { // JavaScript declarations are not checked against callers. - if (read.questionDotToken || isJsSourceFile(read.getSourceFile())) return false; + if (read.questionDotToken || isJsSourceFile(ts.getSourceFileOfNode(read))) return false; const textNode = read.expression; const indexNode = read.argumentExpression; if (!ts.isIdentifier(textNode) || !ts.isIdentifier(indexNode)) return false; @@ -268,7 +268,7 @@ export class StringIndexBounds { if (!(list.flags & ts.NodeFlags.Const)) return null; } else if (declaration.initializer) { writes.push({ - at: declaration.name.getStart(), + at: ts.tokenStartOfNode(declaration.name), step: null, value: declaration.initializer, }); @@ -280,7 +280,7 @@ export class StringIndexBounds { const container = containerOf(declaration); let escapes = false; let assigned = 0; - for (const id of this.namesIn(container).get(symbol.name) ?? []) { + for (const id of this.namesIn(container).get(ts.symbolName(symbol)) ?? []) { if (id === nameNode) continue; const write = writeOf(id); if (!write) continue; diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index be2207bcd..a884b39e1 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -3312,7 +3312,7 @@ export function isChildSurfaceMember( const decl = sym ? sym.declarations[0] : undefined; const iface = decl?.parent; if (!decl || !iface || !ts.isInterfaceDeclaration(iface)) return false; - if (lowerer.isStdlibFile(decl.getSourceFile())) return false; + if (lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) return false; return lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(iface.name))?.kind === "child"; } @@ -3322,7 +3322,9 @@ export function isChildSurfaceMember( * with lib interfaces, so a member can carry declarations from both. A * user's own declaration is in neither, so shadowing never matches. */ export function isStdlibSymbol(lowerer: Lowerer, symbol: ts.Symbol | undefined): boolean { - return !!symbol && symbol.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile())); + return ( + !!symbol && symbol.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) + ); } /** The canonical stdlib-global name `expr` denotes, or null. Three @@ -3380,20 +3382,20 @@ export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): strin if ( decl !== undefined && (ts.isVariableDeclaration(decl) || ts.isBindingElement(decl)) && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) { const imported = lowerer.builtinImportOf(expr); if (imported?.module === "buffer" && imported.member === "Buffer") return "Buffer"; } return null; } - return resolved.name === "global" ? "globalThis" : resolved.name; + return ts.symbolName(resolved) === "global" ? "globalThis" : ts.symbolName(resolved); } if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) { if (stdlibGlobalNameOf(lowerer, expr.expression) !== "globalThis") return null; const symbol = lowerer.checker.getSymbolAtLocation(expr.name); if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; - return symbol.name === "global" ? "globalThis" : symbol.name; + return ts.symbolName(symbol) === "global" ? "globalThis" : ts.symbolName(symbol); } return null; } @@ -3527,7 +3529,7 @@ export function nodeTypesOnlySymbol(lowerer: Lowerer, sym: ts.Symbol | null | un if (!decls || decls.length === 0) return false; let viaNode = false; for (const d of decls) { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); if (sf.isDeclarationFile && isNodeTypesPath(sf.fileName)) viaNode = true; else if (lowerer.isStdlibFile(sf)) return false; } diff --git a/packages/compiler/src/frontend/lowering/symbol-fields.ts b/packages/compiler/src/frontend/lowering/symbol-fields.ts index 0791fc6cd..56718615e 100644 --- a/packages/compiler/src/frontend/lowering/symbol-fields.ts +++ b/packages/compiler/src/frontend/lowering/symbol-fields.ts @@ -118,13 +118,13 @@ export function classSymbolKeyOfSymbol(lowerer: Lowerer, sym: ts.Symbol): ClassS const init = decl.initializer; if (init && (ts.isIdentifier(init) || ts.isPropertyAccessExpression(init))) { const target = classSymbolKeyOf(lowerer, init); - if (!target || bindingWritten(lowerer, sym, decl.getSourceFile())) return null; + if (!target || bindingWritten(lowerer, sym, ts.getSourceFileOfNode(decl))) return null; const preceding = list.declarations.nodes.slice(0, list.declarations.nodes.indexOf(decl)); if ( bindingEarlyUse7( lowerer.program, - decl.getSourceFile(), - decl.getSourceFile().statements.nodes.indexOf(list.parent), + ts.getSourceFileOfNode(decl), + ts.getSourceFileOfNode(decl).statements.nodes.indexOf(list.parent), decl, preceding, ) !== null @@ -139,7 +139,7 @@ export function classSymbolKeyOfSymbol(lowerer: Lowerer, sym: ts.Symbol): ClassS const { registered, description } = call; // Ordinary Symbol() is a fresh identity per evaluation, so only the // module-scope declaration above can name one fixed layout slot. - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (bindingWritten(lowerer, sym, sf)) return null; const preceding = list.declarations.nodes.slice(0, list.declarations.nodes.indexOf(decl)); // Creating a function value does not execute its body. The general @@ -268,7 +268,7 @@ function cjsTableSymbolKeyOf( ) return null; const stmt = assign.parent; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); if (cjsExportDiscardReason(stmt) !== null) return null; const call = literalSymbolCallOf(lowerer, decl.initializer); if (!call) return null; @@ -330,7 +330,7 @@ function tablePropertyWritten( if (node.name.text !== name) return false; const recv = node.expression; if ( - node.getSourceFile() === home && + ts.getSourceFileOfNode(node) === home && ((ts.isIdentifier(recv) && recv.text === "exports") || isModuleExportsAccess(recv)) ) return true; @@ -381,7 +381,7 @@ function bindingWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): bo // Assignment to obj[key] changes the property, never the key's // binding. The same applies to these leaves in destructuring. if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) return "skip"; - if (!ts.isIdentifier(node) || node.text !== sym.name) return; + if (!ts.isIdentifier(node) || node.text !== ts.symbolName(sym)) return; const targetSym = ts.isShorthandPropertyAssignment(node.parent) ? lowerer.checker.getShorthandAssignmentValueSymbol(node.parent) : lowerer.checker.getSymbolAtLocation(node); diff --git a/packages/compiler/src/frontend/lowering/typed-arguments.ts b/packages/compiler/src/frontend/lowering/typed-arguments.ts index b52a3e059..2fbd83482 100644 --- a/packages/compiler/src/frontend/lowering/typed-arguments.ts +++ b/packages/compiler/src/frontend/lowering/typed-arguments.ts @@ -71,7 +71,7 @@ export function typedArgumentsCandidate(node: ts.Node): node is ts.FunctionExpre return ( ts.isFunctionExpression(node) && node.asteriskToken === undefined && - !isJsSourceFile(node.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(node)) && node.parameters.nodes.every(isThisParameter) && bodyReadsArguments(node) ); @@ -243,7 +243,7 @@ export function onlyForwarded( lowerer.checker.getNonNullableType(lowerer.typeOf(p.parent.expression)), ); const decl = sigs.length === 1 ? sigs[0]!.declaration : undefined; - if (decl && !decl.getSourceFile().isDeclarationFile) return undefined; + if (decl && !ts.getSourceFileOfNode(decl).isDeclarationFile) return undefined; } if ( ts.isVariableDeclaration(p) && @@ -317,7 +317,7 @@ export function forwardedTupleType(lowerer: Lowerer, node: ts.Expression): IrTyp ts.isParameter(decl) && decl.dotDotDotToken !== undefined && ts.isIdentifier(decl.name) && - !isJsSourceFile(decl.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && // Checker-only probe first: an ordinary array rest never maps here. restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx) !== null ) { diff --git a/packages/compiler/src/frontend/lowering/var-hoisting.ts b/packages/compiler/src/frontend/lowering/var-hoisting.ts index c445f1751..6276ab671 100644 --- a/packages/compiler/src/frontend/lowering/var-hoisting.ts +++ b/packages/compiler/src/frontend/lowering/var-hoisting.ts @@ -97,7 +97,7 @@ function hoistingScopeOf(node: ts.Node): Scope { ) return p; } - return node.getSourceFile(); + return ts.getSourceFileOfNode(node); } function statementsOf(scope: Scope): readonly ts.Statement[] { @@ -221,7 +221,7 @@ class ScopeAnalysis { * the statement at index `settle`. `read`: `ref` reads the binding * itself, so tsc vouches for one in the scope's own code. */ settles(scope: Scope, settle: number, ref: ts.Identifier, read = true): boolean { - if (ref.getSourceFile() !== this.sf) return true; + if (ts.getSourceFileOfNode(ref) !== this.sf) return true; const statements = statementsOf(scope); let deferred: ts.Node | null = null; let top: ts.Node | null = null; diff --git a/packages/compiler/src/frontend/module-source-candidates.test.ts b/packages/compiler/src/frontend/module-source-candidates.test.ts index 4ff20decc..8fccef000 100644 --- a/packages/compiler/src/frontend/module-source-candidates.test.ts +++ b/packages/compiler/src/frontend/module-source-candidates.test.ts @@ -6,6 +6,7 @@ import * as ast from "./ts7/ast.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { Ts7Host } from "./ts7/program-host.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; +import { getTextOfNode } from "./ts7/node-functions.js"; test("module candidates preserve nested call order and belong to one program snapshot", () => { const directory = mkdtempSync(join(tmpdir(), "scriptc-module-candidates-")); @@ -24,7 +25,7 @@ test("module candidates preserve nested call order and belong to one program sna const source = first.getSourceFile(entry)!; const candidates = moduleSourceCandidates(first, source); expect(candidates.specifiers).toEqual(["first", "second", "third", "fourth"]); - expect(candidates.calls.map((call) => call.expression.getText())).toEqual([ + expect(candidates.calls.map((call) => getTextOfNode(call.expression))).toEqual([ "outer", "require", "import", diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts index c61f4a439..b625c456d 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts @@ -24,6 +24,7 @@ import * as ts from "./ts7/syntax.js"; import { helperTokens } from "./helper-tokens.js"; import { canonicalBuiltinModule } from "./builtin-modules.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; export interface BundledChunkSplit { /** The bundle with chunk declarations blanked and calls respelled. */ @@ -366,16 +367,16 @@ function helperForm( const ret = stmts[0]; if (stmts.length !== 1 || ret === undefined || !ts.isReturnStatement(ret) || !ret.expression) return null; - const actual = helperTokens(ret.expression.getText(sf)); + const actual = helperTokens(getTextOfNode(ret.expression)); if (actual === null) return null; // The own-key reader: esbuild's alias or the 0.11 spelling. - const keysRef = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(ret.expression.getText(sf)); + const keysRef = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(getTextOfNode(ret.expression)); const keys = keysRef?.[1]; if (keys === undefined || (keys !== "Object.keys" && !ownNames(keys))) return null; return actual === template(KEYED_RETURN, cb, mod, keys) ? "keyed" : null; } if (ts.isArrowFunction(body) && body.parameters.nodes.length === 0 && !ts.isBlock(body.body)) { - const actual = helperTokens(body.body.getText(sf)); + const actual = helperTokens(getTextOfNode(body.body)); return actual !== null && actual === template(ARROW_BODY, cb, mod, "") ? "arrow" : null; } return null; @@ -539,7 +540,7 @@ export function splitBundledChunks( const init = t.node.initializer; return ( init !== undefined && - helperTokens(init.getText(sf)) === helperTokens("Object.getOwnPropertyNames") + helperTokens(getTextOfNode(init)) === helperTokens("Object.getOwnPropertyNames") ); }; if (helperNames.length === 0) { @@ -640,8 +641,8 @@ export function splitBundledChunks( }; const forwarded = forwarderSpec(chunk); callEdits.push({ - start: call.getStart(sf), - end: call.getEnd(), + start: tokenStartOfNode(call), + end: call.end, text: `require(${JSON.stringify(forwarded ?? chunk.spec)})`, }); } @@ -656,7 +657,7 @@ export function splitBundledChunks( for (const chunk of chunks.values()) { if (forwarderSpec(chunk) !== null) { // A chunk that only re-exports a builtin module is that module. - bundleEdits.push({ start: chunk.stmt.getStart(sf), end: chunk.stmt.getEnd(), text: "" }); + bundleEdits.push({ start: tokenStartOfNode(chunk.stmt), end: chunk.stmt.end, text: "" }); continue; } const result = chunkSource( @@ -672,7 +673,7 @@ export function splitBundledChunks( ); if (typeof result !== "string") return result; out.push({ path: chunk.path, source: result }); - bundleEdits.push({ start: chunk.stmt.getStart(sf), end: chunk.stmt.getEnd(), text: "" }); + bundleEdits.push({ start: tokenStartOfNode(chunk.stmt), end: chunk.stmt.end, text: "" }); } return { text: applyEdits(source, 0, source.length, bundleEdits), chunks: out }; } @@ -848,8 +849,8 @@ function chunkSource( const p = r.parent; edits.push( ts.isShorthandPropertyAssignment(p) - ? { start: r.getStart(sf), end: r.getEnd(), text: `${name}: ${global}` } - : { start: r.getStart(sf), end: r.getEnd(), text: global }, + ? { start: tokenStartOfNode(r), end: r.end, text: `${name}: ${global}` } + : { start: tokenStartOfNode(r), end: r.end, text: global }, ); } continue; @@ -893,15 +894,17 @@ function chunkSource( } // Declaration order of the bundle: a helper reads only earlier ones. const ordered = [...copied].sort( - (a, b) => top.get(a)!.node.getStart(sf) - top.get(b)!.node.getStart(sf), + (a, b) => tokenStartOfNode(top.get(a)!.node) - tokenStartOfNode(top.get(b)!.node), ); for (const name of ordered) { const node = top.get(name)!.node; - header.push(ts.isFunctionDeclaration(node) ? node.getText(sf) : `var ${node.getText(sf)};`); + header.push( + ts.isFunctionDeclaration(node) ? getTextOfNode(node) : `var ${getTextOfNode(node)};`, + ); } const bodyStrict = strictPrologue(body.statements.nodes); - const text = applyEdits(sf.text, body.getStart(sf) + 1, body.getEnd() - 1, edits); + const text = applyEdits(sf.text, tokenStartOfNode(body) + 1, body.end - 1, edits); const prologue = bundleStrict || bodyStrict ? '"use strict";\n' : ""; return prologue + (header.length > 0 ? header.join("\n") + "\n" : "") + text + "\n"; } diff --git a/packages/compiler/src/frontend/npm-static-bundled-cjs.ts b/packages/compiler/src/frontend/npm-static-bundled-cjs.ts index 89cd0fda1..be43284a6 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-cjs.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-cjs.ts @@ -1,5 +1,6 @@ import * as ts from "./ts7/syntax.js"; import { helperTokens } from "./helper-tokens.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; // Bun's cached node-mode interop helper. Match the complete token stream, // including its dependencies: helper names alone are not a semantic contract. @@ -117,14 +118,14 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) } for (const [name, shape] of helperShapes) { const actual = declarations.get(name); - if (!actual || helperTokens(actual.stmt.getText(sf)) !== shape) return null; + if (!actual || helperTokens(getTextOfNode(actual.stmt)) !== shape) return null; } // These initializers must execute before any factory or namespace. The // original bundle would throw if a var helper were still undefined. let helperEnd = 0; for (const name of helperShapes.keys()) { const stmt = declarations.get(name)!.stmt; - if (stmt.getStart(sf) < helperEnd) return null; + if (tokenStartOfNode(stmt) < helperEnd) return null; helperEnd = stmt.end; } const edits: Edit[] = []; @@ -140,7 +141,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) const call = decl.initializer; if (!ts.isIdentifier(call.expression) || call.expression.text !== "__commonJS") continue; if ( - stmt.getStart(sf) < helperEnd || + tokenStartOfNode(stmt) < helperEnd || (ts.canHaveModifiers(stmt) && ts.getModifiers(stmt)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) ) @@ -193,7 +194,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) if (unsafe) return null; // The comma expression prevents inferred naming of an anonymous export. edits.push( - { start: call.getStart(sf), end: value.getStart(sf), text: "(0," }, + { start: tokenStartOfNode(call), end: tokenStartOfNode(value), text: "(0," }, { start: value.end, end: call.end, text: ")" }, ); factories.set(name, stmt); @@ -212,7 +213,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) const call = decl.initializer; if (!ts.isIdentifier(call.expression) || call.expression.text !== "__toESM") continue; if ( - stmt.getStart(sf) < helperEnd || + tokenStartOfNode(stmt) < helperEnd || (ts.canHaveModifiers(stmt) && ts.getModifiers(stmt)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) ) @@ -230,9 +231,9 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) ) return null; const factory = factories.get(requireCall.expression.text); - if (!factory || factory.end > stmt.getStart(sf)) return null; + if (!factory || factory.end > tokenStartOfNode(stmt)) return null; edits.push({ - start: call.getStart(sf), + start: tokenStartOfNode(call), end: call.end, text: `{default:${requireCall.expression.text}}`, }); @@ -286,7 +287,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) // Remove only helpers that became dead. Other bundle features may share // __defProp, so a recognized declaration alone does not justify erasure. const dead = (n: ts.Node): boolean => - edits.some((e) => n.getStart(sf) >= e.start && n.end <= e.end); + edits.some((e) => tokenStartOfNode(n) >= e.start && n.end <= e.end); const removed = new Set(); for (let changed = true; changed;) { changed = false; @@ -304,7 +305,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) live = true; }); if (!live) { - edits.push({ start: stmt.getStart(sf), end: stmt.end, text: "" }); + edits.push({ start: tokenStartOfNode(stmt), end: stmt.end, text: "" }); removed.add(name); changed = true; } diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts index 166676b3a..ac572000d 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -40,6 +40,7 @@ import { unparen, type Edit, } from "./npm-static-bundled-chunks.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; export interface EsmInitHost { requireTargetEsModuleStamped(fromFile: string, specifier: string): boolean; @@ -92,13 +93,13 @@ function esmHelperForm( const stmts = body.body.statements.nodes; const ret = stmts[0]; if (stmts.length !== 1 || !ret || !ts.isReturnStatement(ret) || !ret.expression) return null; - const text = ret.expression.getText(sf); + const text = getTextOfNode(ret.expression); const keys = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(text)?.[1]; if (keys === undefined || (keys !== "Object.keys" && !ownNames(keys))) return null; return helperTokens(text) === template(KEYED_RETURN, f, r, keys) ? "keyed" : null; } if (ts.isArrowFunction(body) && body.parameters.nodes.length === 0 && !ts.isBlock(body.body)) { - const actual = helperTokens(body.body.getText(sf)); + const actual = helperTokens(getTextOfNode(body.body)); return actual !== null && actual === template(ARROW_BODY, f, r, "") ? "arrow" : null; } return null; @@ -117,7 +118,7 @@ function removeDeclarators(sf: ts.SourceFile, dead: ReadonlySet dead.has(d)); if (deadAt.every((x) => x)) { - edits.push({ start: stmt.getStart(sf), end: stmt.getEnd(), text: "" }); + edits.push({ start: tokenStartOfNode(stmt), end: stmt.end, text: "" }); continue; } const lastLive = deadAt.lastIndexOf(false); @@ -125,8 +126,8 @@ function removeDeclarators(sf: ts.SourceFile, dead: ReadonlySet(); @@ -423,7 +424,7 @@ export function lowerBundledEsmInits( } const spec = bareRequireSpecOf(rhs); if (spec === null) continue; - const req = rhs.getText(sf); + const req = getTextOfNode(rhs); const defaultIsModule = interop && (isNodeMode || !host.requireTargetEsModuleStamped(filePath, spec)); // Every other reference must be a plain member read. @@ -442,11 +443,11 @@ export function lowerBundledEsmInits( break; } if (defaultIsModule && access.name.text === "default") - uses.push({ start: access.getStart(sf), end: access.getEnd(), text: `(${req})` }); - else uses.push({ start: r.getStart(sf), end: r.getEnd(), text: req }); + uses.push({ start: tokenStartOfNode(access), end: access.end, text: `(${req})` }); + else uses.push({ start: tokenStartOfNode(r), end: r.end, text: req }); } if (!ok) continue; - edits.push({ start: el.getStart(sf), end: el.getEnd(), text: req }, ...uses); + edits.push({ start: tokenStartOfNode(el), end: el.end, text: req }, ...uses); deadDeclarators.add(decl); } } @@ -500,7 +501,7 @@ export function lowerBundledEsmInits( return false; }; const getterText = (value: ts.Expression): string => - applyEdits(source, value.getStart(sf), value.getEnd(), edits); + applyEdits(source, tokenStartOfNode(value), value.end, edits); /** A call through the namespace (`ns.fn()`, `L().fn()`) passes the object * the read came from as `this`; the lowered object differs from Node's * module object (`__esModule`, property attributes), so it must stay @@ -628,23 +629,23 @@ export function lowerBundledEsmInits( // call through it (`L().fn()`) loses its receiver. if (calledWithReceiver(access as ts.Expression) && !receiverHidden(entries, member)) return loaderWhy; - const init = initCall === null ? "" : `${initCall.getText(sf)}, `; + const init = initCall === null ? "" : `${getTextOfNode(initCall)}, `; edits.push({ - start: access.getStart(sf), - end: access.getEnd(), + start: tokenStartOfNode(access), + end: access.end, text: `(${init}${getterText(value)})`, }); } deadDeclarators.add(d); deadDeclarators.add(memoDecl); - loaderSpans.push({ start: d.getStart(sf), end: d.getEnd() }); + loaderSpans.push({ start: tokenStartOfNode(d), end: d.end }); lowered.add(target); } for (const r of allRefs) { const table = tables.get(r.text); if (table === undefined) continue; - if (loaderSpans.some((span) => r.getStart(sf) >= span.start && r.getEnd() <= span.end)) + if (loaderSpans.some((span) => tokenStartOfNode(r) >= span.start && r.end <= span.end)) continue; const p = r.parent; // The table's own declaration and __export call are handled below. @@ -709,7 +710,7 @@ export function lowerBundledEsmInits( const inner = unparen(ns); const init = ts.isBinaryExpression(inner) && inner.operatorToken.kind === ts.SyntaxKind.CommaToken - ? `${unparen(inner.left).getText(sf)}; ` + ? `${getTextOfNode(unparen(inner.left))}; ` : ""; if (shadowed(holder, table.entries)) return { degrade: `its shipped JS ${why}` }; const entries = table.entries @@ -717,8 +718,8 @@ export function lowerBundledEsmInits( .map(([n, value]) => `${JSON.stringify(n)}: () => ${getterText(value)}`) .join(", "); edits.push({ - start: holder.parent.getStart(sf), - end: holder.parent.getEnd(), + start: tokenStartOfNode(holder.parent), + end: holder.parent.end, text: `${init}__export(${mainTarget}, { ${entries} });`, }); lowered.add(r.text); @@ -771,7 +772,7 @@ export function lowerBundledEsmInits( `get ${/^[A-Za-z_$][\w$]*$/.test(n) ? n : JSON.stringify(n)}() { return ${getterText(value)}; }`, ) .join(", "); - edits.push({ start: p.getStart(sf), end: p.getEnd(), text: `{ ${getters} }` }); + edits.push({ start: tokenStartOfNode(p), end: p.end, text: `{ ${getters} }` }); lowered.add(r.text); } // A table nothing reads (its module is never required) goes too: its @@ -781,7 +782,7 @@ export function lowerBundledEsmInits( const live = (refsByName.get(target) ?? []).some((x) => { const p = x.parent; if (p !== undefined && ts.isVariableDeclaration(p) && p.name === x) return false; - return !(x.getStart(sf) >= table.stmt.getStart(sf) && x.getEnd() <= table.stmt.getEnd()); + return !(tokenStartOfNode(x) >= tokenStartOfNode(table.stmt) && x.end <= table.stmt.end); }); if (!live) lowered.add(target); } @@ -797,7 +798,7 @@ export function lowerBundledEsmInits( writes.has(target) ) return { degrade: ESM_SHAPE_DEGRADE }; - edits.push({ start: table.stmt.getStart(sf), end: table.stmt.getEnd(), text: "" }); + edits.push({ start: tokenStartOfNode(table.stmt), end: table.stmt.end, text: "" }); deadDeclarators.add(decl); } // Every removed declarator in one pass: declarators of one statement @@ -810,13 +811,13 @@ export function lowerBundledEsmInits( const { call, body } = init; edits.push( { - start: call.getStart(sf), - end: body.getStart(sf) + 1, + start: tokenStartOfNode(call), + end: tokenStartOfNode(body) + 1, text: "(() => { let __scriptc_done = false; function __scriptc_init() {", }, { - start: body.getEnd() - 1, - end: call.getEnd(), + start: body.end - 1, + end: call.end, text: "} return function () { if (__scriptc_done) return; __scriptc_done = true; __scriptc_init(); }; })()", }, ); diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts index dfe45b862..bf753be8c 100644 --- a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts @@ -29,6 +29,7 @@ import { unparen, type Edit, } from "./npm-static-bundled-chunks.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; function isModuleExports(e: ts.Expression): e is ts.PropertyAccessExpression { const x = unparen(e); @@ -132,7 +133,7 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { continue; } // Without the rebinding, `exports` keeps the replaced object. - if (!whole.rebinds || r.getStart(sf) < whole.stmt.getEnd()) { + if (!whole.rebinds || tokenStartOfNode(r) < whole.stmt.end) { unsafe = true; continue; } @@ -140,8 +141,8 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { const shorthand = r.parent !== undefined && ts.isShorthandPropertyAssignment(r.parent) && r.parent.name === r; edits.push({ - start: r.getStart(sf), - end: r.getEnd(), + start: tokenStartOfNode(r), + end: r.end, text: shorthand ? `exports: ${fnName}` : fnName, }); continue; @@ -168,8 +169,8 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { (ts.isPropertyAccessExpression(outer) || ts.isElementAccessExpression(outer)) && outer.expression === access ) { - if (access.getStart(sf) < whole.stmt.getEnd() && !insideFunction(access)) unsafe = true; - edits.push({ start: access.getStart(sf), end: access.getEnd(), text: fnName }); + if (tokenStartOfNode(access) < whole.stmt.end && !insideFunction(access)) unsafe = true; + edits.push({ start: tokenStartOfNode(access), end: access.end, text: fnName }); } } } @@ -177,7 +178,7 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { if (whole.rebinds) { // `exports = module.exports = W` → `module.exports = W`. const e = unparen(whole.stmt.expression) as ts.BinaryExpression; - edits.push({ start: e.getStart(sf), end: unparen(e.right).getStart(sf), text: "" }); + edits.push({ start: tokenStartOfNode(e), end: tokenStartOfNode(unparen(e.right)), text: "" }); } if (edits.length === 0) return null; return applyEdits(source, 0, source.length, edits); diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts index 1af4ea1ed..1e7489595 100644 --- a/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts @@ -17,6 +17,7 @@ * never reassigned. Other shapes answer null and keep their existing * treatment. */ import * as ts from "./ts7/syntax.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; function unparen(e: ts.Expression): ts.Expression { let x = e; @@ -219,14 +220,14 @@ export function normalizeCjsExportsVar(sf: ts.SourceFile): string | null { (ts.isPropertyAccessExpression(p) || ts.isElementAccessExpression(p)) && p.expression === n ) { - edits.push({ start: n.getStart(sf), end: n.getEnd(), text: "exports" }); + edits.push({ start: tokenStartOfNode(n), end: n.end, text: "exports" }); return undefined; } ok = false; /* used as a value, assigned, or passed along */ return undefined; }); if (!ok || edits.length === 0) return null; - edits.push({ start: alias.stmt.getStart(sf), end: alias.stmt.getEnd(), text: "" }); + edits.push({ start: tokenStartOfNode(alias.stmt), end: alias.stmt.end, text: "" }); edits.sort((a, b) => a.start - b.start); let out = ""; let at = 0; diff --git a/packages/compiler/src/frontend/npm-static-declaration-syntax.ts b/packages/compiler/src/frontend/npm-static-declaration-syntax.ts index 4a84dcfeb..0d713dcff 100644 --- a/packages/compiler/src/frontend/npm-static-declaration-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-declaration-syntax.ts @@ -10,6 +10,7 @@ * too; inferred checked values validate at the ordinary return boundary. */ import * as ts from "./ts7/syntax.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; export interface NpmStaticOverloadParameter { name: string; @@ -56,11 +57,11 @@ export function applyNpmStaticJsDocNamepaths( for (const tag of doc.tags?.nodes ?? []) { const expression = tag.typeExpression; if (expression?.kind !== ts.SyntaxKind.JSDocTypeExpression) continue; - const start = expression.getStart(sourceFile); + const start = tokenStartOfNode(expression); if (source[start] !== "{") continue; let depth = 1; let quote = ""; - for (let i = start + 1; i < tag.getEnd() && depth > 0; i++) { + for (let i = start + 1; i < tag.end && depth > 0; i++) { const char = source[i]!; if (quote !== "") { if (char === "\\") i++; @@ -80,7 +81,7 @@ export function applyNpmStaticJsDocNamepaths( continue; } const name = /^[$A-Z_a-z][$\w]*(?:[.#~][$A-Z_a-z][$\w]*)*/.exec( - source.slice(i, tag.getEnd()), + source.slice(i, tag.end), )?.[0]; if (!name) continue; if (name.includes("~") || name.includes("#")) replacements.set(i, name.length); @@ -171,7 +172,7 @@ export function applyNpmStaticNullableClassFields( hasModifier(member, ts.SyntaxKind.StaticKeyword) ) continue; - const leading = source.slice(member.getFullStart(), member.getStart(sourceFile)); + const leading = source.slice(member.pos, tokenStartOfNode(member)); if (jsDocTypeOf(member) !== undefined || leading.includes("@type")) continue; fields.set(member.name.text, { member, invalid: false }); } @@ -285,7 +286,7 @@ export function applyNpmStaticNullableClassFields( for (const field of fields.values()) { if (field.invalid || field.className === undefined) continue; inserts.push({ - offset: field.member.getStart(sourceFile), + offset: tokenStartOfNode(field.member), text: `/** @type {${field.className} | null} */ `, }); } @@ -358,7 +359,7 @@ export function applyNpmStaticFindReturnWidening( (returnType.typeArguments?.nodes.length ?? 0) !== 0 ) continue; - insertions.push({ offset: returnType.getEnd(), length: " | undefined".length }); + insertions.push({ offset: returnType.end, length: " | undefined".length }); } } if (insertions.length === 0) return null; @@ -428,26 +429,22 @@ function hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean { ); } -function safeTypeText( - node: ts.TypeNode, - sourceFile: ts.SourceFile, - className: string, -): string | null { - if (SAFE_KEYWORD_TYPES.has(node.kind) || ts.isThisTypeNode(node)) return node.getText(sourceFile); +function safeTypeText(node: ts.TypeNode, className: string): string | null { + if (SAFE_KEYWORD_TYPES.has(node.kind) || ts.isThisTypeNode(node)) return getTextOfNode(node); if (ts.isLiteralTypeNode(node) && node.literal.kind === ts.SyntaxKind.NullKeyword) return "null"; if (ts.isParenthesizedTypeNode(node)) { - const inner = safeTypeText(node.type, sourceFile, className); + const inner = safeTypeText(node.type, className); return inner === null ? null : `(${inner})`; } if (ts.isArrayTypeNode(node)) { - const element = safeTypeText(node.elementType, sourceFile, className); + const element = safeTypeText(node.elementType, className); return element === null ? null : `${element}[]`; } if (ts.isTypeOperatorNode(node) && node.operator === ts.SyntaxKind.ReadonlyKeyword) { - return safeTypeText(node.type, sourceFile, className); + return safeTypeText(node.type, className); } if (ts.isUnionTypeNode(node)) { - const arms = node.types.nodes.map((type) => safeTypeText(type, sourceFile, className)); + const arms = node.types.nodes.map((type) => safeTypeText(type, className)); return arms.some((arm) => arm === null) ? null : arms.join(" | "); } if ( @@ -483,7 +480,7 @@ function overloadSignature( ) { return null; } - const returnType = safeTypeText(method.type, sourceFile, className); + const returnType = safeTypeText(method.type, className); if (returnType === null) return null; const parameters: NpmStaticOverloadParameter[] = []; for (const parameter of method.parameters.nodes) { @@ -496,7 +493,7 @@ function overloadSignature( ) { return null; } - const type = safeTypeText(parameter.type, sourceFile, className); + const type = safeTypeText(parameter.type, className); if (type === null) return null; parameters.push({ name: parameter.name.text, @@ -553,13 +550,9 @@ export function parseNpmStaticDeclarationOverloads( return classes; } -function nullableSelfType( - node: ts.TypeNode, - sourceFile: ts.SourceFile, - className: string, -): string | null { +function nullableSelfType(node: ts.TypeNode, className: string): string | null { if (!ts.isUnionTypeNode(node) || node.types.nodes.length !== 2) return null; - const arms = node.types.nodes.map((type) => safeTypeText(type, sourceFile, className)); + const arms = node.types.nodes.map((type) => safeTypeText(type, className)); return arms.includes(className) && arms.includes("null") ? `${className} | null` : null; } @@ -613,8 +606,7 @@ export function parseNpmStaticDeclarationProperties( continue; } const type = - nullableSelfType(member.type, sourceFile, className) ?? - nullableVoidCallbackType(member.type); + nullableSelfType(member.type, className) ?? nullableVoidCallbackType(member.type); if (type !== null) properties.set(member.name.text, type); } if (properties.size > 0) classes.set(className, properties); @@ -1012,13 +1004,10 @@ export function applyNpmStaticDeclarationOverloads( ) { continue; } - const leading = source.slice( - bodyStatement.getFullStart(), - bodyStatement.getStart(sourceFile), - ); + const leading = source.slice(bodyStatement.pos, tokenStartOfNode(bodyStatement)); if (!leading.includes("@type")) { inserts.push({ - offset: bodyStatement.getStart(sourceFile), + offset: tokenStartOfNode(bodyStatement), text: `/** @type {${backing.type}} */ `, }); } @@ -1036,13 +1025,13 @@ export function applyNpmStaticDeclarationOverloads( (parameter) => parameter.optional && !existing.includes(`[${parameter.name}]`), ); if (missingOptional) - inserts.push({ offset: member.getStart(sourceFile), text: `${implementation} ` }); + inserts.push({ offset: tokenStartOfNode(member), text: `${implementation} ` }); else if ( jsDocs.length === 0 && member.parameters.nodes.length === 0 && SCALAR_METHOD_RETURNS.has(signatures[0]!.returnType) ) { - inserts.push({ offset: member.getStart(sourceFile), text: `${implementation} ` }); + inserts.push({ offset: tokenStartOfNode(member), text: `${implementation} ` }); } } continue; @@ -1050,7 +1039,9 @@ export function applyNpmStaticDeclarationOverloads( const implementation = jsDocs.length === 0 ? implementationComment(statement.name.text, member, signatures) : null; if (jsDocs.length === 0 && implementation === null) continue; - const offset = jsDocs[0]?.getStart(sourceFile) ?? member.getStart(sourceFile); + const offset = + (jsDocs[0] === undefined ? undefined : tokenStartOfNode(jsDocs[0])) ?? + tokenStartOfNode(member); const text = `${signatures.map(overloadComment).join(" ")} ${implementation === null ? "" : implementation + " "}`; inserts.push({ offset, text }); } @@ -1158,9 +1149,9 @@ export function applyNpmStaticDeclarationProperties( !callbackInitializer(member.initializer)) ) continue; - const leading = source.slice(member.getFullStart(), member.getStart(sourceFile)); + const leading = source.slice(member.pos, tokenStartOfNode(member)); if (jsDocTypeOf(member) !== undefined || leading.includes("@type")) continue; - inserts.push({ offset: member.getStart(sourceFile), text: `/** @type {${type}} */ ` }); + inserts.push({ offset: tokenStartOfNode(member), text: `/** @type {${type}} */ ` }); } const constructor = statement.members.nodes.find( (member): member is ts.ConstructorDeclaration => @@ -1184,12 +1175,9 @@ export function applyNpmStaticDeclarationProperties( } const type = properties.get(left.name.text); if (type === undefined) continue; - const leading = source.slice( - bodyStatement.getFullStart(), - bodyStatement.getStart(sourceFile), - ); + const leading = source.slice(bodyStatement.pos, tokenStartOfNode(bodyStatement)); if (leading.includes("@type")) continue; - inserts.push({ offset: bodyStatement.getStart(sourceFile), text: `/** @type {${type}} */ ` }); + inserts.push({ offset: tokenStartOfNode(bodyStatement), text: `/** @type {${type}} */ ` }); } } if (inserts.length === 0) return null; diff --git a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts index 348b04817..c48420625 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts @@ -6,6 +6,7 @@ import { isModuleExports, } from "./cjs-syntax.js"; import { rewriteBundledFunctionImports } from "./npm-static-bundled-cjs.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; /** Resolution stays with the compilation host. Syntax never reads disk or * starts a parser implicitly, so native and Node hosts run the same pass. */ @@ -57,7 +58,7 @@ function liveExportTableFirst( }; let sawTable = false; for (const stmt of sf.statements.nodes) { - if (stmt.getStart(sf) >= installAt) return sawTable; + if (tokenStartOfNode(stmt) >= installAt) return sawTable; if (ts.isFunctionDeclaration(stmt) || ts.isEmptyStatement(stmt)) continue; if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression)) continue; if (ts.isVariableStatement(stmt)) { @@ -146,7 +147,7 @@ function chaseValue(e: ts.Expression): { kind: "ident" | "hoist"; text: string } cur.kind === ts.SyntaxKind.FalseKeyword || cur.kind === ts.SyntaxKind.NullKeyword ) { - return { kind: "hoist", text: cur.getText() }; + return { kind: "hoist", text: getTextOfNode(cur) }; } // ident-rooted member chains: this.x / a.b.c / a["k"].d — side-effect // free reads the tail const can evaluate once. @@ -161,7 +162,7 @@ function chaseValue(e: ts.Expression): { kind: "ident" | "hoist"; text: string } ) { walk = walk.expression; } - if (ts.isIdentifier(walk)) return { kind: "hoist", text: cur.getText() }; + if (ts.isIdentifier(walk)) return { kind: "hoist", text: getTextOfNode(cur) }; return null; } @@ -182,7 +183,7 @@ function chaseObjectLiteral(e: ts.Expression): { kind: "hoist"; text: string } | ) return null; } - return { kind: "hoist", text: cur.getText() }; + return { kind: "hoist", text: getTextOfNode(cur) }; } /** The getter body's returned expression for the two descriptor getter @@ -326,7 +327,7 @@ function recognizedToEsmDecl(d: ts.VariableDeclaration): boolean { let to: ts.Expression = expr.arguments.nodes[0]!; while (ts.isParenthesizedExpression(to)) to = to.expression; if (!ts.isConditionalExpression(to)) return false; - const condText = to.condition.getText(); + const condText = getTextOfNode(to.condition); if (!condText.includes(isNodeMode) || !condText.includes("__esModule")) return false; let stamp: ts.Expression = to.whenTrue; while (ts.isParenthesizedExpression(stamp)) stamp = stamp.expression; @@ -443,7 +444,7 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH ) { if (parent.name.text === "default" && defaultIsModule) { if (!readOnlyAccess(parent)) return fail(TO_ESM_ESCAPE_DEGRADE); - pads.push({ start: child.getEnd(), end: parent.getEnd() }); + pads.push({ start: child.end, end: parent.end }); } } else { return fail(TO_ESM_ESCAPE_DEGRADE); @@ -451,8 +452,8 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH // the wrapper itself: callee + '(' down, and everything after the // require argument (`, 1` included) — the bare require survives at its // original offsets. - pads.push({ start: call.getStart(sf), end: arg0!.getStart(sf) }); - pads.push({ start: arg0!.getEnd(), end: call.getEnd() }); + pads.push({ start: tokenStartOfNode(call), end: tokenStartOfNode(arg0!) }); + pads.push({ start: arg0!.end, end: call.end }); } if (bindings.size > 0) { @@ -513,7 +514,7 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH violated = true; return; } - pads.push({ start: n.expression.getEnd(), end: n.getEnd() }); + pads.push({ start: n.expression.end, end: n.end }); } }; ts.walkPreorder(sf, padDefaults); @@ -628,8 +629,8 @@ export function rewriteBundlerCjsSyntax( ts.isIdentifier(rhs.arguments.nodes[0]!) ) { esbuildMainTarget = (rhs.arguments.nodes[0] as ts.Identifier).text; - tablePos = stmt.getStart(sf); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + tablePos = tokenStartOfNode(stmt); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -662,9 +663,9 @@ export function rewriteBundlerCjsSyntax( names, finalRhs: cur, viaBareExports, - pos: stmt.getStart(sf), - start: stmt.getStart(sf), - end: stmt.getEnd(), + pos: tokenStartOfNode(stmt), + start: tokenStartOfNode(stmt), + end: stmt.end, }); continue; // classified later (only when a rewrite actually happens) } @@ -684,7 +685,7 @@ export function rewriteBundlerCjsSyntax( isModuleExports(inner.left) && ts.isObjectLiteralExpression(inner.right) ) { - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -695,7 +696,7 @@ export function rewriteBundlerCjsSyntax( const table = exportCallOf(e); if (table !== null) { exportTables.push(table); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -705,7 +706,7 @@ export function rewriteBundlerCjsSyntax( const spec = bareRequireSpecOf(e.arguments.nodes[0]!); if (spec !== null) { addStarSpec(spec); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -716,7 +717,7 @@ export function rewriteBundlerCjsSyntax( const spec = bareRequireSpecOf(e.arguments.nodes[1]!); if (spec !== null) { addStarSpec(spec); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -725,7 +726,7 @@ export function rewriteBundlerCjsSyntax( const def = definePropertyExportOf(e); if (def !== null) { if (def.esModuleStamp) { - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); // the canonical table re-stamps it (lexer-visibly — merve detects // this defineProperty form, so parity wants a visible entry) continue; @@ -733,7 +734,7 @@ export function rewriteBundlerCjsSyntax( const chased = def.value !== null ? chaseValue(def.value) : null; if (chased !== null) { definePropEntries.push({ name: def.name, value: def.value }); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; } // unchaseable descriptor: LEAVE the statement (its runtime fence @@ -792,7 +793,7 @@ export function rewriteBundlerCjsSyntax( // The table feeding module.exports keeps a getter it cannot chase // as a live accessor rather than a snapshot. if (spec.kind === "any" && value !== null && t.target === esbuildMainTarget) - claim(name, { name, kind: "getter", value: value.getText() }); + claim(name, { name, kind: "getter", value: getTextOfNode(value) }); else claim(name, spec); } } @@ -951,8 +952,8 @@ export function rewriteBundlerCjsSyntax( helperDecls.set( d.name.text, decls.length === 1 - ? { start: stmt.getStart(sf), end: stmt.getEnd() } - : { start: d.getStart(sf), end: d.getEnd() }, + ? { start: tokenStartOfNode(stmt), end: stmt.end } + : { start: tokenStartOfNode(d), end: d.end }, ); } if (anyHelper && decls.length > 1) helperStmts.push(stmt); @@ -966,8 +967,8 @@ export function rewriteBundlerCjsSyntax( if (parent !== undefined && ts.isVariableDeclaration(parent) && parent.name === node) return; const list = refs.get(node.text); - if (list === undefined) refs.set(node.text, [node.getStart(sf)]); - else list.push(node.getStart(sf)); + if (list === undefined) refs.set(node.text, [tokenStartOfNode(node)]); + else list.push(tokenStartOfNode(node)); } }; ts.walkPreorder(sf, record); @@ -999,12 +1000,12 @@ export function rewriteBundlerCjsSyntax( const deadAt = decls.map((d) => { if (!ts.isIdentifier(d.name) || !removed.has(d.name.text)) return false; const span = helperDecls.get(d.name.text)!; - if (span.start !== d.getStart(sf)) return false; + if (span.start !== tokenStartOfNode(d)) return false; declaratorSpans.add(span); return true; }); if (deadAt.every((x) => x)) { - declaratorPads.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + declaratorPads.push({ start: tokenStartOfNode(stmt), end: stmt.end }); continue; } // Dead declarators before the last live one take their trailing @@ -1015,8 +1016,8 @@ export function rewriteBundlerCjsSyntax( const d = decls[i]!; declaratorPads.push( i < lastLive - ? { start: d.getStart(sf), end: decls[i + 1]!.getStart(sf) } - : { start: decls[lastLive]!.getEnd(), end: d.getEnd() }, + ? { start: tokenStartOfNode(d), end: tokenStartOfNode(decls[i + 1]!) } + : { start: decls[lastLive]!.end, end: d.end }, ); } } diff --git a/packages/compiler/src/frontend/npm-static-subtyping.ts b/packages/compiler/src/frontend/npm-static-subtyping.ts index 04c66a559..18d69d4c4 100644 --- a/packages/compiler/src/frontend/npm-static-subtyping.ts +++ b/packages/compiler/src/frontend/npm-static-subtyping.ts @@ -19,8 +19,8 @@ export function isNpmStaticSubclassArgument( symbol.declarations.some( (declaration) => (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration)) && - /\.[cm]?js$/.test(declaration.getSourceFile().fileName) && - npmStaticPackageOfPath(declaration.getSourceFile().fileName) !== null, + /\.[cm]?js$/.test(ts.getSourceFileOfNode(declaration).fileName) && + npmStaticPackageOfPath(ts.getSourceFileOfNode(declaration).fileName) !== null, ) ); }; @@ -50,12 +50,12 @@ export function isNpmStaticSubclassArgument( }; let accepted = false; ts.walkPreorder(source, (node) => { - if (node.end < diagnostic.pos || node.getStart(source) > diagnostic.end) return "skip"; + if (node.end < diagnostic.pos || ts.tokenStartOfNode(node) > diagnostic.end) return "skip"; if (!ts.isCallExpression(node) && !ts.isNewExpression(node)) return undefined; const index = node.arguments?.nodes.findIndex( (argument) => - argument.getStart(source) === diagnostic.pos && argument.end === diagnostic.end, + ts.tokenStartOfNode(argument) === diagnostic.pos && argument.end === diagnostic.end, ) ?? -1; if (index < 0) return undefined; const parameter = checker.getResolvedSignature(node)?.getParameters()[index]; diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index c29f31c8a..2e75542d7 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -1236,7 +1236,7 @@ export function isEsModuleStamp(expr: ts.Expression): boolean { function pureCjsExport7(program: ts.Program, stmt: ts.Statement): boolean { const cjs = cjsExportAssignmentOf(stmt); if (cjs === null) return false; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); const plainMember = (left: ts.Expression): boolean => { if (!ts.isPropertyAccessExpression(left) || left.questionDotToken) return false; // A replacement export can have setters or non-writable function @@ -1291,7 +1291,7 @@ function sourceBinding7(program: ts.Program, ident: ts.Identifier): boolean { return ( symbol !== undefined && symbol.declarations.some( - (decl) => !decl.getSourceFile().isDeclarationFile && !ts.isSourceFile(decl), + (decl) => !ts.getSourceFileOfNode(decl).isDeclarationFile && !ts.isSourceFile(decl), ) ); } @@ -1318,7 +1318,7 @@ function purePrefixStmt7(program: ts.Program, s: ts.Statement): boolean { if (ts.isEmptyStatement(s) || ts.isFunctionDeclaration(s)) return true; if (ts.isExpressionStatement(s) && ts.isStringLiteral(s.expression)) return true; // directive if (isRequireStatement7(s)) return true; - if (isCjsJsFile7(s.getSourceFile(), program)) { + if (isCjsJsFile7(ts.getSourceFileOfNode(s), program)) { if (ts.isExpressionStatement(s) && isEsModuleStamp(s.expression)) { const call = s.expression as ts.CallExpression; const object = (call.expression as ts.PropertyAccessExpression).expression as ts.Identifier; @@ -1445,7 +1445,7 @@ function requireEarlyReference7( if (cached !== undefined) return cached; let found: ts.Identifier | null = null; const stmt = decl.parent?.parent; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (stmt !== undefined && ts.isVariableStatement(stmt) && ts.isSourceFile(stmt.parent)) { const stmts = sf.statements.nodes; const k = stmts.indexOf(stmt); @@ -2021,7 +2021,7 @@ function isCreateRequireImport7(program: ts.Program, ident: ts.Identifier): bool if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { const target = checker.getAliasedSymbol(symbol); const targetDecl = target.declarations[0]; - if (targetDecl !== undefined && targetDecl.getSourceFile().isDeclarationFile) { + if (targetDecl !== undefined && ts.getSourceFileOfNode(targetDecl).isDeclarationFile) { for ( let p: ts.Node | undefined = targetDecl.parent; p !== undefined && !ts.isSourceFile(p); @@ -2029,7 +2029,8 @@ function isCreateRequireImport7(program: ts.Program, ident: ts.Identifier): bool ) { if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) { return ( - canonicalBuiltinModule(p.name.text) === "module" && target.name === "createRequire" + canonicalBuiltinModule(p.name.text) === "module" && + ts.symbolName(target) === "createRequire" ); } } @@ -2113,7 +2114,7 @@ function stableCreateRequireBindingReason7( return "its createRequire binding is redeclared"; if ((ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Export) !== 0) return "its createRequire result escapes through an exported binding"; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); const refs = identifierOccurrences7(sf, decl.name.text); checker.prefetchSymbolNodesExact(refs); for (const ref of refs) { @@ -2180,7 +2181,7 @@ function npmStaticModuleImportReason7( } if (!bindings || !ts.isNamedImports(bindings)) return null; const checker = program.getTypeChecker(); - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); for (const specifier of bindings.elements.nodes) { if ((specifier.propertyName?.text ?? specifier.name.text) !== "createRequire") continue; const imported = checker.getSymbolAtLocation(specifier.name); @@ -2260,7 +2261,7 @@ function requireCallBindingKind7(program: ts.Program, node: ts.Node): RequireCal const symbol = checker.getSymbolAtLocation(call.expression); if (symbol === undefined) return "ambient"; const decls = symbol.declarations; - if (decls.every((d) => d.getSourceFile().isDeclarationFile)) return "ambient"; + if (decls.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) return "ambient"; return isCreateRequireBinding7(program, call.expression) ? "createRequire" : "source"; } @@ -2415,15 +2416,15 @@ function selfImportTdzFences7( // are initialized. Hoisted functions have none. const tdzEndOf = (aliased: ts.Symbol): number | null => { const d = aliased.valueDeclaration ?? aliased.declarations[0]; - if (d === undefined || d.getSourceFile() !== sf) return null; + if (d === undefined || ts.getSourceFileOfNode(d) !== sf) return null; if (ts.isFunctionDeclaration(d)) return null; - return d.getEnd(); + return d.end; }; const fence = (ref: ts.Identifier, name: string): void => { diags.push( unsupportedDiag( "SC1030", - { file: sf.fileName, start: ref.getStart(sf), end: ref.getEnd() }, + { file: sf.fileName, start: ts.tokenStartOfNode(ref), end: ref.end }, `the self-import binding '${name}' read above its declaration (the import aliases this module's OWN binding — Node initializes it when the declaration executes, so this read throws ReferenceError; move the read below the declaration)`, ), ); @@ -2439,7 +2440,7 @@ function selfImportTdzFences7( ts.isIdentifier(node) && node !== bindingName && node.text === bindingName.text && - node.getStart(sf) < end && + ts.tokenStartOfNode(node) < end && !insideFunction(node) && checker.getSymbolAtLocation(node) === sym ) { @@ -2472,7 +2473,7 @@ function selfImportTdzFences7( const memberSym = checker.getSymbolAtLocation(node.name); if (memberSym !== undefined) { const end = tdzEndOf(memberSym); - if (end !== null && node.getStart(sf) < end) + if (end !== null && ts.tokenStartOfNode(node) < end) fence(node.expression, `${nsName.text}.${node.name.text}`); } } @@ -2672,7 +2673,7 @@ function topLevelInertness7(program: ts.Program): (stmt: ts.Statement) => ts.Nod if (sym === undefined) return false; if (sym.flags & ts.SymbolFlags.Alias) sym = checker.getAliasedSymbol(sym); const decls = sym.declarations; - return decls.length > 0 && decls.every((d) => d.getSourceFile().isDeclarationFile); + return decls.length > 0 && decls.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile); }; const hasDecorator = (n: ts.Node): boolean => ((n as { modifiers?: ts.NodeArray }).modifiers?.nodes ?? []).some( @@ -2946,8 +2947,8 @@ export function makeCycleAdmission( } }; const lineOf = (node: ts.Node): string => { - const nsf = node.getSourceFile(); - return `${nsf.fileName}:${ts.getLineAndCharacterOfPosition(nsf, node.getStart(nsf)).line + 1}`; + const nsf = ts.getSourceFileOfNode(node); + return `${nsf.fileName}:${ts.getLineAndCharacterOfPosition(nsf, ts.tokenStartOfNode(node)).line + 1}`; }; // Cluster verdict memo (keyed by the component array identity): true // when every member is an ES module passing the inert-top-level bar. @@ -3454,7 +3455,7 @@ function exactCycleRefusal7( const targetOf = (stmt: ts.ImportDeclaration | ts.ExportDeclaration): ts.SourceFile | null => { const spec = stmt.moduleSpecifier; if (spec === undefined || !ts.isStringLiteral(spec)) return null; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); return resolveImport7(program, sf, spec.text) ?? npmStaticDepSf7(program, sf, spec.text); }; for (const m of comp) { @@ -3688,8 +3689,8 @@ function computeCycleEarlyBindings7( module: m, exposed, rank, - start: stmt.getStart(m), - end: stmt.getEnd(), + start: ts.tokenStartOfNode(stmt), + end: stmt.end, reference: null, folded: null, }); @@ -3734,13 +3735,13 @@ function computeCycleEarlyBindings7( if ( c.kind === "class" && sf === c.module && - ref.getStart(sf) >= c.start && - ref.getEnd() <= c.end + ts.tokenStartOfNode(ref) >= c.start && + ref.end <= c.end ) continue; let early: boolean; if (inDeferredPosition7(ref)) early = c.exposed; - else if (sf === c.module) early = ref.getStart(sf) < c.start; + else if (sf === c.module) early = ts.tokenStartOfNode(ref) < c.start; else if (!memberSet.has(sf) || !rankOf.has(sf)) early = false; else early = !known || rankOf.get(sf)! < c.rank; if (early) c.reference = ref; @@ -3879,7 +3880,7 @@ function moduleEarlyBindingsOf7( store(stmt.name, [stmt]); } else if (ts.isClassDeclaration(stmt) && stmt.name !== undefined) { store(stmt.name, [stmt]); - candidates.push({ name: stmt.name, index, start: stmt.getStart(sf), cls: stmt }); + candidates.push({ name: stmt.name, index, start: ts.tokenStartOfNode(stmt), cls: stmt }); candidateNames.add(stmt.name.text); } else if (ts.isVariableStatement(stmt)) { const lexical = (stmt.declarationList.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0; @@ -3891,7 +3892,7 @@ function moduleEarlyBindingsOf7( } if (!lexical) continue; for (const name of boundNames7(d.name)) { - candidates.push({ name, index, start: stmt.getStart(sf) }); + candidates.push({ name, index, start: ts.tokenStartOfNode(stmt) }); candidateNames.add(name.text); } } @@ -3983,9 +3984,10 @@ function moduleEarlyBindingsOf7( c !== undefined && node !== c.name && !reachedRefs.has(symbol) && - !(c.cls !== undefined && node.getStart(sf) >= c.cls.pos && node.end <= c.cls.end) + !(c.cls !== undefined && ts.tokenStartOfNode(node) >= c.cls.pos && node.end <= c.cls.end) ) { - if (inDeferredPosition7(node) || node.getStart(sf) < c.start) reachedRefs.set(symbol, node); + if (inDeferredPosition7(node) || ts.tokenStartOfNode(node) < c.start) + reachedRefs.set(symbol, node); } for (const unit of unitsBySymbol.get(symbol) ?? []) { if (reachedUnits.has(unit)) continue; @@ -4004,8 +4006,8 @@ function moduleEarlyBindingsOf7( const report = (declaration: ts.Node): void => { while ( next < candidates.length && - candidates[next]!.name.getStart(sf) >= declaration.getStart(sf) && - candidates[next]!.name.getEnd() <= declaration.getEnd() + ts.tokenStartOfNode(candidates[next]!.name) >= ts.tokenStartOfNode(declaration) && + candidates[next]!.name.end <= declaration.end ) { const c = candidates[next++]!; const symbol = checker.getSymbolAtLocation(c.name); @@ -4065,7 +4067,7 @@ function returnsOnlyClosuresOver7( const decls = symbol === undefined ? [] : symbol.declarations; if (decls.length !== 1) return false; const decl = decls[0]!; - if (decl.getSourceFile().isDeclarationFile) return false; + if (ts.getSourceFileOfNode(decl).isDeclarationFile) return false; let fn: ts.Node | undefined; if (ts.isFunctionDeclaration(decl)) fn = decl; else if (ts.isVariableDeclaration(decl) && decl.initializer !== undefined) { @@ -4186,12 +4188,13 @@ function laterJobCallee7(checker: ts.TypeChecker, callee: ts.Expression): boolea } if (symbol === undefined) return false; const decls = symbol.declarations; - if (decls.length === 0 || !decls.every((d) => d.getSourceFile().isDeclarationFile)) return false; + if (decls.length === 0 || !decls.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) + return false; if (LATER_JOB_BUILTINS7.has(name.text)) return true; // The JavaScript standard library's callbacks mostly run synchronously; // package declarations describe user code. return decls.every((d) => { - const file = d.getSourceFile().fileName; + const file = ts.getSourceFileOfNode(d).fileName; return isNodeTypesPath(file) || file === fallbackDtsPath(); }); } @@ -4334,8 +4337,8 @@ function resolveNpmImport7( } function locOf7(node: ts.Node): { file: string; start: number; end: number } { - const sf = node.getSourceFile(); - return { file: sf.fileName, start: node.getStart(sf), end: node.getEnd() }; + const sf = ts.getSourceFileOfNode(node); + return { file: sf.fileName, start: ts.tokenStartOfNode(node), end: node.end }; } /** The program file holding an opted-in package's resolved runtime JS: @@ -4867,7 +4870,7 @@ function preflight7(load: LoadResult): { } if (reDep && !reDep.fileName.endsWith(".json")) { deps.push({ dep: reDep, stmt }); - depPositions.get(sf)!.push({ dep: reDep, pos: stmt.getStart(sf) }); + depPositions.get(sf)!.push({ dep: reDep, pos: ts.tokenStartOfNode(stmt) }); } continue; } @@ -4915,7 +4918,7 @@ function preflight7(load: LoadResult): { refusals.get(sf)!.push({ crash: { message, className, loc: locOf7(stmt) }, fallback: unsupportedDiag("SC1010", locOf7(stmt), fallbackFeature), - pos: stmt.getStart(sf), + pos: ts.tokenStartOfNode(stmt), }); }; // Project modules win before npm fallback: this is the source graph @@ -5128,7 +5131,7 @@ function preflight7(load: LoadResult): { } if (dep && !isJson) { deps.push({ dep, stmt }); - depPositions.get(sf)!.push({ dep, pos: stmt.getStart(sf) }); + depPositions.get(sf)!.push({ dep, pos: ts.tokenStartOfNode(stmt) }); } } { @@ -5168,7 +5171,11 @@ function preflight7(load: LoadResult): { for (let k = 0; k < stmts.length; k++) { const stmt = stmts[k]!; for (const req of requiresOf7(stmt)) { - const loc = { file: sf.fileName, start: req.node.getStart(sf), end: req.node.getEnd() }; + const loc = { + file: sf.fileName, + start: ts.tokenStartOfNode(req.node), + end: req.node.end, + }; const bindingKind = requireCallBindingKind7(program, req.node); // TypeScript source files join this scan only through the // canonical createRequire bridge. Their ordinary require-shaped @@ -5314,7 +5321,7 @@ function preflight7(load: LoadResult): { ); for (const call of nestedBareRequires) { const spec = requireSpecOf7(call)!; - const loc = { file: sf.fileName, start: call.getStart(sf), end: call.getEnd() }; + const loc = { file: sf.fileName, start: ts.tokenStartOfNode(call), end: call.end }; const bindingKind = requireCallBindingKind7(program, call); if (!jsRequireFile && bindingKind !== "createRequire") continue; if (isNodeEsmFile7(sf, program)) { @@ -5730,7 +5737,7 @@ function cjsNamedImportLinkCheck( // swaps ` as ` for `: ` — reproduced exactly, quirks included // (multi-line import statements simply lose the hint). const text = bad.sf.text; - const pos = bad.nameNode.getStart(bad.sf); + const pos = ts.tokenStartOfNode(bad.nameNode); const lineStart = text.lastIndexOf("\n", pos - 1) + 1; const lineEnd = text.indexOf("\n", pos); let line = text.slice(lineStart, lineEnd < 0 ? text.length : lineEnd); @@ -6044,7 +6051,7 @@ function earlierLinkCrash( if (!element.isTypeOnly) { entries.push({ local: element.name.text, - start: (element.propertyName ?? element.name).getStart(sf), + start: ts.tokenStartOfNode(element.propertyName ?? element.name), kind: 0, }); } @@ -6068,7 +6075,7 @@ function earlierLinkCrash( for (const element of exportClause.elements.nodes) { if ( !element.isTypeOnly && - (element.propertyName ?? element.name).getStart(sf) === crash.loc.start + ts.tokenStartOfNode(element.propertyName ?? element.name) === crash.loc.start ) { return [1, reexport]; } @@ -6134,7 +6141,11 @@ export function isJsSourceFile(sf: ts.SourceFile): boolean { export function isCheckedJsSourceFile(sf: ts.SourceFile, projectCheckJs: boolean): boolean { if (!isJsSourceFile(sf) || isNodeModulesPath(sf.fileName)) return false; let checked = projectCheckJs; - const head = sf.text.slice(0, sf.statements.nodes[0]?.getStart(sf) ?? sf.text.length); + const head = sf.text.slice( + 0, + (sf.statements.nodes.length === 0 ? undefined : ts.tokenStartOfNode(sf.statements.nodes[0]!)) ?? + sf.text.length, + ); for (const line of head.split("\n")) { const directive = /^\s*\/\/\s*@ts-(no)?check\b/.exec(line); if (directive) checked = directive[1] === undefined; @@ -6333,8 +6344,8 @@ export function cjsVoidInitPreambleReals( tail = tail.right; } if (!isVoid(tail)) return null; - const sf = cjs.expr.getSourceFile(); - const myPos = cjs.expr.getStart(sf); + const sf = ts.getSourceFileOfNode(cjs.expr); + const myPos = ts.tokenStartOfNode(cjs.expr); const reals = new Map< string, Extract, { kind: "member" }> @@ -6347,7 +6358,7 @@ export function cjsVoidInitPreambleReals( other?.kind === "member" && ts.isIdentifier(other.name) && other.name.text === name && - other.expr.getStart(sf) > myPos && + ts.tokenStartOfNode(other.expr) > myPos && !isVoid(other.value), ); if (real?.kind !== "member") return null; @@ -6505,7 +6516,7 @@ export function isCjsExportTableLiteral(obj: ts.ObjectLiteralExpression): boolea } const decl = obj.parent; if (!ts.isVariableDeclaration(decl) || decl.initializer !== obj) return false; - const sf = obj.getSourceFile(); + const sf = ts.getSourceFileOfNode(obj); if (!isJsSourceFile(sf)) return false; for (const stmt of sf.statements.nodes) { const cjs = cjsExportAssignmentOf(stmt); diff --git a/packages/compiler/src/frontend/services.test.ts b/packages/compiler/src/frontend/services.test.ts index bd172bbc5..e491b0ac5 100644 --- a/packages/compiler/src/frontend/services.test.ts +++ b/packages/compiler/src/frontend/services.test.ts @@ -7,6 +7,7 @@ import { Ts7Api } from "./ts7/rpc-api.js"; import { checkPreflight, isNodeEsmFile, loadProgram } from "./program.js"; import { clearResolveCaches } from "./resolve.js"; import type { Ts7ApiOptions } from "./ts7/program-host.js"; +import { getTextOfNode } from "./ts7/node-functions.js"; const directories: string[] = []; afterEach(() => { @@ -80,7 +81,7 @@ test("syntax sessions reuse one connection and returned ASTs survive edits and c services.close(); services.close(); expect(connections[0]!.close).toHaveBeenCalledTimes(1); - expect(before.statements.nodes[0]!.getText()).toBe(before.text); + expect(getTextOfNode(before.statements.nodes[0]!)).toBe(before.text); }); test("program hosts remain usable after their creating services close", () => { diff --git a/packages/compiler/src/frontend/tier-analysis.ts b/packages/compiler/src/frontend/tier-analysis.ts index 2ed5c8c9b..b75e491a8 100644 --- a/packages/compiler/src/frontend/tier-analysis.ts +++ b/packages/compiler/src/frontend/tier-analysis.ts @@ -107,8 +107,8 @@ function nodeAt(sf: ts.SourceFile, start: number, end: number): ts.Node { let node: ts.Node = sf; for (;;) { let next: ts.Node | undefined; - node.forEachChild((child) => { - if (child.getStart(sf) <= start && end <= child.getEnd()) { + ts.forEachChild(node, (child) => { + if (ts.tokenStartOfNode(child) <= start && end <= child.end) { next = child; return true; } @@ -263,23 +263,23 @@ function functionName(node: ts.Node): string { if (ts.isBinaryExpression(parent) && parent.right === node) { const left = parent.left; if (ts.isIdentifier(left)) return left.text; - if (ts.isPropertyAccessExpression(left)) return left.getText(); + if (ts.isPropertyAccessExpression(left)) return ts.getTextOfNode(left); } } - const sf = node.getSourceFile(); - const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart(sf)); + const sf = ts.getSourceFileOfNode(node); + const pos = ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(node)); return ``; } function locOfNode(node: ts.Node): SrcLoc { - const sf = node.getSourceFile(); - return { file: sf.fileName, start: node.getStart(sf), end: node.getEnd() }; + const sf = ts.getSourceFileOfNode(node); + return { file: sf.fileName, start: ts.tokenStartOfNode(node), end: node.end }; } function enclosingFunction(node: ts.Node): ts.Node { let current: ts.Node | undefined = node.parent; while (current !== undefined && !isFunctionLikeOrFile(current)) current = current.parent; - return current ?? node.getSourceFile(); + return current ?? ts.getSourceFileOfNode(node); } function functionUnit(node: ts.Node, instance: string | null): DemotionUnit { @@ -423,7 +423,10 @@ class UnitResolver { return this.declarationUnit(binding); } if (ts.isVariableDeclaration(decl) || ts.isParameter(decl) || ts.isBindingElement(decl)) { - const name = nameText(decl.name) ?? decl.name?.getText() ?? ""; + const name = + nameText(decl.name) ?? + (decl.name === undefined ? undefined : ts.getTextOfNode(decl.name)) ?? + ""; const scope = enclosingFunction(decl); const unit: DemotionUnit = { kind: "binding", name, loc: locOfNode(decl) }; if (ts.isParameter(decl) && ts.isFunctionLikeDeclaration(decl.parent)) @@ -437,7 +440,7 @@ class UnitResolver { /** The unit an operand expression denotes. */ valueUnit(expr: ts.Node): { unit: DemotionUnit; reflection: boolean } | null { const { root, viaPrototype } = rootOf(expr); - const sf = root.getSourceFile(); + const sf = ts.getSourceFileOfNode(root); if (root.kind === ts.SyntaxKind.ThisKeyword) { const owner = enclosingFunction(root); if (ts.isSourceFile(owner)) @@ -475,7 +478,7 @@ class UnitResolver { if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = this.checker.getAliasedSymbol(symbol); const decls = symbol === undefined ? [] : symbol.declarations; - const decl = decls.find((d) => !d.getSourceFile().isDeclarationFile); + const decl = decls.find((d) => !ts.getSourceFileOfNode(d).isDeclarationFile); if (decl === undefined) { // CommonJS `exports`/`module` as values: the module object. if (isModuleObjectName(root)) diff --git a/packages/compiler/src/frontend/ts7/api-conformance.ts b/packages/compiler/src/frontend/ts7/api-conformance.ts new file mode 100644 index 000000000..304b4f32f --- /dev/null +++ b/packages/compiler/src/frontend/ts7/api-conformance.ts @@ -0,0 +1,297 @@ +/* Compile-time checks that scriptc's AST and semantic objects have the shapes + * of tsc-ts/api's interfaces. The frontend keeps its own types, whose nodes + * extend the AstNode class, because native code reads class fields directly + * and reads through a structural interface dynamically. This module only + * declares types and emits nothing. */ + +import type * as api from "tsc-ts/api"; +import type { AstNode, AstNodeList } from "./ast-node.js"; +import type * as ast from "./ast-types.js"; + +type Conforms = [T, U]; + +/* Members where tsgo's wire, which the RPC model reads, differs from tsc-ts: + * - SourceFile.externalModuleIndicator is `true` where moduleDetection makes a + * file a module without an import or export. + * - JSDocTypeLiteral.jsdocPropertyTags is one tag. + * - SyntaxList.children is a node list. + * - SyntheticExpression's isSpread and type are factory fields that a parsed + * tree never has. */ +type Without = Omit; + +export interface AstConformance { + Node: Conforms; + NodeList: Conforms; + SourceFile: Conforms< + Without, + Without + >; + ArrayBindingPattern: Conforms; + ArrayLiteralExpression: Conforms; + ArrayTypeNode: Conforms; + ArrowFunction: Conforms; + AsExpression: Conforms; + AwaitExpression: Conforms; + BigIntLiteral: Conforms; + BinaryExpression: Conforms; + BindingElement: Conforms; + Block: Conforms; + BodyBase: Conforms; + BreakStatement: Conforms; + CallExpression: Conforms; + CallSignatureDeclaration: Conforms; + CaseBlock: Conforms; + CaseClause: Conforms; + CatchClause: Conforms; + ClassDeclaration: Conforms; + ClassElementBase: Conforms; + ClassExpression: Conforms; + ClassLikeBase: Conforms; + ClassStaticBlockDeclaration: Conforms< + ast.ClassStaticBlockDeclaration, + api.ClassStaticBlockDeclaration + >; + ComputedPropertyName: Conforms; + ConditionalExpression: Conforms; + ConditionalTypeNode: Conforms; + ConstructSignatureDeclaration: Conforms< + ast.ConstructSignatureDeclaration, + api.ConstructSignatureDeclaration + >; + ConstructorDeclaration: Conforms; + ConstructorTypeNode: Conforms; + ContinueStatement: Conforms; + DebuggerStatement: Conforms; + DeclarationBase: Conforms; + Decorator: Conforms; + DefaultClause: Conforms; + DeleteExpression: Conforms; + DoStatement: Conforms; + ElementAccessExpression: Conforms; + EmptyStatement: Conforms; + EnumDeclaration: Conforms; + EnumMember: Conforms; + ExportAssignment: Conforms; + ExportDeclaration: Conforms; + ExportSpecifier: Conforms; + ExpressionBase: Conforms; + ExpressionStatement: Conforms; + ExpressionWithTypeArguments: Conforms< + ast.ExpressionWithTypeArguments, + api.ExpressionWithTypeArguments + >; + ExternalModuleReference: Conforms; + ForInStatement: Conforms; + ForOfStatement: Conforms; + ForStatement: Conforms; + FunctionDeclaration: Conforms; + FunctionExpression: Conforms; + FunctionLikeBase: Conforms; + FunctionLikeWithBodyBase: Conforms; + FunctionTypeNode: Conforms; + GetAccessorDeclaration: Conforms; + HeritageClause: Conforms; + Identifier: Conforms; + IfStatement: Conforms; + ImportAttribute: Conforms; + ImportAttributes: Conforms; + ImportClause: Conforms; + ImportDeclaration: Conforms; + ImportEqualsDeclaration: Conforms; + ImportSpecifier: Conforms; + ImportTypeNode: Conforms; + IndexSignatureDeclaration: Conforms; + IndexedAccessTypeNode: Conforms; + InferTypeNode: Conforms; + InterfaceDeclaration: Conforms; + IntersectionTypeNode: Conforms; + IterationStatementBase: Conforms; + JSDoc: Conforms; + JSDocAllType: Conforms; + JSDocAugmentsTag: Conforms; + JSDocCallbackTag: Conforms; + JSDocCommentBase: Conforms; + JSDocDeprecatedTag: Conforms; + JSDocImplementsTag: Conforms; + JSDocImportTag: Conforms; + JSDocLink: Conforms; + JSDocLinkCode: Conforms; + JSDocLinkPlain: Conforms; + JSDocNameReference: Conforms; + JSDocNonNullableType: Conforms; + JSDocNullableType: Conforms; + JSDocOptionalType: Conforms; + JSDocOverloadTag: Conforms; + JSDocOverrideTag: Conforms; + JSDocParameterTag: Conforms; + JSDocPrivateTag: Conforms; + JSDocPropertyTag: Conforms; + JSDocProtectedTag: Conforms; + JSDocPublicTag: Conforms; + JSDocReadonlyTag: Conforms; + JSDocReturnTag: Conforms; + JSDocSatisfiesTag: Conforms; + JSDocSeeTag: Conforms; + JSDocSignature: Conforms; + JSDocTagBase: Conforms; + JSDocTemplateTag: Conforms; + JSDocText: Conforms; + JSDocThisTag: Conforms; + JSDocThrowsTag: Conforms; + JSDocTypeBase: Conforms; + JSDocTypeExpression: Conforms; + JSDocTypeLiteral: Conforms< + Without, + Without + >; + JSDocTypeTag: Conforms; + JSDocTypedefTag: Conforms; + JSDocUnknownTag: Conforms; + JSDocVariadicType: Conforms; + JsxAttribute: Conforms; + JsxAttributes: Conforms; + JsxClosingElement: Conforms; + JsxClosingFragment: Conforms; + JsxElement: Conforms; + JsxExpression: Conforms; + JsxFragment: Conforms; + JsxNamespacedName: Conforms; + JsxOpeningElement: Conforms; + JsxOpeningFragment: Conforms; + JsxSelfClosingElement: Conforms; + JsxSpreadAttribute: Conforms; + JsxText: Conforms; + KeywordExpression: Conforms; + KeywordTypeNode: Conforms; + LabeledStatement: Conforms; + LeftHandSideExpressionBase: Conforms< + ast.LeftHandSideExpressionBase, + api.LeftHandSideExpressionBase + >; + LiteralExpressionBase: Conforms; + LiteralLikeNodeBase: Conforms; + LiteralTypeNode: Conforms; + MappedTypeNode: Conforms; + MemberExpressionBase: Conforms; + MetaProperty: Conforms; + MethodDeclaration: Conforms; + MethodSignatureDeclaration: Conforms< + ast.MethodSignatureDeclaration, + api.MethodSignatureDeclaration + >; + MissingDeclaration: Conforms; + ModifiersBase: Conforms; + ModuleBlock: Conforms; + ModuleDeclaration: Conforms; + NamedExports: Conforms; + NamedImports: Conforms; + NamedMemberBase: Conforms; + NamedTupleMember: Conforms; + NamespaceExport: Conforms; + NamespaceExportDeclaration: Conforms< + ast.NamespaceExportDeclaration, + api.NamespaceExportDeclaration + >; + NamespaceImport: Conforms; + NewExpression: Conforms; + NoSubstitutionTemplateLiteral: Conforms< + ast.NoSubstitutionTemplateLiteral, + api.NoSubstitutionTemplateLiteral + >; + NodeBase: Conforms; + NodeWithTypeArgumentsBase: Conforms; + NonNullExpression: Conforms; + NotEmittedStatement: Conforms; + NotEmittedTypeElement: Conforms; + NumericLiteral: Conforms; + ObjectBindingPattern: Conforms; + ObjectLiteralElementBase: Conforms; + ObjectLiteralExpression: Conforms; + OmittedExpression: Conforms; + OptionalTypeNode: Conforms; + ParameterDeclaration: Conforms; + ParenthesizedExpression: Conforms; + ParenthesizedTypeNode: Conforms; + PartiallyEmittedExpression: Conforms< + ast.PartiallyEmittedExpression, + api.PartiallyEmittedExpression + >; + PostfixUnaryExpression: Conforms; + PrefixUnaryExpression: Conforms; + PrimaryExpressionBase: Conforms; + PrivateIdentifier: Conforms; + PropertyAccessExpression: Conforms; + PropertyAssignment: Conforms; + PropertyDeclaration: Conforms; + PropertySignatureDeclaration: Conforms< + ast.PropertySignatureDeclaration, + api.PropertySignatureDeclaration + >; + QualifiedName: Conforms; + RegularExpressionLiteral: Conforms; + RestTypeNode: Conforms; + ReturnStatement: Conforms; + SatisfiesExpression: Conforms; + SemicolonClassElement: Conforms; + SetAccessorDeclaration: Conforms; + ShorthandPropertyAssignment: Conforms< + ast.ShorthandPropertyAssignment, + api.ShorthandPropertyAssignment + >; + SpreadAssignment: Conforms; + SpreadElement: Conforms; + StatementBase: Conforms; + StringLiteral: Conforms; + SwitchStatement: Conforms; + SyntaxList: Conforms, Without>; + SyntheticExpression: Conforms< + Without, + Without + >; + SyntheticReferenceExpression: Conforms< + ast.SyntheticReferenceExpression, + api.SyntheticReferenceExpression + >; + TaggedTemplateExpression: Conforms; + TemplateExpression: Conforms; + TemplateHead: Conforms; + TemplateLiteralLikeNodeBase: Conforms< + ast.TemplateLiteralLikeNodeBase, + api.TemplateLiteralLikeNodeBase + >; + TemplateLiteralTypeNode: Conforms; + TemplateLiteralTypeSpan: Conforms; + TemplateMiddle: Conforms; + TemplateSpan: Conforms; + TemplateTail: Conforms; + ThisTypeNode: Conforms; + ThrowStatement: Conforms; + Token: Conforms; + TryStatement: Conforms; + TupleTypeNode: Conforms; + TypeAliasDeclaration: Conforms; + TypeAssertion: Conforms; + TypeElementBase: Conforms; + TypeLiteralNode: Conforms; + TypeNodeBase: Conforms; + TypeOfExpression: Conforms; + TypeOperatorNode: Conforms; + TypeParameterDeclaration: Conforms; + TypePredicateNode: Conforms; + TypeQueryNode: Conforms; + TypeReferenceNode: Conforms; + UnaryExpressionBase: Conforms; + UnionOrIntersectionTypeNodeBase: Conforms< + ast.UnionOrIntersectionTypeNodeBase, + api.UnionOrIntersectionTypeNodeBase + >; + UnionTypeNode: Conforms; + UpdateExpressionBase: Conforms; + VariableDeclaration: Conforms; + VariableDeclarationList: Conforms; + VariableStatement: Conforms; + VoidExpression: Conforms; + WhileStatement: Conforms; + WithStatement: Conforms; + YieldExpression: Conforms; +} diff --git a/packages/compiler/src/frontend/ts7/ast-guards.generated.ts b/packages/compiler/src/frontend/ts7/ast-guards.generated.ts index aeaf56e9a..d163ac18d 100644 --- a/packages/compiler/src/frontend/ts7/ast-guards.generated.ts +++ b/packages/compiler/src/frontend/ts7/ast-guards.generated.ts @@ -4,6 +4,7 @@ import type { AbstractKeyword, AccessExpression, AccessorDeclaration, AccessorKeyword, AdditiveOperator, AdditiveOperatorOrHigher, AnyImportSyntax, ArrayBindingElement, ArrayBindingPattern, ArrayDestructuringAssignment, ArrayLiteralExpression, ArrayTypeNode, ArrowFunction, AsExpression, AssertKeyword, AssertionExpression, AssertsKeyword, AssignmentOperator, AssignmentOperatorOrHigher, AssignmentOperatorToken, AsteriskToken, AsyncKeyword, AwaitExpression, AwaitKeyword, BigIntLiteral, BinaryExpression, BinaryOperator, BinaryOperatorToken, BindingElement, BindingName, BindingPattern, BitwiseOperator, BitwiseOperatorOrHigher, Block, BlockOrExpression, BooleanLiteral, BreakOrContinueStatement, BreakStatement, CallExpression, CallLikeExpression, CallOrNewExpression, CallSignatureDeclaration, CaseBlock, CaseClause, CaseKeyword, CatchClause, ClassDeclaration, ClassExpression, ClassLikeDeclaration, ClassStaticBlockDeclaration, ColonToken, CompoundAssignmentOperator, ComputedPropertyName, ConciseBody, ConditionalExpression, ConditionalTypeNode, ConstKeyword, ConstructSignatureDeclaration, ConstructorDeclaration, ConstructorTypeNode, ContinueStatement, DebuggerStatement, DeclarationName, DeclareKeyword, Decorator, DefaultClause, DefaultKeyword, DeleteExpression, DestructuringAssignment, DoStatement, DotDotDotToken, DotToken, ElementAccessExpression, EmptyStatement, EndOfFile, EntityName, EnumDeclaration, EnumMember, EqualityOperator, EqualityOperatorOrHigher, EqualsGreaterThanToken, EqualsToken, ExclamationToken, ExponentiationOperator, ExportAssignment, ExportDeclaration, ExportKeyword, ExportSpecifier, Expression, ExpressionStatement, ExpressionWithTypeArguments, ExternalModuleReference, FalseLiteral, ForInStatement, ForInitializer, ForOfStatement, ForStatement, FunctionBody, FunctionDeclaration, FunctionExpression, FunctionLikeDeclaration, FunctionTypeNode, GetAccessorDeclaration, HeritageClause, Identifier, IfStatement, ImportAttribute, ImportAttributeName, ImportAttributes, ImportClause, ImportClauseOrBindingPattern, ImportDeclaration, ImportEqualsDeclaration, ImportExpression, ImportPhaseModifierSyntaxKind, ImportSpecifier, ImportTypeNode, InKeyword, IndexSignatureDeclaration, IndexedAccessTypeNode, InferTypeNode, InterfaceDeclaration, IntersectionTypeNode, JSDoc, JSDocAllType, JSDocAugmentsTag, JSDocCallbackTag, JSDocComment, JSDocDeprecatedTag, JSDocFullName, JSDocImplementsTag, JSDocImportTag, JSDocLink, JSDocLinkCode, JSDocLinkPlain, JSDocNameReference, JSDocNonNullableType, JSDocNullableType, JSDocOptionalType, JSDocOverloadTag, JSDocOverrideTag, JSDocParameterTag, JSDocPrivateTag, JSDocPropertyTag, JSDocProtectedTag, JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocTemplateTag, JSDocText, JSDocThisTag, JSDocThrowsTag, JSDocTypeExpression, JSDocTypeLiteral, JSDocTypeTag, JSDocTypedefTag, JSDocUnknownTag, JSDocVariadicType, JsxAttribute, JsxAttributeLike, JsxAttributeName, JsxAttributeValue, JsxAttributes, JsxChild, JsxClosingElement, JsxClosingFragment, JsxElement, JsxExpression, JsxFragment, JsxNamespacedName, JsxOpeningElement, JsxOpeningFragment, JsxOpeningLikeElement, JsxSelfClosingElement, JsxSpreadAttribute, JsxTagNameExpression, JsxText, JsxTokenSyntaxKind, KeywordExpression, KeywordExpressionSyntaxKind, KeywordTypeNode, KeywordTypeSyntaxKind, LabeledStatement, LeftHandSideExpression, LiteralExpression, LiteralLikeNode, LiteralToken, LiteralTypeNode, LogicalOperator, LogicalOperatorOrHigher, LogicalOrCoalescingAssignmentOperator, MappedTypeNode, MemberName, MetaProperty, MethodDeclaration, MethodSignatureDeclaration, MinusToken, MissingDeclaration, Modifier, ModifierLike, ModifierSyntaxKind, ModuleBlock, ModuleBody, ModuleDeclaration, ModuleExportName, ModuleName, ModuleReference, MultiplicativeOperator, MultiplicativeOperatorOrHigher, NamedExportBindings, NamedExports, NamedImportBindings, NamedImports, NamedImportsOrExports, NamedTupleMember, NamespaceExport, NamespaceExportDeclaration, NamespaceImport, NewExpression, NoSubstitutionTemplateLiteral, Node, NonNullExpression, NotEmittedStatement, NotEmittedTypeElement, NullLiteral, NumericLiteral, NumericOrStringLikeLiteral, ObjectBindingPattern, ObjectDestructuringAssignment, ObjectLiteralElementLike, ObjectLiteralExpression, ObjectLiteralLikeNode, ObjectTypeDeclaration, OmittedExpression, OptionalTypeNode, OutKeyword, OverrideKeyword, ParameterDeclaration, ParenthesizedExpression, ParenthesizedTypeNode, PartiallyEmittedExpression, PlusToken, PostfixUnaryExpression, PostfixUnaryOperator, PrefixUnaryExpression, PrefixUnaryOperator, PrivateIdentifier, PrivateKeyword, PropertyAccessExpression, PropertyAssignment, PropertyDeclaration, PropertyName, PropertyNameLiteral, PropertySignatureDeclaration, ProtectedKeyword, PseudoLiteralSyntaxKind, PseudoLiteralToken, PublicKeyword, QualifiedName, QuestionDotToken, QuestionToken, ReadonlyKeyword, RegularExpressionLiteral, RelationalOperator, RelationalOperatorOrHigher, RestTypeNode, ReturnStatement, SatisfiesExpression, SemicolonClassElement, SetAccessorDeclaration, ShiftOperator, ShiftOperatorOrHigher, ShorthandPropertyAssignment, SignatureDeclaration, SourceFile, SpreadAssignment, SpreadElement, Statement, StaticKeyword, StringLiteral, StringLiteralLikeNode, SuperExpression, SwitchStatement, SyntaxList, SyntheticExpression, SyntheticReferenceExpression, TaggedTemplateExpression, TemplateExpression, TemplateHead, TemplateLiteral, TemplateLiteralLikeNode, TemplateLiteralToken, TemplateLiteralTypeNode, TemplateLiteralTypeSpan, TemplateMiddle, TemplateMiddleOrTail, TemplateSpan, TemplateTail, ThisExpression, ThisTypeNode, ThrowStatement, Token, TriviaSyntaxKind, TrueLiteral, TryStatement, TupleTypeNode, TypeAliasDeclaration, TypeAssertion, TypeLiteralNode, TypeNode, TypeOfExpression, TypeOperatorNode, TypeParameterDeclaration, TypePredicateNode, TypePredicateParameterName, TypeQueryNode, TypeReferenceNode, UnaryExpressionBase, UnionOrIntersectionTypeNode, UnionTypeNode, VariableDeclaration, VariableDeclarationList, VariableOrParameterDeclaration, VariableOrPropertyDeclaration, VariableStatement, VoidExpression, WhileStatement, WithStatement, YieldExpression } from "./ast-types.js"; import { SyntaxKind, NodeFlags, ScriptKind, OuterExpressionKinds } from "./enums.js"; +import { getSourceFileOfNode } from "./node-functions.js"; export function isToken(node: Node | undefined): node is Token { if (node === undefined) return false; @@ -1913,7 +1914,7 @@ export function skipOuterExpressions(node: Node, kinds: OuterExpressionKinds = O function isJSDocTypeAssertion(node: Node | undefined): boolean { if (node === undefined) return false; - const sourceFile = node.getSourceFile(); + const sourceFile = getSourceFileOfNode(node); if (sourceFile.scriptKind !== ScriptKind.JS && sourceFile.scriptKind !== ScriptKind.JSX) { return false; } diff --git a/packages/compiler/src/frontend/ts7/ast-node.test.ts b/packages/compiler/src/frontend/ts7/ast-node.test.ts index e2440b787..b9b8f0e7b 100644 --- a/packages/compiler/src/frontend/ts7/ast-node.test.ts +++ b/packages/compiler/src/frontend/ts7/ast-node.test.ts @@ -20,7 +20,13 @@ import { walkPreorder } from "./ast.js"; import { ts7Executable } from "./rpc-api.js"; import { Ts7RpcClient } from "./rpc-client.js"; import { spawnTs7Wire } from "./rpc-process.js"; -import { isJSDocTypeLiteral, isJSDocPropertyTag } from "./ast-guards.generated.js"; +import { + isArrayLiteralExpression, + isCallExpression, + isJSDocTypeLiteral, + isJSDocPropertyTag, +} from "./ast-guards.generated.js"; +import { ModifierFlags, SyntaxKind } from "./enums.js"; const require = createRequire(import.meta.url); const sdkRoot = dirname(require.resolve("typescript/package.json")); @@ -95,6 +101,8 @@ const cases: Record = { '/** @typedef {Object} Options\n * @property {string} name\n * @property {number} count\n */\n/** @type {Options} */ const options = { name: "x", count: 2 };', "missing.ts": "const incomplete = ;\nfunction f( {\n", "empty.ts": "", + "lists.ts": + "const trailing = [1, 2 /* last */, ];\nconst plain = [1, 2];\nf(1, 2,);\nexport abstract class Flags { protected static readonly value = 1; }\n", }; const decoded = new Map(); let directory: string; @@ -159,6 +167,26 @@ function ids( return nodes?.map((n) => (n as OracleNode).index); } +test("node lists report a trailing comma and modifier lists their flags", () => { + const { file } = decoded.get("lists.ts")!; + const lists: [SyntaxKind, boolean][] = []; + const modifiers: number[] = []; + walkPreorder(file.root, (node) => { + if (isArrayLiteralExpression(node)) lists.push([node.kind, node.elements.hasTrailingComma()]); + if (isCallExpression(node)) lists.push([node.kind, node.arguments.hasTrailingComma()]); + if (node.modifiers !== undefined) modifiers.push(node.modifiers.modifierFlags); + }); + expect(lists).toEqual([ + [SyntaxKind.ArrayLiteralExpression, true], + [SyntaxKind.ArrayLiteralExpression, false], + [SyntaxKind.CallExpression, true], + ]); + expect(modifiers).toEqual([ + ModifierFlags.Export | ModifierFlags.Abstract, + ModifierFlags.Protected | ModifierFlags.Static | ModifierFlags.Readonly, + ]); +}); + test("JSDoc property refinements describe the pinned native wire", () => { const { file, oracle } = decoded.get("properties.js")!; let checked = 0; diff --git a/packages/compiler/src/frontend/ts7/ast-node.ts b/packages/compiler/src/frontend/ts7/ast-node.ts index 3760fff8d..8eaa87c72 100644 --- a/packages/compiler/src/frontend/ts7/ast-node.ts +++ b/packages/compiler/src/frontend/ts7/ast-node.ts @@ -35,6 +35,25 @@ export class AstNodeList { get end(): number { return this.wire.end(this.index) >>> 0; } + + /** Whether a comma follows the last node before the list's end. */ + hasTrailingComma(): boolean { + const last = this.nodes[this.nodes.length - 1]; + if (last === undefined) return false; + const text = last.getSourceFile().text; + const next = astSkipTrivia(text, last.end, false, false); + return next < this.end && text.charCodeAt(next) === 44; + } +} + +/** A declaration's modifiers, with the flags of all of them: tsc-ts's + * ModifierList. */ +export class AstModifierList extends AstNodeList { + get modifierFlags(): number { + let result = 0; + for (const modifier of this.nodes) result |= astModifierFlag(modifier.kind); + return result; + } } /** One immutable AST response owns one object per node and one array per @@ -85,6 +104,12 @@ export class AstFile { } list(index: number): AstNodeList { + return new AstNodeList(this.wire, index, this.listNodes(index)); + } + modifierList(index: number): AstModifierList { + return new AstModifierList(this.wire, index, this.listNodes(index)); + } + private listNodes(index: number): AstNode[] { let nodes = this.lists[index]; if (nodes === undefined) { nodes = []; @@ -92,7 +117,7 @@ export class AstFile { this.lists[index] = nodes; this.materialized?.(); } - return new AstNodeList(this.wire, index, nodes); + return nodes; } namedChild(node: AstNode, name: string): number { @@ -422,6 +447,11 @@ export class AstNode { return result; } + get modifiers(): AstModifierList | undefined { + const index = this.file.namedChild(this, "modifiers"); + return index === 0 ? undefined : this.file.modifierList(index); + } + get body(): AstNode | undefined { const body = this.childNode("body"); // TS7 can serialize a reparsed @type callable before a method's @@ -599,9 +629,6 @@ export class AstNode { get members(): AstNodeList | undefined { return this.childList("members"); } - get modifiers(): AstNodeList | undefined { - return this.childList("modifiers"); - } get moduleReference(): AstNode | undefined { return this.childNode("moduleReference"); } diff --git a/packages/compiler/src/frontend/ts7/ast-types.generated.ts b/packages/compiler/src/frontend/ts7/ast-types.generated.ts index de6ff892f..59f01e9ea 100644 --- a/packages/compiler/src/frontend/ts7/ast-types.generated.ts +++ b/packages/compiler/src/frontend/ts7/ast-types.generated.ts @@ -3,7 +3,7 @@ // Regenerate when changing the TypeScript pin; do not edit by hand. import type { AstNode } from "./ast-node.js"; -import type { JsxTagNamePropertyAccess, NodeArray } from "./ast-types.js"; +import type { JsxTagNamePropertyAccess, ModifierArray, NodeArray } from "./ast-types.js"; import { SyntaxKind, NodeFlags, ModifierFlags, TokenFlags } from "./enums.js"; type Node = AstNode; @@ -142,7 +142,7 @@ export interface DeclarationBase extends Node { } export interface ModifiersBase extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.VariableStatement | SyntaxKind.Parameter | SyntaxKind.MissingDeclaration | SyntaxKind.FunctionDeclaration | SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression | SyntaxKind.InterfaceDeclaration | SyntaxKind.TypeAliasDeclaration | SyntaxKind.EnumMember | SyntaxKind.EnumDeclaration | SyntaxKind.ImportDeclaration | SyntaxKind.ExportAssignment | SyntaxKind.NamespaceExportDeclaration | SyntaxKind.Constructor | SyntaxKind.GetAccessor | SyntaxKind.SetAccessor | SyntaxKind.IndexSignature | SyntaxKind.MethodSignature | SyntaxKind.MethodDeclaration | SyntaxKind.PropertySignature | SyntaxKind.PropertyDeclaration | SyntaxKind.ClassStaticBlockDeclaration | SyntaxKind.BinaryExpression | SyntaxKind.ArrowFunction | SyntaxKind.FunctionExpression | SyntaxKind.PropertyAssignment | SyntaxKind.ShorthandPropertyAssignment | SyntaxKind.FunctionType | SyntaxKind.ConstructorType | SyntaxKind.ModuleDeclaration | SyntaxKind.ImportEqualsDeclaration | SyntaxKind.ExportDeclaration | SyntaxKind.TypeParameter; } @@ -170,7 +170,7 @@ export interface FunctionLikeWithBodyBase extends Node { } export interface ClassLikeBase extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: Identifier | undefined; readonly typeParameters: NodeArray | undefined; @@ -209,7 +209,7 @@ export interface ClassElementBase extends Node { } export interface NamedMemberBase extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -397,7 +397,7 @@ export interface Block extends Node { export interface VariableStatement extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.VariableStatement; readonly declarationList: VariableDeclarationList; @@ -420,7 +420,7 @@ export interface VariableDeclarationList extends Node { export interface ParameterDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.Parameter; readonly dotDotDotToken: DotDotDotToken | undefined; @@ -441,14 +441,14 @@ export interface BindingElement extends Node { export interface MissingDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.MissingDeclaration; } export interface FunctionDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -461,7 +461,7 @@ export interface FunctionDeclaration extends Node { export interface ClassDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: Identifier | undefined; readonly typeParameters: NodeArray | undefined; @@ -472,7 +472,7 @@ export interface ClassDeclaration extends Node { export interface ClassExpression extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: Identifier | undefined; readonly typeParameters: NodeArray | undefined; @@ -490,7 +490,7 @@ export interface HeritageClause extends Node { export interface InterfaceDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.InterfaceDeclaration; readonly name: Identifier; @@ -501,7 +501,7 @@ export interface InterfaceDeclaration extends Node { export interface TypeAliasDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.TypeAliasDeclaration; readonly name: Identifier; @@ -511,7 +511,7 @@ export interface TypeAliasDeclaration extends Node { export interface EnumMember extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -521,7 +521,7 @@ export interface EnumMember extends Node { export interface EnumDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.EnumDeclaration; readonly name: Identifier; @@ -546,7 +546,7 @@ export interface NotEmittedTypeElement extends Node { export interface ImportDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ImportDeclaration; readonly importClause: ImportClause | undefined; @@ -574,7 +574,7 @@ export interface NamedImports extends Node { export interface ExportAssignment extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ExportAssignment; readonly isExportEquals: boolean; @@ -584,7 +584,7 @@ export interface ExportAssignment extends Node { export interface NamespaceExportDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.NamespaceExportDeclaration; readonly name: Identifier; @@ -628,7 +628,7 @@ export interface ConstructSignatureDeclaration extends Node { export interface ConstructorDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -639,7 +639,7 @@ export interface ConstructorDeclaration extends Node { } export interface GetAccessorDeclaration extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -653,7 +653,7 @@ export interface GetAccessorDeclaration extends Node { } export interface SetAccessorDeclaration extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -668,7 +668,7 @@ export interface SetAccessorDeclaration extends Node { export interface IndexSignatureDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -678,7 +678,7 @@ export interface IndexSignatureDeclaration extends Node { export interface MethodSignatureDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -690,7 +690,7 @@ export interface MethodSignatureDeclaration extends Node { export interface MethodDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -704,7 +704,7 @@ export interface MethodDeclaration extends Node { export interface PropertySignatureDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -715,7 +715,7 @@ export interface PropertySignatureDeclaration extends Node { export interface PropertyDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -731,7 +731,7 @@ export interface SemicolonClassElement extends Node { export interface ClassStaticBlockDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ClassStaticBlockDeclaration; readonly body: Block; @@ -786,7 +786,7 @@ export interface NoSubstitutionTemplateLiteral extends Node { export interface BinaryExpression extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.BinaryExpression; readonly left: Expression; @@ -818,7 +818,7 @@ export interface YieldExpression extends Node { export interface ArrowFunction extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -831,7 +831,7 @@ export interface ArrowFunction extends Node { export interface FunctionExpression extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -969,7 +969,7 @@ export interface SpreadAssignment extends Node { export interface PropertyAssignment extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -980,7 +980,7 @@ export interface PropertyAssignment extends Node { export interface ShorthandPropertyAssignment extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -1180,7 +1180,7 @@ export interface ParenthesizedTypeNode extends Node { export interface FunctionTypeNode extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -1190,7 +1190,7 @@ export interface FunctionTypeNode extends Node { export interface ConstructorTypeNode extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -1562,7 +1562,7 @@ export interface JSDocNameReference extends Node { export interface ModuleDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly asteriskToken: AsteriskToken | undefined; readonly body: ModuleBody | undefined; @@ -1573,7 +1573,7 @@ export interface ModuleDeclaration extends Node { export interface ImportEqualsDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ImportEqualsDeclaration; readonly isTypeOnly: boolean; @@ -1583,7 +1583,7 @@ export interface ImportEqualsDeclaration extends Node { export interface ExportDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ExportDeclaration; readonly isTypeOnly: boolean; @@ -1647,7 +1647,7 @@ export interface JSDocLinkCode extends Node { export interface TypeParameterDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.TypeParameter; readonly name: Identifier; @@ -1842,7 +1842,7 @@ export interface CaseClause extends Node { export interface DefaultClause extends Node { readonly flags: NodeFlags; readonly kind: SyntaxKind.DefaultClause; - readonly expression: Expression; + readonly expression: undefined; readonly statements: NodeArray; } diff --git a/packages/compiler/src/frontend/ts7/ast-types.ts b/packages/compiler/src/frontend/ts7/ast-types.ts index 6d421e58d..e40e40e30 100644 --- a/packages/compiler/src/frontend/ts7/ast-types.ts +++ b/packages/compiler/src/frontend/ts7/ast-types.ts @@ -1,4 +1,4 @@ -import type { AstNode, AstNodeList } from "./ast-node.js"; +import type { AstModifierList, AstNode, AstNodeList } from "./ast-node.js"; import { SyntaxKind } from "./enums.js"; import type { EndOfFile, @@ -14,8 +14,11 @@ export type Node = AstNode; /** A node list. The type is the list class itself, not a structural shape: * native code represents a class and a record type differently. */ export type NodeArray = AstNodeList; +/** A declaration's modifiers: a node list with the flags of all of them. */ +export type ModifierArray = AstModifierList; export type Path = string; -export type __String = string; +/** An escaped symbol name, the key of member and export tables (TypeScript 7's `__String`). */ +export type __String = string & { readonly __escapedIdentifier: void }; export interface TextRange { pos: number; end: number; diff --git a/packages/compiler/src/frontend/ts7/ast.ts b/packages/compiler/src/frontend/ts7/ast.ts index ec26aa0b0..a92cfafd8 100644 --- a/packages/compiler/src/frontend/ts7/ast.ts +++ b/packages/compiler/src/frontend/ts7/ast.ts @@ -53,6 +53,7 @@ import { ModifierFlags, SyntaxKind } from "./enums.js"; // The frontend's node types, guards and token spellings share the concrete // AstNode layout. Parser and transform factories remain outside this client. export * from "./ast-types.js"; +export * from "./node-functions.js"; export * from "./ast-guards.generated.js"; export { tokenToString } from "./ast-tokens.generated.js"; diff --git a/packages/compiler/src/frontend/ts7/checker.test.ts b/packages/compiler/src/frontend/ts7/checker.test.ts index 7b3647b2d..ac062b283 100644 --- a/packages/compiler/src/frontend/ts7/checker.test.ts +++ b/packages/compiler/src/frontend/ts7/checker.test.ts @@ -10,10 +10,10 @@ import { ts7Executable } from "./rpc-api.js"; import { Ts7Session } from "./session.js"; import { SemanticChecker } from "./semantic-checker.js"; import { SemanticSnapshot } from "./semantic-model.js"; -import { TypeFlags } from "./enums.js"; +import { SignatureKind, SymbolFlags, TypeFlags } from "./enums.js"; import { createProgram, Ts7Host } from "./program-adapter.js"; import type { AstNode } from "./ast-node.js"; -import type { SourceFile } from "./ast-types.js"; +import type { Identifier, SourceFile } from "./ast-types.js"; const tempRoot = process.platform === "win32" ? tmpdir() : "/tmp"; const protocolPath = (path: string) => path.replaceAll("\\", "/"); @@ -167,6 +167,12 @@ test("all public query and prefetch entry points reject a disposed facade before const calls: Record unknown> = { getCallSignatures: () => facade.getCallSignatures(type), getConstructSignatures: () => facade.getConstructSignatures(type), + getSignaturesOfType: () => facade.getSignaturesOfType(type, SignatureKind.Call), + resolveName: () => facade.resolveName("value", SymbolFlags.Value, name), + getResolvedSymbol: () => facade.getResolvedSymbol(name as Identifier), + getAnyType: () => facade.getAnyType(), + getNeverType: () => facade.getNeverType(), + getBigIntType: () => facade.getBigIntType(), getPropertyOfType: () => facade.getPropertyOfType(type, "missing"), prefetchSourceFile: () => facade.prefetchSourceFile(root), prefetchSourceFileStructures: () => facade.prefetchSourceFileStructures([]), diff --git a/packages/compiler/src/frontend/ts7/checker.ts b/packages/compiler/src/frontend/ts7/checker.ts index 41eb0d0b9..4f2d60294 100644 --- a/packages/compiler/src/frontend/ts7/checker.ts +++ b/packages/compiler/src/frontend/ts7/checker.ts @@ -35,7 +35,7 @@ import { CheckerCache } from "./checker-cache.js"; * repeats an IPC request. All paths are verified against the raw checker * and against 5.9.3 by the adapter's suites. */ -import type { Node, SourceFile } from "./ast-types.js"; +import type { Identifier, Node, SourceFile } from "./ast-types.js"; import type { IndexInfo, InterfaceType, @@ -46,9 +46,10 @@ import type { TypeReference, } from "./semantic-types.js"; import type { SemanticChecker as Checker } from "./semantic-checker.js"; -import type { SemanticProject as Project } from "./semantic-model.js"; +import { symbolNameOf, type SemanticProject as Project } from "./semantic-model.js"; import { isTypeNode, walkPreorder } from "./ast.js"; -import { SignatureKind, SyntaxKind, TypeFlags } from "./enums.js"; +import { SignatureKind, SyntaxKind, TypeFlags, type SymbolFlags } from "./enums.js"; +import { getSourceFileOfNode } from "./node-functions.js"; /** Array-overload chunk size: large enough that per-request overhead * vanishes, small enough to keep any single JSON-RPC payload modest. */ @@ -259,6 +260,14 @@ export class CheckerFacade { return sigs; } + /** The call or construct signatures of `type`, as getCallSignatures and getConstructSignatures + * answer them. */ + getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { + return kind === SignatureKind.Call + ? this.getCallSignatures(type) + : this.getConstructSignatures(type); + } + /** 5.9.3's type.getProperty(name). */ getPropertyOfType(type: Type, name: string): Ts7Symbol | undefined { this.ensureActive(); @@ -370,7 +379,7 @@ export class CheckerFacade { private markManaged(roots: readonly Node[]): void { for (const root of roots) { - const sf = root.getSourceFile(); + const sf = getSourceFileOfNode(root); this.cache.managedTypes.add(sf); this.cache.managedSymbols.add(sf); } @@ -447,7 +456,7 @@ export class CheckerFacade { private autoPrefetch(node: Node, kind: "types" | "symbols"): void { if (this.options.autoPrefetch === false) return; - const sf = node.getSourceFile(); + const sf = getSourceFileOfNode(node); // A referenced declaration rarely needs the rest of its library's // metadata. Keep those queries direct; explicit whole-file prefetch // remains available to consumers that do need the complete surface. @@ -786,6 +795,38 @@ export class CheckerFacade { return this.raw.getTypeOfSymbolAtLocation(symbol, location); } + resolveName( + name: string, + meaning: SymbolFlags, + location?: Node, + excludeGlobals?: boolean, + ): Ts7Symbol | undefined { + this.ensureActive(); + return this.raw.resolveName(name, meaning, location, excludeGlobals); + } + + /** The value `node` names where it is: resolveName of its text with SymbolFlags.Value and + * SymbolFlags.ExportValue. */ + getResolvedSymbol(node: Identifier): Ts7Symbol | undefined { + this.ensureActive(); + return this.raw.getResolvedSymbol(node); + } + + getAnyType(): Type { + this.ensureActive(); + return this.anyType(); + } + + getNeverType(): Type { + this.ensureActive(); + return this.intrinsic("never", () => this.raw.getNeverType()); + } + + getBigIntType(): Type { + this.ensureActive(); + return this.intrinsic("bigint", () => this.raw.getBigIntType()); + } + getUnknownType(): Type { this.ensureActive(); if (this.cache.unknownType === null) this.cache.unknownType = this.raw.getUnknownType(); @@ -851,7 +892,7 @@ export class CheckerFacade { * programs see the es2025 lib's Promise (the ambient world forces it). */ private promiseArgumentOf(type: Type): Type | null { if (!type.isTypeReference()) return null; - const name = type.getTarget()?.getSymbol()?.name; + const name = symbolNameOf(type.getTarget()?.getSymbol()); if (name !== "Promise" && name !== "PromiseLike") return null; const args = this.getTypeArguments(type); return args[0] ?? null; diff --git a/packages/compiler/src/frontend/ts7/diagnostic.ts b/packages/compiler/src/frontend/ts7/diagnostic.ts new file mode 100644 index 000000000..db3ff0e63 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/diagnostic.ts @@ -0,0 +1,38 @@ +import type { DiagnosticCategory } from "./enums.generated.js"; +import type { Ts7DiagnosticData } from "./session-schema.generated.js"; + +/** A diagnostic with every member set, as tsc-ts/api's Diagnostic has them. The server leaves out + * the members a diagnostic doesn't have. `fileName` is undefined for global and program + * diagnostics. */ +export interface Ts7Diagnostic { + readonly fileName: string | undefined; + readonly pos: number; + readonly end: number; + readonly code: number; + readonly category: DiagnosticCategory; + readonly text: string; + readonly reportsUnnecessary: boolean; + readonly reportsDeprecated: boolean; + readonly messageChain: readonly Ts7Diagnostic[]; + readonly relatedInformation: readonly Ts7Diagnostic[]; +} + +/** `data` with its absent members set: undefined, false, or no chained or related diagnostics. */ +export function ts7Diagnostic(data: Ts7DiagnosticData): Ts7Diagnostic { + const chain: Ts7Diagnostic[] = []; + for (const item of data.messageChain ?? []) chain.push(ts7Diagnostic(item)); + const related: Ts7Diagnostic[] = []; + for (const item of data.relatedInformation ?? []) related.push(ts7Diagnostic(item)); + return { + fileName: data.fileName, + pos: data.pos, + end: data.end, + code: data.code, + category: data.category, + text: data.text, + reportsUnnecessary: data.reportsUnnecessary ?? false, + reportsDeprecated: data.reportsDeprecated ?? false, + messageChain: chain, + relatedInformation: related, + }; +} diff --git a/packages/compiler/src/frontend/ts7/node-functions.ts b/packages/compiler/src/frontend/ts7/node-functions.ts new file mode 100644 index 000000000..3b37779e8 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/node-functions.ts @@ -0,0 +1,23 @@ +/* Position and text functions over nodes, with tsc-ts/api's names. The + * frontend calls these rather than node methods, so that its node types can + * be tsc-ts/api's interfaces, which have no methods. Today every node is an + * AstNode, and each function forwards to the AstNode method. */ + +import type { AstNode } from "./ast-node.js"; +import type { Node, SourceFile } from "./ast-types.js"; + +/** The start of the node's first token, after its leading trivia, and before + * its JSDoc comments when `includeJSDoc` is true. */ +export function tokenStartOfNode(node: Node, includeJSDoc = false): number { + return (node as AstNode).getStart(undefined, includeJSDoc); +} + +/** The node's text from its first token to its end. */ +export function getTextOfNode(node: Node): string { + return (node as AstNode).getText(); +} + +/** The source file that contains the node. */ +export function getSourceFileOfNode(node: Node): SourceFile { + return (node as AstNode).getSourceFile(); +} diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 2f2fdf76b..dbc0804d5 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -21,7 +21,7 @@ import { dirname, join, resolve } from "node:path"; import { Ts7Api } from "./api.js"; import type { Ts7TimingInfo } from "./session-timing.js"; import type { Ts7FileSystem } from "./rpc-filesystem.js"; -import type { CompilerOptions, Diagnostic } from "./semantic-types.js"; +import type { CompilerOptions, Diagnostic, SourceFileMetadata } from "./semantic-types.js"; import type { SourceFile } from "./ast-types.js"; import type { Ts7SessionProject as Project, Ts7SessionSnapshot as Snapshot } from "./session.js"; import { CheckerFacade } from "./checker.js"; @@ -343,10 +343,22 @@ export class Ts7Program { return this.project.program.getSyntacticDiagnostics(sourceFile?.fileName); } + getBindDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { + return this.project.program.getBindDiagnostics(sourceFile?.fileName); + } + getSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { return this.project.program.getSemanticDiagnostics(sourceFile?.fileName); } + getSuggestionDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { + return this.project.program.getSuggestionDiagnostics(sourceFile?.fileName); + } + + getDeclarationDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { + return this.project.program.getDeclarationDiagnostics(sourceFile?.fileName); + } + getGlobalDiagnostics(): readonly Diagnostic[] { return this.project.program.getGlobalDiagnostics(); } @@ -359,6 +371,10 @@ export class Ts7Program { return this.project.program.getConfigFileParsingDiagnostics(); } + getSourceFileMetadata(fileName: string): SourceFileMetadata | undefined { + return this.project.program.getSourceFileMetadata(resolve(fileName)); + } + isSourceFileDefaultLibrary(file: SourceFile): boolean { return this.project.program.isSourceFileDefaultLibrary(file); } diff --git a/packages/compiler/src/frontend/ts7/semantic-checker.test.ts b/packages/compiler/src/frontend/ts7/semantic-checker.test.ts index 5c5f2e2fc..90a7a25cf 100644 --- a/packages/compiler/src/frontend/ts7/semantic-checker.test.ts +++ b/packages/compiler/src/frontend/ts7/semantic-checker.test.ts @@ -10,6 +10,8 @@ import { tsgoExecutable } from "./rpc-api.js"; import { spawnTs7Wire } from "./rpc-process.js"; import { Ts7Session } from "./session.js"; import { SyntaxKind } from "./enums.js"; +import { getSourceFileOfNode } from "./node-functions.js"; +import { forEachChild } from "./ast.js"; const source = [ "export class Box { value = 1; }", @@ -98,15 +100,15 @@ test("synthetic type roots and descendants match the pinned SDK without claiming expect(actual.file.node(1)).toBe(actual); expect(actual.parent).toBeUndefined(); expect(() => actual.file.sourceFile).toThrow("expected a source file root"); - expect(() => actual.getSourceFile()).toThrow("expected a source file root"); - actual.forEachChild((child) => { + expect(() => getSourceFileOfNode(actual)).toThrow("expected a source file root"); + forEachChild(actual, (child) => { expect(child.parent).toBe(actual); }); checked++; } expect(checked).toBe(6); expect(root.file.sourceFile).toBe(root); - expect(root.getSourceFile()).toBe(root); + expect(getSourceFileOfNode(root)).toBe(root); } finally { snapshot.dispose(); sdkSnapshot.dispose(); diff --git a/packages/compiler/src/frontend/ts7/semantic-checker.ts b/packages/compiler/src/frontend/ts7/semantic-checker.ts index 17ca43bf8..92aa77333 100644 --- a/packages/compiler/src/frontend/ts7/semantic-checker.ts +++ b/packages/compiler/src/frontend/ts7/semantic-checker.ts @@ -16,16 +16,32 @@ import { type SemanticQuery, } from "./semantic-model.js"; import { SemanticSymbolFlags } from "./semantic-schema.generated.js"; +import type { TypePredicateKind } from "./enums.generated.js"; -export interface SemanticIndexInfo { - keyType: SemanticType; - valueType: SemanticType; - isReadonly: boolean; - declaration: SemanticNodeHandle | undefined; +/** An index signature of a type. Its declaration resolves on first read: most queries only count a + * type's index infos. */ +export class SemanticIndexInfo { + private declarationNode: AstNode | undefined; + private declarationResolved = false; + + constructor( + readonly keyType: SemanticType, + readonly type: SemanticType, + readonly isReadonly: boolean, + private readonly declarationHandle: SemanticNodeHandle | undefined, + ) {} + + get declaration(): AstNode | undefined { + if (!this.declarationResolved) { + this.declarationNode = this.declarationHandle?.resolve(); + this.declarationResolved = true; + } + return this.declarationNode; + } } export interface SemanticTypePredicate { - kind: number; + kind: TypePredicateKind; parameterIndex: number; parameterName: string | undefined; type: SemanticType | undefined; @@ -382,15 +398,17 @@ export class SemanticChecker { const data = this.project.request("getIndexInfosOfType", { type: type.id, }); - return (data ?? []).map((item) => ({ - keyType: this.project.type(item.keyType), - valueType: this.project.type(item.valueType), - isReadonly: item.isReadonly ?? false, - declaration: - item.declaration === undefined - ? undefined - : new SemanticNodeHandle(item.declaration, this.project), - })); + return (data ?? []).map( + (item) => + new SemanticIndexInfo( + this.project.type(item.keyType), + this.project.type(item.valueType), + item.isReadonly ?? false, + item.declaration === undefined + ? undefined + : new SemanticNodeHandle(item.declaration, this.project), + ), + ); } private getWellKnownSymbols(): WellKnownSymbols { this.project.ensureActive(); diff --git a/packages/compiler/src/frontend/ts7/semantic-model.test.ts b/packages/compiler/src/frontend/ts7/semantic-model.test.ts index e0230e8fe..36a445bc8 100644 --- a/packages/compiler/src/frontend/ts7/semantic-model.test.ts +++ b/packages/compiler/src/frontend/ts7/semantic-model.test.ts @@ -23,7 +23,13 @@ import { isUnionType, type Type, } from "typescript/unstable/sync"; -import { SemanticNodeHandle, SemanticSnapshot, SemanticType } from "./semantic-model.js"; +import { + SemanticNodeHandle, + SemanticSnapshot, + SemanticType, + symbolNameOf, +} from "./semantic-model.js"; +import type { __String } from "./ast-types.js"; import { SemanticObjectFlags as ObjectFlags, SemanticSignatureFlags as SignatureFlags, @@ -97,7 +103,7 @@ test("absent handles and empty known lists do not issue requests", () => { expect(type.getExtendsType()).toBeUndefined(); expect(type.getBaseType()).toBeUndefined(); expect(type.getConstraint()).toBeUndefined(); - expect(type.getTypes()).toBeUndefined(); + expect(type.getTypes()).toEqual([]); expect(type.getBaseTypes()).toBeUndefined(); expect(type.getTypeParameters()).toEqual([]); expect(type.getOuterTypeParameters()).toEqual([]); @@ -304,8 +310,8 @@ test("symbol tables keep escaped keys, display names, canonical context and cach answers.set("getExportSymbolOfSymbol", symbol(3)); const members = parent.getMembers(); const exports = parent.getExports(); - expect(members.get("___x")?.name).toBe("__x"); - expect(exports.get("__x")?.name).toBe("__x"); + expect(symbolNameOf(members.get("___x" as __String))).toBe("__x"); + expect(symbolNameOf(exports.get("__x" as __String))).toBe("__x"); expect(parent.getMembers()).toBe(members); expect(parent.getExports()).toBe(exports); expect(parent.getParent()).toBe(first.symbol(symbol(2))); diff --git a/packages/compiler/src/frontend/ts7/semantic-model.ts b/packages/compiler/src/frontend/ts7/semantic-model.ts index b0fa3da51..125e324c7 100644 --- a/packages/compiler/src/frontend/ts7/semantic-model.ts +++ b/packages/compiler/src/frontend/ts7/semantic-model.ts @@ -4,6 +4,8 @@ import type { SemanticTypeData as TypeResponse, } from "./semantic-schema.generated.js"; import { AstNode } from "./ast-node.js"; +import type { __String } from "./ast-types.js"; +import type * as enums from "./enums.generated.js"; import { parseAstNodeHandle } from "./ast-wire.js"; import type { SemanticChecker, SemanticJsDocTag } from "./semantic-checker.js"; import { parseSemanticJson } from "./semantic-json.js"; @@ -290,9 +292,9 @@ export class SemanticNodeHandle { export class SemanticSymbol { readonly id: number; - readonly escapedName: string; - readonly name: string; - readonly flags: number; + /** The key of the member and export tables the symbol is in; `symbolName` gives its name. */ + readonly escapedName: __String; + readonly flags: enums.SymbolFlags; readonly checkFlags: number; /** The declarations' node handles, as the server sends them. */ readonly declarationHandles: SemanticNodeHandle[]; @@ -302,16 +304,15 @@ export class SemanticSymbol { private valueDeclarationResolved = false; private readonly parent: number | undefined; private readonly exportSymbol: number | undefined; - private membersCache: Map | undefined; - private exportsCache: Map | undefined; + private membersCache: Map<__String, SemanticSymbol> | undefined; + private exportsCache: Map<__String, SemanticSymbol> | undefined; constructor( data: SymbolResponse, readonly canonicalProject: SemanticProject, ) { this.id = data.id; - this.escapedName = data.name; - this.name = data.name.startsWith("___") ? data.name.slice(1) : data.name; + this.escapedName = data.name as __String; this.flags = data.flags; this.checkFlags = data.checkFlags; this.declarationHandles = (data.declarations ?? []).map( @@ -362,18 +363,18 @@ export class SemanticSymbol { if (result === undefined) throw new Error(`Missing export symbol for ${this.id}`); return result; } - private table(method: string): Map { - const table = new Map(); + private table(method: string): Map<__String, SemanticSymbol> { + const table = new Map<__String, SemanticSymbol>(); for (const symbol of this.canonicalProject.fetchSymbols(this.id, method)) table.set(symbol.escapedName, symbol); return table; } - getMembers(): Map { + getMembers(): Map<__String, SemanticSymbol> { this.canonicalProject.ensureActive(); if (this.membersCache === undefined) this.membersCache = this.table("getMembersOfSymbol"); return this.membersCache; } - getExports(): Map { + getExports(): Map<__String, SemanticSymbol> { this.canonicalProject.ensureActive(); if (this.exportsCache === undefined) this.exportsCache = this.table("getExportsOfSymbol"); return this.exportsCache; @@ -392,10 +393,26 @@ export class SemanticSymbol { } } +/** The name of `symbol`, as TypeScript 7's API gives it: its escaped name, with the extra `_` of a + * name that starts with `__` removed. */ +export function symbolName(symbol: SemanticSymbol): string { + const name: string = symbol.escapedName; + return name.startsWith("___") ? name.slice(1) : name; +} + +/** `symbolName(symbol)`, or undefined without a symbol. */ +export function symbolNameOf(symbol: SemanticSymbol | null | undefined): string | undefined { + return symbol === undefined || symbol === null ? undefined : symbolName(symbol); +} + /** Type refinements keep the same native object, including its project and * lazy handle caches. Narrowed fields are read from their original storage. */ -export interface SemanticObjectType extends SemanticType { - readonly objectFlags: number; +export type SemanticObjectType = SemanticType; +export interface SemanticTypeParameter extends SemanticType { + readonly isThisType: boolean; +} +export interface SemanticTemplateLiteralType extends SemanticType { + readonly texts: string[]; } export interface SemanticIntrinsicType extends SemanticType { readonly intrinsicName: string; @@ -416,7 +433,7 @@ export interface SemanticBooleanLiteralType extends SemanticLiteralType { readonly value: boolean; } export interface SemanticTupleType extends SemanticObjectType { - readonly elementFlags: number[]; + readonly elementFlags: enums.ElementFlags[]; readonly fixedLength: number; readonly readonly: boolean; } @@ -424,12 +441,14 @@ export interface SemanticTupleType extends SemanticObjectType { /** Kind-specific accessors share one nominal layout. */ export class SemanticType { readonly id: number; - readonly flags: number; - readonly objectFlags: number | undefined; + readonly flags: enums.TypeFlags; + /** The object flags of an object type; 0 for other types, as TypeScript 7's API gives them. */ + readonly objectFlags: enums.ObjectFlags; readonly value: string | number | boolean | bigint | undefined; readonly intrinsicName: string | undefined; + /** Whether a type parameter is the `this` type; undefined for other types. */ readonly isThisType: boolean | undefined; - readonly elementFlags: number[] | undefined; + readonly elementFlags: enums.ElementFlags[] | undefined; readonly fixedLength: number | undefined; readonly readonly: boolean | undefined; readonly texts: string[] | undefined; @@ -444,7 +463,7 @@ export class SemanticType { ) { this.id = data.id; this.flags = data.flags; - this.objectFlags = data.objectFlags; + this.objectFlags = data.objectFlags ?? 0; const value = data.value; if ((data.flags & TypeFlags.BigIntLiteral) !== 0 && value != null) { if (typeof value !== "string") @@ -454,7 +473,8 @@ export class SemanticType { this.value = value ?? undefined; } this.intrinsicName = data.intrinsicName; - this.isThisType = data.isThisType; + this.isThisType = + (data.flags & TypeFlags.TypeParameter) !== 0 ? (data.isThisType ?? false) : undefined; this.elementFlags = data.elementFlags; this.fixedLength = data.fixedLength; this.readonly = data.readonly; @@ -494,26 +514,26 @@ export class SemanticType { getConstraint(): SemanticType | undefined { return this.project.fetchType(this.id, "getConstraintOfType", this.data.substConstraint); } - getTypeParameters(): SemanticType[] { + getTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getTypeParametersOfType", this.data.typeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } - getOuterTypeParameters(): SemanticType[] { + getOuterTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getOuterTypeParametersOfType", this.data.outerTypeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } - getLocalTypeParameters(): SemanticType[] { + getLocalTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getLocalTypeParametersOfType", this.data.localTypeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } getAliasTypeArguments(): SemanticType[] { return this.project.fetchTypes( @@ -522,10 +542,11 @@ export class SemanticType { this.data.aliasTypeArguments ?? [], ); } - getTypes(): SemanticType[] | undefined { + /** The members of a union or intersection, or the types of a template literal type; empty for + * other types. */ + getTypes(): SemanticType[] { this.project.ensureActive(); - if ((this.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) === 0) - return undefined; + if ((this.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) === 0) return []; if (this.constituentsPanic !== undefined) throw this.constituentsPanic; if (this.constituents === undefined) { try { @@ -589,7 +610,7 @@ export class SemanticType { return result; } isClassOrInterface(): boolean { - return this.isObjectType() && ((this.objectFlags ?? 0) & ObjectFlags.ClassOrInterface) !== 0; + return this.isObjectType() && (this.objectFlags & ObjectFlags.ClassOrInterface) !== 0; } isUnionType(): boolean { return (this.flags & TypeFlags.Union) !== 0; @@ -597,7 +618,7 @@ export class SemanticType { isIntersectionType(): boolean { return (this.flags & TypeFlags.Intersection) !== 0; } - isObjectType(): this is SemanticObjectType { + isObjectType(): boolean { return (this.flags & TypeFlags.Object) !== 0; } isIntrinsicType(): this is SemanticIntrinsicType { @@ -622,10 +643,10 @@ export class SemanticType { return (this.flags & TypeFlags.BooleanLiteral) !== 0; } isTypeReference(): boolean { - return this.isObjectType() && ((this.objectFlags ?? 0) & ObjectFlags.Reference) !== 0; + return this.isObjectType() && (this.objectFlags & ObjectFlags.Reference) !== 0; } - isTupleType(): boolean { - return this.isObjectType() && ((this.objectFlags ?? 0) & ObjectFlags.Tuple) !== 0; + isTupleType(): this is SemanticTupleType { + return this.isObjectType() && (this.objectFlags & ObjectFlags.Tuple) !== 0; } isIndexType(): boolean { return (this.flags & TypeFlags.Index) !== 0; @@ -639,20 +660,20 @@ export class SemanticType { isSubstitutionType(): boolean { return (this.flags & TypeFlags.Substitution) !== 0; } - isTemplateLiteralType(): boolean { + isTemplateLiteralType(): this is SemanticTemplateLiteralType { return (this.flags & TypeFlags.TemplateLiteral) !== 0; } isStringMappingType(): boolean { return (this.flags & TypeFlags.StringMapping) !== 0; } - isTypeParameter(): boolean { + isTypeParameter(): this is SemanticTypeParameter { return (this.flags & TypeFlags.TypeParameter) !== 0; } } export class SemanticSignature { readonly id: number; - readonly flags: number; + readonly flags: enums.SignatureFlags; /** The declaration's node handle, as the server sends it. */ readonly declarationHandle: SemanticNodeHandle | undefined; private declarationNode: AstNode | undefined; @@ -680,12 +701,12 @@ export class SemanticSignature { return this.declarationNode; } - getTypeParameters(): SemanticType[] { + getTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getTypeParametersOfSignature", this.data.typeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } getParameters(): SemanticSymbol[] { return this.project.fetchSymbols( diff --git a/packages/compiler/src/frontend/ts7/semantic-types.ts b/packages/compiler/src/frontend/ts7/semantic-types.ts index b4f3d5f2e..ea43e3885 100644 --- a/packages/compiler/src/frontend/ts7/semantic-types.ts +++ b/packages/compiler/src/frontend/ts7/semantic-types.ts @@ -1,27 +1,40 @@ /** Frontend names over the concrete native client. No SDK object or - * structural cast participates in the production checker boundary. */ + * structural cast participates in the production checker boundary. The + * names follow tsc-ts/api's. */ export type { SemanticType as Type, - SemanticType as InterfaceType, - SemanticType as TypeReference, SemanticType as UnionOrIntersectionType, SemanticType as UnionType, + SemanticType as IntersectionType, + SemanticType as FreshableType, + SemanticType as InterfaceType, + SemanticType as TypeReference, SemanticTupleType as TupleType, // A reference carries its element flags on the target, not necessarily on // itself. Claiming a tuple shape here makes a native presence test constant. - SemanticObjectType as TupleTypeReference, + SemanticType as TupleTypeReference, + SemanticLiteralType as LiteralType, SemanticStringLiteralType as StringLiteralType, SemanticNumberLiteralType as NumberLiteralType, + SemanticBigIntLiteralType as BigIntLiteralType, SemanticBooleanLiteralType as BooleanLiteralType, + SemanticIntrinsicType as IntrinsicType, SemanticObjectType as ObjectType, + SemanticTypeParameter as TypeParameter, + SemanticType as IndexType, + SemanticType as IndexedAccessType, + SemanticType as ConditionalType, + SemanticType as SubstitutionType, + SemanticTemplateLiteralType as TemplateLiteralType, + SemanticType as StringMappingType, SemanticSignature as Signature, SemanticSymbol as Symbol, } from "./semantic-model.js"; +export { symbolName, symbolNameOf } from "./semantic-model.js"; export type { SemanticIndexInfo as IndexInfo, SemanticTypePredicate as TypePredicate, } from "./semantic-checker.js"; -export type { - Ts7CompilerOptionsData as CompilerOptions, - Ts7DiagnosticData as Diagnostic, -} from "./session-schema.generated.js"; +export type { Ts7CompilerOptionsData as CompilerOptions } from "./session-schema.generated.js"; +export type { Ts7Diagnostic as Diagnostic } from "./diagnostic.js"; +export type { Ts7SourceMetadata as SourceFileMetadata } from "./session-schema.generated.js"; diff --git a/packages/compiler/src/frontend/ts7/session.test.ts b/packages/compiler/src/frontend/ts7/session.test.ts index 522b72fe6..d957fe0dd 100644 --- a/packages/compiler/src/frontend/ts7/session.test.ts +++ b/packages/compiler/src/frontend/ts7/session.test.ts @@ -14,6 +14,8 @@ import { spawnTs7Wire } from "./rpc-process.js"; import { tsgoExecutable } from "./rpc-api.js"; import { Ts7Session, Ts7SessionProject, Ts7SessionSnapshot } from "./session.js"; import { AstNode } from "./ast-node.js"; +import { ts7Diagnostic } from "./diagnostic.js"; +import { getTextOfNode } from "./node-functions.js"; const tempRoot = process.platform === "win32" ? tmpdir() : "/tmp"; const protocolPath = (path: string) => path.replaceAll("\\", "/"); @@ -104,8 +106,11 @@ test("owned snapshots preserve SDK options, diagnostics, metadata and default-pr "getSuggestionDiagnostics", "getDeclarationDiagnostics", ] as const) { - expect(project.program[name](file), name).toEqual(expected.program[name](file)); - expect(project.program[name](), name).toEqual(expected.program[name]()); + // scriptc's diagnostics have every member set (ts7Diagnostic); the SDK's leave absent ones out. + expect(project.program[name](file), name).toEqual( + expected.program[name](file).map(ts7Diagnostic), + ); + expect(project.program[name](), name).toEqual(expected.program[name]().map(ts7Diagnostic)); } for (const name of [ "getProgramDiagnostics", @@ -289,7 +294,7 @@ test("a released latest snapshot cannot lend stale ASTs to the same project", () source = "export const third = true;"; const third = session.updateSnapshot({ fileChanges: { created: [file] } }); expect(third.getProject(config)!.program.getSourceFile(file)!.text).toBe(source); - expect(old.statements.nodes[0]!.getText()).toBe("export const value = 1;"); + expect(getTextOfNode(old.statements.nodes[0]!)).toBe("export const value = 1;"); } finally { session.close(); rmSync(dir, { recursive: true, force: true }); diff --git a/packages/compiler/src/frontend/ts7/session.ts b/packages/compiler/src/frontend/ts7/session.ts index 4933da9d6..87b97d25e 100644 --- a/packages/compiler/src/frontend/ts7/session.ts +++ b/packages/compiler/src/frontend/ts7/session.ts @@ -1,3 +1,4 @@ +import { ts7Diagnostic, type Ts7Diagnostic } from "./diagnostic.js"; import type { SourceFile } from "./ast-types.js"; import { AstFile, AstNode } from "./ast-node.js"; import { Ts7RpcClient } from "./rpc-client.js"; @@ -314,31 +315,35 @@ export class Ts7SessionProgram { isSourceFileDefaultLibrary(file: AstNode): boolean { return this.getSourceFileMetadataByPath(file.path)?.isDefaultLibrary ?? false; } - private diagnostics(method: string, file?: SemanticDocument): Ts7DiagnosticData[] { - return this.session.request(method, this.query(file)) ?? []; + private diagnostics(method: string, file?: SemanticDocument): Ts7Diagnostic[] { + const result: Ts7Diagnostic[] = []; + for (const data of this.session.request(method, this.query(file)) ?? + []) + result.push(ts7Diagnostic(data)); + return result; } - getSyntacticDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getSyntacticDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getSyntacticDiagnostics", file); } - getBindDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getBindDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getBindDiagnostics", file); } - getSemanticDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getSemanticDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getSemanticDiagnostics", file); } - getSuggestionDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getSuggestionDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getSuggestionDiagnostics", file); } - getDeclarationDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getDeclarationDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getDeclarationDiagnostics", file); } - getProgramDiagnostics(): Ts7DiagnosticData[] { + getProgramDiagnostics(): Ts7Diagnostic[] { return this.diagnostics("getProgramDiagnostics"); } - getGlobalDiagnostics(): Ts7DiagnosticData[] { + getGlobalDiagnostics(): Ts7Diagnostic[] { return this.diagnostics("getGlobalDiagnostics"); } - getConfigFileParsingDiagnostics(): Ts7DiagnosticData[] { + getConfigFileParsingDiagnostics(): Ts7Diagnostic[] { return this.diagnostics("getConfigFileParsingDiagnostics"); } dispose(): void { diff --git a/packages/compiler/src/frontend/ts7/source-parser.test.ts b/packages/compiler/src/frontend/ts7/source-parser.test.ts index d5b54f080..b6950b3d5 100644 --- a/packages/compiler/src/frontend/ts7/source-parser.test.ts +++ b/packages/compiler/src/frontend/ts7/source-parser.test.ts @@ -8,6 +8,7 @@ import type { Ts7FileSystem } from "./rpc-filesystem.js"; import { Ts7SourceParser, type Ts7SourceKind } from "./source-parser.js"; import { closeSourceParser, parseSourceFile } from "./source-parser-node.js"; import * as ts from "./syntax.js"; +import { getTextOfNode, tokenStartOfNode } from "./node-functions.js"; const parsers: Ts7SourceParser[] = []; const directories: string[] = []; @@ -46,9 +47,9 @@ test("each parse owns its text and nodes across same-path edits, deletion and cl expect(restored.text).toBe(first.text); parser.close(); parser.close(); - expect(first.statements.nodes[0]!.getText()).toBe("export const first = 1;"); + expect(getTextOfNode(first.statements.nodes[0]!)).toBe("export const first = 1;"); expect(second.statements.nodes[0]!.parent).toBe(second); - expect(other.statements.nodes[0]!.getText()).toBe("exports.third = 3;"); + expect(getTextOfNode(other.statements.nodes[0]!)).toBe("exports.third = 3;"); expect(() => parser.parse("late.ts", "", "ts")).toThrow("closed"); }); @@ -114,9 +115,9 @@ for (const { name, kind, source } of syntaxCases) { expect(file.isDeclarationFile).toBe(oracle.isDeclarationFile); expect( file.statements.nodes.map((statement) => ({ - start: statement.getStart(), + start: tokenStartOfNode(statement), end: statement.end, - text: statement.getText(), + text: getTextOfNode(statement), })), ).toEqual( oracle.statements.map((statement) => ({ @@ -198,7 +199,11 @@ test("JSDoc callable types cannot replace method body views", () => { const expected = oracle.statements[0]!; if (!ts.isClassDeclaration(native) || !ts5.isClassDeclaration(expected)) throw new Error("expected class"); - expect(native.members.nodes.map((member) => member.body?.getText())).toEqual( + expect( + native.members.nodes.map((member) => + member.body === undefined ? undefined : getTextOfNode(member.body), + ), + ).toEqual( expected.members.map((member) => ts5.isMethodDeclaration(member) ? member.body?.getText() : undefined, ), @@ -231,7 +236,7 @@ test("a parser starts a new server once it has parsed its source budget, and ear expect(servers).toHaveLength(2); const third = parser.parse("b.ts", "export const b = 3;", "ts"); expect(servers).toHaveLength(2); - expect(first.statements.nodes[0]!.getText()).toBe(`export const a = ${"1 + ".repeat(20)}1;`); - expect(second.statements.nodes[0]!.getText()).toBe("export const b = 2;"); - expect(third.statements.nodes[0]!.getText()).toBe("export const b = 3;"); + expect(getTextOfNode(first.statements.nodes[0]!)).toBe(`export const a = ${"1 + ".repeat(20)}1;`); + expect(getTextOfNode(second.statements.nodes[0]!)).toBe("export const b = 2;"); + expect(getTextOfNode(third.statements.nodes[0]!)).toBe("export const b = 3;"); }); diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 4c3dd92bc..6c4f0bcef 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -69,7 +69,7 @@ export function jsOpenObjectType( shapes: ShapeRegistry, unions: UnionRegistry, ): IrType { - if (!decl || !isJsSourceFile(decl.getSourceFile())) return type; + if (!decl || !isJsSourceFile(ts.getSourceFileOfNode(decl))) return type; const arms = type.kind === "union" ? unions.get(type.unionId)?.arms : [type]; const present = arms?.filter((arm) => arm.kind !== "undefinedT"); const parameter = ts.isParameter(decl) @@ -1004,13 +1004,13 @@ export function isGeneralNodeStreamInterface( if (psym === undefined) return false; return psym.declarations.some((d) => { if (!(ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d))) return false; - if (!ctx.isStdlibFile(d.getSourceFile())) return false; - if (psym.name === "ReadableStream" || psym.name === "WritableStream") + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(d))) return false; + if (ts.symbolName(psym) === "ReadableStream" || ts.symbolName(psym) === "WritableStream") return ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"); - if (psym.name === "ChildStdin") return isDeclaredInAmbientModule(d, "child_process"); + if (ts.symbolName(psym) === "ChildStdin") return isDeclaredInAmbientModule(d, "child_process"); return ( - (psym.name === "Readable" || psym.name === "Writable") && - isNodeTypesPath(d.getSourceFile().fileName) && + (ts.symbolName(psym) === "Readable" || ts.symbolName(psym) === "Writable") && + isNodeTypesPath(ts.getSourceFileOfNode(d).fileName) && isDeclaredInAmbientModule(d, "stream") ); }); @@ -1027,7 +1027,7 @@ export function isWebAssemblyType( if (!symbol) return false; return symbol.declarations.some( (d) => - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d as ts.Declaration, "WebAssembly"), ); } @@ -1315,7 +1315,7 @@ export function isLegacyUrlSymbol( ): boolean { if (!symbol) return false; for (const d of symbol.declarations) { - if (!ctx.isStdlibFile(d.getSourceFile())) continue; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(d))) continue; let family = false; for (let n: ts.Node | undefined = d; n; n = n.parent) { if (ts.isInterfaceDeclaration(n) && LEGACY_URL_TYPES.has(n.name.text)) family = true; @@ -1355,7 +1355,10 @@ export function isOsConstantsSymbol( const alias = types && ts.isTypeAliasDeclaration(d) && d.name.text === "SignalConstants"; const signals = !types && ts.isVariableDeclaration(d) && ts.isIdentifier(d.name) && d.name.text === "signals"; - if (!(alias || signals || ts.isModuleDeclaration(d)) || !ctx.isStdlibFile(d.getSourceFile())) + if ( + !(alias || signals || ts.isModuleDeclaration(d)) || + !ctx.isStdlibFile(ts.getSourceFileOfNode(d)) + ) continue; let inConstants = ts.isModuleDeclaration(d) && d.name.text === "constants"; for (let n: ts.Node | undefined = d.parent; n; n = n.parent) { @@ -1378,14 +1381,14 @@ function isWorkerHandleSymbol( ctx: Pick, ): boolean { return ( - (symbol?.name === "Worker" || - symbol?.name === "MessagePort" || - symbol?.name === "MessageChannel" || - symbol?.name === "BroadcastChannel") && + ((symbol !== undefined && ts.symbolName(symbol) === "Worker") || + (symbol !== undefined && ts.symbolName(symbol) === "MessagePort") || + (symbol !== undefined && ts.symbolName(symbol) === "MessageChannel") || + (symbol !== undefined && ts.symbolName(symbol) === "BroadcastChannel")) && symbol.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "worker_threads"), ) ); @@ -1404,11 +1407,11 @@ function isLookupOptionsUnion(type: ts.Type, ctx: Pick ctx.isStdlibFile(d.getSourceFile())) + !/^Lookup(One|All)?Options$/.test(ts.symbolName(symbol)) || + !symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) return false; - if (symbol.name === "LookupAllOptions") all = true; + if (ts.symbolName(symbol) === "LookupAllOptions") all = true; } return all; } @@ -1455,12 +1458,12 @@ function isFetchBodyUnion(type: ts.Type, ctx: Pick ctx.isStdlibFile(d.getSourceFile())) + !FETCH_BODY_MEMBERS.has(ts.symbolName(symbol)) || + !symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) return false; - if (symbol.name === "FormData") formData = true; - if (symbol.name === "URLSearchParams") searchParams = true; + if (ts.symbolName(symbol) === "FormData") formData = true; + if (ts.symbolName(symbol) === "URLSearchParams") searchParams = true; } return formData && searchParams; } @@ -1480,7 +1483,7 @@ function isDynInterfaceSubset(type: ts.Type, ctx: TypeMapperCtx): boolean { if ( parent === undefined || !ts.isInterfaceDeclaration(parent) || - !ctx.isStdlibFile(parent.getSourceFile()) || + !ctx.isStdlibFile(ts.getSourceFileOfNode(parent)) || (owner !== undefined && owner !== parent) ) return false; @@ -1502,16 +1505,17 @@ function isFetchHeadersUnion(type: ts.Type, ctx: Pick ctx.isStdlibFile(d.getSourceFile())) + symbol !== undefined && + ts.symbolName(symbol) === "Headers" && + symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { headers = true; continue; } - const alias = member.getAliasSymbol()?.name; + const alias = ts.symbolNameOf(member.getAliasSymbol()); if ( - symbol?.name === "Array" || - symbol?.name === "ReadonlyArray" || + (symbol !== undefined && ts.symbolName(symbol) === "Array") || + (symbol !== undefined && ts.symbolName(symbol) === "ReadonlyArray") || alias === "Record" || alias === "HeaderRecord" ) { @@ -1540,7 +1544,7 @@ function isReceiveMessageResult( !fn || !ts.isFunctionDeclaration(fn) || fn.name?.text !== "receiveMessageOnPort" || - !ctx.isStdlibFile(decl.getSourceFile()) || + !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)) || !isDeclaredInAmbientModule(fn, "worker_threads") ) { return false; @@ -1584,9 +1588,9 @@ export function mapAsyncGeneratorAnnotation(type: ts.Type, ctx: TypeMapperCtx): const symbol = type.getSymbol(); if ( !symbol || - !["AsyncIterator", "AsyncIterableIterator", "AsyncIterable"].includes(symbol.name) || + !["AsyncIterator", "AsyncIterableIterator", "AsyncIterable"].includes(ts.symbolName(symbol)) || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return null; @@ -1615,11 +1619,13 @@ export function isFetchHandleType( const symbol = member.getSymbol(); if ( !symbol || - (symbol.name !== "Headers" && symbol.name !== "Request" && symbol.name !== "Response") || + (ts.symbolName(symbol) !== "Headers" && + ts.symbolName(symbol) !== "Request" && + ts.symbolName(symbol) !== "Response") || !symbol.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return false; @@ -1700,7 +1706,7 @@ function spreadsCheckedDynamic(symbol: ts.Symbol | undefined, ctx: TypeMapperCtx if (symbol === undefined || (symbol.flags & ts.SymbolFlags.ObjectLiteral) === 0) return false; const decl = symbol.declarations[0]; if (decl === undefined || !ts.isObjectLiteralExpression(decl)) return false; - if (isJsSourceFile(decl.getSourceFile())) return false; + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) return false; if (ctx.checker.getContextualType(decl)) return false; return decl.properties.nodes.some( (prop) => @@ -1714,7 +1720,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // TypeScript's intrinsic globalThis symbol has no declaration. A stored // reference needs native object identity, including var's undefined state. const intrinsic = type.getSymbol(); - if (intrinsic?.name === "globalThis" && intrinsic.declarations.length === 0) return DYN; + if ( + intrinsic !== undefined && + ts.symbolName(intrinsic) === "globalThis" && + intrinsic.declarations.length === 0 + ) + return DYN; if (ctx.jsDynamicDeclaration !== undefined && intrinsic !== undefined) { const objectLiteral = (intrinsic.flags & ts.SymbolFlags.ObjectLiteral) !== 0; const classInstance = @@ -1753,8 +1764,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // intersection mapping decomposes it into unsupported utility types. const directAlias = type.getAliasSymbol(); if ( - directAlias?.name === "ExecFileException" && - directAlias.declarations.some((decl) => ctx.isStdlibFile(decl.getSourceFile())) + directAlias !== undefined && + ts.symbolName(directAlias) === "ExecFileException" && + directAlias.declarations.some((decl) => ctx.isStdlibFile(ts.getSourceFileOfNode(decl))) ) { return { kind: "object", className: "%Error" }; } @@ -1763,11 +1775,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // ordinary Error as well. const execException = type.getSymbol(); if ( - execException?.name === "ExecException" && + execException !== undefined && + ts.symbolName(execException) === "ExecException" && execException.declarations.some( (decl) => ts.isInterfaceDeclaration(decl) && - ctx.isStdlibFile(decl.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(decl)) && isDeclaredInAmbientModule(decl, "child_process"), ) ) { @@ -1788,7 +1801,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if ( type.isIntersectionType() && ts.constituentTypes(type).every((t) => { - if (t.getSymbol()?.name !== "Promise") return false; + if (ts.symbolNameOf(t.getSymbol()) !== "Promise") return false; const arg = ctx.checker.getTypeArguments(t as ts.TypeReference)[0]; return arg !== undefined && isWebAssemblyType(arg, ctx); }) @@ -1807,7 +1820,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (ctx.erasingSignature || ctx.isErasing?.()) && !(type.flags & ts.TypeFlags.TypeParameter) && !checker.getCallSignatures(type).some((sig) => sig === ctx.erasingSignature) && - !ERASURE_KEPT_CONTAINERS.has(type.getSymbol()?.name ?? "") && + !ERASURE_KEPT_CONTAINERS.has(ts.symbolNameOf(type.getSymbol()) ?? "") && mentionsErasedTypeParam(type, ctx, 0) ) { contextResolutions++; @@ -1890,8 +1903,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // constructors. Only the standard library's interface gets this ABI. const errorOptionsSymbol = widened.getSymbol(); if ( - errorOptionsSymbol?.name === "ErrorOptions" && - errorOptionsSymbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + errorOptionsSymbol !== undefined && + ts.symbolName(errorOptionsSymbol) === "ErrorOptions" && + errorOptionsSymbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { return DYN; } @@ -1906,7 +1920,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { { const parseArgsSym = widened.getAliasSymbol() ?? widened.getSymbol(); const belongs = (declaration: ts.Node): boolean => { - if (!ctx.isStdlibFile(declaration.getSourceFile())) return false; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(declaration))) return false; let family = false; for (let node: ts.Node | undefined = declaration; node; node = node.parent) { if ( @@ -1922,7 +1936,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { }; if ( parseArgsSym && - PARSE_ARGS_DYN_TYPES.has(parseArgsSym.name) && + PARSE_ARGS_DYN_TYPES.has(ts.symbolName(parseArgsSym)) && parseArgsSym.declarations.some(belongs) ) return DYN; @@ -1942,10 +1956,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const boxSym = widened.getSymbol(); if ( boxSym && - (boxSym.name === "Number" || boxSym.name === "String" || boxSym.name === "Boolean") && - boxSym.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + (ts.symbolName(boxSym) === "Number" || + ts.symbolName(boxSym) === "String" || + ts.symbolName(boxSym) === "Boolean") && + boxSym.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { - return boxSym.name === "Number" ? F64 : boxSym.name === "String" ? STRING : BOOL; + return ts.symbolName(boxSym) === "Number" + ? F64 + : ts.symbolName(boxSym) === "String" + ? STRING + : BOOL; } } // The lib's PRIMITIVE-CONSTRUCTOR interfaces as TYPES (`typeof String`, @@ -1963,16 +1983,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const ctorSym = widened.getSymbol(); if ( ctorSym && - (ctorSym.name === "StringConstructor" || - ctorSym.name === "NumberConstructor" || - ctorSym.name === "BooleanConstructor") && - ctorSym.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + (ts.symbolName(ctorSym) === "StringConstructor" || + ts.symbolName(ctorSym) === "NumberConstructor" || + ts.symbolName(ctorSym) === "BooleanConstructor") && + ctorSym.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { return funcOf( [STRING], - ctorSym.name === "StringConstructor" + ts.symbolName(ctorSym) === "StringConstructor" ? STRING - : ctorSym.name === "NumberConstructor" + : ts.symbolName(ctorSym) === "NumberConstructor" ? F64 : BOOL, ); @@ -2076,7 +2096,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // declaration file. { const classSym = widened.getSymbol(); - const name = classSym?.name; + const name = ts.symbolNameOf(classSym); if (name === "LifetimeRegion" || name === "InternTable" || name === "ThreadSlots") { for (const d of classSym!.declarations) { if (!ts.isClassDeclaration(d)) continue; @@ -2104,7 +2124,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return decls && decls.length > 0 && decls.every((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isStdlibFile(sf) && !ctx.isExternalTypeFile(sf); }) ? decls @@ -2114,7 +2134,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { decls === null || decls.every( (d) => - packageTypeFile(d.getSourceFile(), ctx) && + packageTypeFile(ts.getSourceFileOfNode(d), ctx) && (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d) || ts.isTypeLiteralNode(d) || @@ -2321,18 +2341,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Nameless class DECLARATIONS are legal only as `export default class // {}` — classNamer spells them "%anon" (unique per file), the same // name their collection registered. - !classDecl.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(classDecl).isDeclarationFile && checker.getConstructSignatures(widened).length === 0 && // A TypeScript class declared OUTSIDE the lowered program (reached // through `import type` alone) never registers, and the program can // hold no instance of it: its instance type maps below as the // structural type it describes, like an interface. - (ctx.isProgramFile(classDecl.getSourceFile()) || isJsSourceFile(classDecl.getSourceFile())) + (ctx.isProgramFile(ts.getSourceFileOfNode(classDecl)) || + isJsSourceFile(ts.getSourceFileOfNode(classDecl))) ) { // A JavaScript class outside the lowered program (jsdoc `typeof // import()` over a never-required module) stays unmappable: JSDoc // types are not checked, so its slots fall back to the dynamic value. - if (!ctx.isProgramFile(classDecl.getSourceFile())) return null; + if (!ctx.isProgramFile(ts.getSourceFileOfNode(classDecl))) return null; // The class inside a MIXIN function: per-call-site instantiations // share this one node — the CURRENT instantiation (collecting or // lowering) is the answer; outside any mixin context the node's type @@ -2376,10 +2397,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if ( classDecl && (ts.isClassDeclaration(classDecl) || ts.isClassExpression(classDecl)) && - !classDecl.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(classDecl).isDeclarationFile && checker.getConstructSignatures(widened).length > 0 ) { - if (!ctx.isProgramFile(classDecl.getSourceFile())) return null; + if (!ctx.isProgramFile(ts.getSourceFileOfNode(classDecl))) return null; // The MIXIN class node's static side (`typeof C` inside the mixin): // the current instantiation's classval, like the instance type above. { @@ -2445,9 +2466,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // naming the offending half, as does the `new Map` lowering per site. const psym = widened.getSymbol(); const isStdlibInterface = (name: string): boolean => - psym?.name === name && + psym !== undefined && + ts.symbolName(psym) === name && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); // The builtin Error classes: references to the lib's Error interfaces map to the runtime-provided class // hierarchy (provenance, not the name — a user's own `class Error` @@ -2461,8 +2483,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // node:assert's AssertionError is declared as a class (in the shipped // fallback and in @types/node), not as a lib interface. if ( - psym?.name === "AssertionError" && - psym.declarations.some((d) => ts.isClassDeclaration(d) && ctx.isStdlibFile(d.getSourceFile())) + psym !== undefined && + ts.symbolName(psym) === "AssertionError" && + psym.declarations.some( + (d) => ts.isClassDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), + ) ) return { kind: "object", className: "%AssertionError" }; // The lib's `Object` interface is a TOP type: tsc admits every @@ -2479,12 +2504,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // representation. The node:module class's merged instance interface is // included, while unrelated user interfaces named Module stay structural. if ( - (psym?.name === "ScriptcModule" || psym?.name === "Module") && + ((psym !== undefined && ts.symbolName(psym) === "ScriptcModule") || + (psym !== undefined && ts.symbolName(psym) === "Module")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && - (psym.name === "ScriptcModule" || + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + (ts.symbolName(psym) === "ScriptcModule" || isDeclaredInAmbientNamespace(d, "NodeJS") || isDeclaredInAmbientModule(d, "module")), ) @@ -2499,11 +2525,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // NodeJS.EventEmitter interface (the structural surface net.Socket // et al. extend) is the same runtime value. Provenance-checked. if ( - psym?.name === "EventEmitter" && + psym !== undefined && + ts.symbolName(psym) === "EventEmitter" && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "events") || isDeclaredInAmbientNamespace(d, "NodeJS")), ) ) { @@ -2514,11 +2541,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // precedent). The NAME is generic, so the provenance check adds the // enclosing ambient module, like net.Server. if ( - psym?.name === "Interface" && + psym !== undefined && + ts.symbolName(psym) === "Interface" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "readline") || isDeclaredInAmbientModule(d, "readline/promises")), ) @@ -2529,11 +2557,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // it keeps Node's own shape (an undefined name, the escape sequences' // code member). if ( - psym?.name === "Key" && + psym !== undefined && + ts.symbolName(psym) === "Key" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "readline") || isDeclaredInAmbientModule(d, "readline/promises")), ) @@ -2542,11 +2571,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { } // readline/promises.Readline: an f64 handle the same way. if ( - psym?.name === "Readline" && + psym !== undefined && + ts.symbolName(psym) === "Readline" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "readline/promises"), ) ) { @@ -2559,11 +2589,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // plus the ambient module suffices for both the fallback declarations // and @types/node's class Channel. if ( - psym?.name === "Channel" && + psym !== undefined && + ts.symbolName(psym) === "Channel" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "diagnostics_channel"), ) ) { @@ -2573,11 +2604,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // the runtime's store id space (the Channel story — stores are // process-lived; contexts ride the fiber machinery). if ( - psym?.name === "AsyncLocalStorage" && + psym !== undefined && + ts.symbolName(psym) === "AsyncLocalStorage" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "async_hooks"), ) ) { @@ -2586,11 +2618,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // diagnostics_channel.TracingChannel: the same f64-handle story over the // runtime's tracing registry (five event channels per entry). if ( - psym?.name === "TracingChannel" && + psym !== undefined && + ts.symbolName(psym) === "TracingChannel" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "diagnostics_channel"), ) ) { @@ -2600,7 +2633,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const encodeResult = psym && psym.declarations.some((decl) => { - if (!ctx.isStdlibFile(decl.getSourceFile())) return false; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(decl))) return false; if (ts.isInterfaceDeclaration(decl) && decl.name.text === "EncodeIntoResult") return true; const parent = decl.parent; return ( @@ -2625,19 +2658,20 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // WHATWG codec instances have ordinary reference identity and ownership. // Their private encoding slot is omitted from enumeration and JSON. if ( - (psym?.name === "TextEncoder" || psym?.name === "TextDecoder") && + ((psym !== undefined && ts.symbolName(psym) === "TextEncoder") || + (psym !== undefined && ts.symbolName(psym) === "TextDecoder")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "record", shapeId: ctx.shapes.intern( [ - { name: `%${psym.name}`, type: F64 }, - ...(psym.name === "TextDecoder" + { name: `%${ts.symbolName(psym)}`, type: F64 }, + ...(ts.symbolName(psym) === "TextDecoder" ? [ { name: "%fatal", type: BOOL }, { name: "%ignoreBOM", type: BOOL }, @@ -2659,11 +2693,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // special-cased in lowerNew / lowerStringDecoderMethodCall, so the // record shape never surfaces. if ( - psym?.name === "StringDecoder" && + psym !== undefined && + ts.symbolName(psym) === "StringDecoder" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { @@ -2688,14 +2723,14 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if ( flags & ts.TypeFlags.Object && promiseDeclarations.length > 0 && - promiseDeclarations.every((d) => d.getSourceFile().isDeclarationFile) + promiseDeclarations.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile) ) { const props = checker.getPropertiesOfType(widened); if ( props.length === 3 && - ["promise", "resolve", "reject"].every((n) => props.some((p) => p.name === n)) + ["promise", "resolve", "reject"].every((n) => props.some((p) => ts.symbolName(p) === n)) ) { - const promProp = props.find((p) => p.name === "promise")!; + const promProp = props.find((p) => ts.symbolName(p) === "promise")!; const promT = mapType(checker.getTypeOfSymbol(promProp), ctx); if (promT?.kind === "promise") { const inner = promT.inner; @@ -2744,9 +2779,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // option record whose members include functions, agents and signals. // Values ride checked-dynamic storage, like SpawnOptions. if ( - psym?.name === "RequestOptions" && + psym !== undefined && + ts.symbolName(psym) === "RequestOptions" && psym.declarations.some((d) => { - if (!ts.isInterfaceDeclaration(d) || !ctx.isStdlibFile(d.getSourceFile())) return false; + if (!ts.isInterfaceDeclaration(d) || !ctx.isStdlibFile(ts.getSourceFileOfNode(d))) + return false; for (let p: ts.Node | undefined = d.parent; p !== undefined; p = p.parent) { if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) return HTTP_REQUEST_OPTION_MODULES.has(p.name.text); @@ -2769,9 +2806,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { { const alias = widened.getAliasSymbol(); if ( - alias?.name === "ReadableStreamReadResult" && + alias !== undefined && + ts.symbolName(alias) === "ReadableStreamReadResult" && alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const elemTs = widened.getAliasTypeArguments()?.[0]; @@ -2783,10 +2821,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { } if ( psym && - (psym.name === "ReadableStreamReadValueResult" || - psym.name === "ReadableStreamReadDoneResult") && + (ts.symbolName(psym) === "ReadableStreamReadValueResult" || + ts.symbolName(psym) === "ReadableStreamReadDoneResult") && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const elemTs = checker.getTypeArguments(widened as ts.TypeReference)[0]; @@ -2798,11 +2836,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // node:net's SocketAddress and BlockList: native handles (scr_net.c) // whose getters and methods dispatch through the handle. if ( - (psym?.name === "SocketAddress" || psym?.name === "BlockList") && + ((psym !== undefined && ts.symbolName(psym) === "SocketAddress") || + (psym !== undefined && ts.symbolName(psym) === "BlockList")) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2813,11 +2852,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // checked-dynamic objects. if ( psym && - WEBCRYPTO_DYN_TYPES.has(psym.name) && + WEBCRYPTO_DYN_TYPES.has(ts.symbolName(psym)) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2826,20 +2865,21 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // whose getters and methods dispatch through the handle. The JsonWebKey // that export() returns is a plain checked-dynamic object. if ( - (psym?.name === "KeyObject" || psym?.name === "JsonWebKey") && + ((psym !== undefined && ts.symbolName(psym) === "KeyObject") || + (psym !== undefined && ts.symbolName(psym) === "JsonWebKey")) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; } if ( psym && - (FETCH_AMBIENT_TYPES as readonly string[]).includes(psym.name) && + (FETCH_AMBIENT_TYPES as readonly string[]).includes(ts.symbolName(psym)) && !( - psym.name === "ReadableStream" && + ts.symbolName(psym) === "ReadableStream" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"), ) @@ -2847,7 +2887,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2860,12 +2900,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Symbol.asyncDispose method. A DisposableStack is a native handle whose // methods are function values of its checked-dynamic view. if ( - (psym?.name === "Console" || - psym?.name === "Disposable" || - psym?.name === "AsyncDisposable" || - psym?.name === "DisposableStack") && + ((psym !== undefined && ts.symbolName(psym) === "Console") || + (psym !== undefined && ts.symbolName(psym) === "Disposable") || + (psym !== undefined && ts.symbolName(psym) === "AsyncDisposable") || + (psym !== undefined && ts.symbolName(psym) === "DisposableStack")) && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2875,8 +2915,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // representation; lowerCipherMethodCall dispatches their methods. if ( psym !== undefined && - CIPHER_TYPE_NAMES.has(psym.name) && - psym.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + CIPHER_TYPE_NAMES.has(ts.symbolName(psym)) && + psym.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { return DYN; } @@ -3003,11 +3043,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // fallback and by @types/node (whose NonSharedBuffer alias resolves to // the same symbol); provenance-checked like URL. if ( - psym?.name === "Buffer" && + psym !== undefined && + ts.symbolName(psym) === "Buffer" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return bytesOf("u8"); @@ -3018,11 +3059,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // instance type). Provenance, not the name: a user's own URL maps as a // record/class like any other. if ( - psym?.name === "URL" && + psym !== undefined && + ts.symbolName(psym) === "URL" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "url" }; @@ -3031,11 +3073,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // interface by the shipped fallback and by @types/node (plus `class // URLSearchParams` in the "url" module). Provenance-checked like URL. if ( - psym?.name === "URLSearchParams" && + psym !== undefined && + ts.symbolName(psym) === "URLSearchParams" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "searchParams" }; @@ -3043,11 +3086,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // fs.Stats: @types/node's `class Stats` (module "fs") or the fallback // declarations' interface. Provenance-checked like URL. if ( - psym?.name === "Stats" && + psym !== undefined && + ts.symbolName(psym) === "Stats" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "stats" }; @@ -3055,13 +3099,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Capacity snapshots and options keep their live object representation // across numeric/BigInt overloads, runtime options, and callbacks. if ( - (psym?.name === "StatsFs" || - psym?.name === "BigIntStatsFs" || - psym?.name === "StatFsOptions") && + ((psym !== undefined && ts.symbolName(psym) === "StatsFs") || + (psym !== undefined && ts.symbolName(psym) === "BigIntStatsFs") || + (psym !== undefined && ts.symbolName(psym) === "StatFsOptions")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) @@ -3071,11 +3115,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // distinguishes it from user interfaces with the same name; both the // fallback and @types/node declare it in "fs/promises". if ( - psym?.name === "FileHandle" && + psym !== undefined && + ts.symbolName(psym) === "FileHandle" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs/promises"), ) ) { @@ -3084,11 +3129,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // fs.Dir: an open directory stream, declared in "fs" by both the // fallback and @types/node. if ( - psym?.name === "Dir" && + psym !== undefined && + ts.symbolName(psym) === "Dir" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) { @@ -3099,11 +3145,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // reads work either way) or the fallback declarations' plain interface. // Provenance-checked like Stats. if ( - psym?.name === "SpawnSyncReturns" && + psym !== undefined && + ts.symbolName(psym) === "SpawnSyncReturns" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "spawnRes" }; @@ -3113,13 +3160,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // refinements only tell the checker which stdio properties are null. if ( psym !== undefined && - (psym.name === "ChildProcess" || - psym.name === "ChildProcessWithoutNullStreams" || - psym.name === "ChildProcessByStdio") && + (ts.symbolName(psym) === "ChildProcess" || + ts.symbolName(psym) === "ChildProcessWithoutNullStreams" || + ts.symbolName(psym) === "ChildProcessByStdio") && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "child_process"), ) ) { @@ -3133,25 +3180,27 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // that module (http's Server) stays unmapped and fences at its use // site. if ( - (psym?.name === "Server" || psym?.name === "Socket" || psym?.name === "TLSSocket") && + ((psym !== undefined && ts.symbolName(psym) === "Server") || + (psym !== undefined && ts.symbolName(psym) === "Socket") || + (psym !== undefined && ts.symbolName(psym) === "TLSSocket")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "net") || // http.Server extends net.Server in @types/node — and tls.Server // / https.Server extend it transitively: same lowered surface // (listen/close/address/'error'), same handle kind. tls.TLSSocket // extends net.Socket the same way: post-handshake it IS a net // socket to every lowered member. - (psym.name === "Server" && + (ts.symbolName(psym) === "Server" && (isDeclaredInAmbientModule(d, "http") || isDeclaredInAmbientModule(d, "tls") || isDeclaredInAmbientModule(d, "https"))) || - (psym.name === "TLSSocket" && isDeclaredInAmbientModule(d, "tls"))), + (ts.symbolName(psym) === "TLSSocket" && isDeclaredInAmbientModule(d, "tls"))), ) ) { - return psym.name === "Server" ? { kind: "netServer" } : { kind: "netSocket" }; + return ts.symbolName(psym) === "Server" ? { kind: "netServer" } : { kind: "netSocket" }; } // tls.SecureContext: the opaque parsed-cert/key handle SNI callbacks // answer with (tls.createSecureContext({ cert, key }) mints it; the TLS @@ -3159,11 +3208,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node declares `interface SecureContext` inside `declare module // "tls"`, and the shipped fallback mirrors it. if ( - psym?.name === "SecureContext" && + psym !== undefined && + ts.symbolName(psym) === "SecureContext" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "tls"), ) ) { @@ -3172,11 +3222,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Scrypt's runtime options retain property accessors and aliases. Use // checked values so presence probes and repeated getter reads match Node. if ( - psym?.name === "ScryptOptions" && + psym !== undefined && + ts.symbolName(psym) === "ScryptOptions" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "crypto"), ) ) @@ -3185,26 +3236,28 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // provenance module-qualified so user classes with these common names // retain their ordinary structural/class representation. if ( - (psym?.name === "Hash" || psym?.name === "Hmac") && + ((psym !== undefined && ts.symbolName(psym) === "Hash") || + (psym !== undefined && ts.symbolName(psym) === "Hmac")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "crypto"), ) ) { - return psym.name === "Hash" ? CRYPTOHASH_T : CRYPTOHMAC_T; + return ts.symbolName(psym) === "Hash" ? CRYPTOHASH_T : CRYPTOHMAC_T; } // fs.FSWatcher: the fs.watch handle (scr_watch.c). Module-checked like // net.Server — @types/node declares `interface FSWatcher extends // EventEmitter` inside `declare module "fs"`, and the shipped fallback // mirrors it. if ( - psym?.name === "FSWatcher" && + psym !== undefined && + ts.symbolName(psym) === "FSWatcher" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) { @@ -3239,11 +3292,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ts.constituentTypes(type).some((part) => { const sym = part.getSymbol(); return ( - (sym?.name === "ReadableStream" || sym?.name === "WritableStream") && + ((sym !== undefined && ts.symbolName(sym) === "ReadableStream") || + (sym !== undefined && ts.symbolName(sym) === "WritableStream")) && sym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d, "NodeJS"), ) ); @@ -3252,22 +3306,24 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return DYN; } const nodeWritableInterface = - psym?.name === "WritableStream" && + psym !== undefined && + ts.symbolName(psym) === "WritableStream" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d, "NodeJS"), ); if (nodeWritableInterface && ctx.generalNodeStreams === true) { return ctx.dynNodeStreams === true ? DYN : { kind: "object", className: "%Writable" }; } if ( - (psym?.name === "WriteStream" && + (psym !== undefined && + ts.symbolName(psym) === "WriteStream" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "tty") || (isDeclaredInAmbientModule(d, "process") && isDeclaredInAmbientNamespace(d, "NodeJS"))), )) || @@ -3280,21 +3336,23 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // stream.Writable. As with stream.Readable below, unsupported producers // of the broad @types class fence where they are constructed. if ( - (psym?.name === "ChildStdin" && + (psym !== undefined && + ts.symbolName(psym) === "ChildStdin" && ctx.generalNodeStreams !== true && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "child_process"), )) || - (psym?.name === "Writable" && + (psym !== undefined && + ts.symbolName(psym) === "Writable" && ctx.generalNodeStreams !== true && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && - isNodeTypesPath(d.getSourceFile().fileName) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + isNodeTypesPath(ts.getSourceFileOfNode(d).fileName) && isDeclaredInAmbientModule(d, "stream"), )) ) { @@ -3304,11 +3362,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // ChildStdin is the runtime Writable too (child.stdioStream bridges it). if ( ctx.generalNodeStreams === true && - psym?.name === "ChildStdin" && + psym !== undefined && + ts.symbolName(psym) === "ChildStdin" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "child_process"), ) ) { @@ -3326,12 +3385,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Writable join them under the general representation. for (const [irName, rec] of RUNTIME_STREAM_CLASSES) { if ( - psym?.name === rec.lib && + psym !== undefined && + ts.symbolName(psym) === rec.lib && psym.declarations.some( (d) => ts.isClassDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && - (!isNodeTypesPath(d.getSourceFile().fileName) || + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + (!isNodeTypesPath(ts.getSourceFileOfNode(d).fileName) || ctx.generalNodeStreams === true || (rec.lib !== "Readable" && rec.lib !== "Writable")) && isDeclaredInAmbientModule(d, "stream"), @@ -3344,12 +3404,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node's): their instances are runtime Transform streams. if ( psym !== undefined && - ZLIB_STREAM_MODES.has(psym.name) && + ZLIB_STREAM_MODES.has(ts.symbolName(psym)) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || - (ts.isInterfaceDeclaration(d) && isNodeTypesPath(d.getSourceFile().fileName))) && - ctx.isStdlibFile(d.getSourceFile()) && + (ts.isInterfaceDeclaration(d) && isNodeTypesPath(ts.getSourceFileOfNode(d).fileName))) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "zlib"), ) ) { @@ -3359,29 +3419,35 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node's): their instances are runtime Readable / Writable // streams (scr_fs_stream.c). if ( - (psym?.name === "ReadStream" || psym?.name === "WriteStream") && + ((psym !== undefined && ts.symbolName(psym) === "ReadStream") || + (psym !== undefined && ts.symbolName(psym) === "WriteStream")) && psym.declarations.some( (d) => ts.isClassDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) { - return { kind: "object", className: psym.name === "ReadStream" ? "%Readable" : "%Writable" }; + return { + kind: "object", + className: ts.symbolName(psym) === "ReadStream" ? "%Readable" : "%Writable", + }; } if ( - (psym?.name === "Readable" && + (psym !== undefined && + ts.symbolName(psym) === "Readable" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "stream"), )) || - (psym?.name === "ReadableStream" && + (psym !== undefined && + ts.symbolName(psym) === "ReadableStream" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d, "NodeJS"), )) ) { @@ -3390,7 +3456,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // checked-dynamic stream slots, NodeJS.ReadableStream holds any // stream value (dyn-streams.ts); stream.Readable keeps the class. if (ctx.generalNodeStreams === true) { - if (ctx.dynNodeStreams === true && psym.name === "ReadableStream") return DYN; + if (ctx.dynNodeStreams === true && ts.symbolName(psym) === "ReadableStream") return DYN; return { kind: "object", className: "%Readable" }; } return { kind: "childStream" }; @@ -3400,11 +3466,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // EventEmitter` and the fallback declarations' interface both live // inside `declare module "dgram"`. if ( - psym?.name === "Socket" && + psym !== undefined && + ts.symbolName(psym) === "Socket" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "dgram"), ) ) { @@ -3417,11 +3484,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // `declare module "node:test"` (isDeclaredInAmbientModule answers for // both spellings). if ( - (psym?.name === "TestContext" || psym?.name === "SuiteContext") && + ((psym !== undefined && ts.symbolName(psym) === "TestContext") || + (psym !== undefined && ts.symbolName(psym) === "SuiteContext")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "test"), ) ) { @@ -3431,18 +3499,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // records built from F (mock-shapes.ts). Mock then maps as the hybrid // { %call: F, mock } record through mapHybridCallableIntersection. if ( - (psym?.name === "MockFunctionContext" || psym?.name === "MockFunctionCall") && + ((psym !== undefined && ts.symbolName(psym) === "MockFunctionContext") || + (psym !== undefined && ts.symbolName(psym) === "MockFunctionCall")) && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "test"), ) ) { const fnTs = checker.getTypeArguments(widened as ts.TypeReference)[0]; const fn = fnTs ? mapType(fnTs, ctx) : null; if (!fn || fn.kind !== "func") return null; - return psym.name === "MockFunctionContext" + return ts.symbolName(psym) === "MockFunctionContext" ? mockContextType(fn, ctx) : mockCallRecordType(fn, ctx); } @@ -3452,9 +3521,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // above; optional metadata uses the same checked property view. // Provenance-checked like the rest. if ( - psym?.name === "ErrnoException" && + psym !== undefined && + ts.symbolName(psym) === "ErrnoException" && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "object", className: "%Error" }; @@ -3466,11 +3536,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // of fencing at the declaration. Module-checked like the rest (https' // Agent extends http's; both live in their ambient modules). if ( - psym?.name === "Agent" && + psym !== undefined && + ts.symbolName(psym) === "Agent" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "http") || isDeclaredInAmbientModule(d, "https")), ) ) { @@ -3480,15 +3551,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // net.Server (the names repeat nowhere today, but provenance stays the // rule). if ( - (psym?.name === "IncomingMessage" || psym?.name === "ServerResponse") && + ((psym !== undefined && ts.symbolName(psym) === "IncomingMessage") || + (psym !== undefined && ts.symbolName(psym) === "ServerResponse")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http"), ) ) { - return psym.name === "IncomingMessage" ? { kind: "httpReq" } : { kind: "httpRes" }; + return ts.symbolName(psym) === "IncomingMessage" ? { kind: "httpReq" } : { kind: "httpRes" }; } // http2's compatibility surface (the allowHTTP1 lowering): the secure // server IS a net server (listen/close/address/emit ride the same @@ -3499,19 +3571,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // member at their use sites (lower-server.ts). Module-checked like the // rest. if ( - (psym?.name === "Http2SecureServer" || - psym?.name === "Http2ServerRequest" || - psym?.name === "Http2ServerResponse") && + ((psym !== undefined && ts.symbolName(psym) === "Http2SecureServer") || + (psym !== undefined && ts.symbolName(psym) === "Http2ServerRequest") || + (psym !== undefined && ts.symbolName(psym) === "Http2ServerResponse")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http2"), ) ) { - return psym.name === "Http2SecureServer" + return ts.symbolName(psym) === "Http2SecureServer" ? { kind: "netServer" } - : psym.name === "Http2ServerRequest" + : ts.symbolName(psym) === "Http2ServerRequest" ? { kind: "httpReq" } : { kind: "httpRes" }; } @@ -3520,33 +3592,34 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // kinds (client and server flavors share a kind — the runtime handle // carries the role). Module-checked like the rest. if ( - (psym?.name === "Http2Server" || - psym?.name === "Http2Session" || - psym?.name === "ClientHttp2Session" || - psym?.name === "ServerHttp2Session" || - psym?.name === "ClientHttp2Stream" || - psym?.name === "ServerHttp2Stream") && + ((psym !== undefined && ts.symbolName(psym) === "Http2Server") || + (psym !== undefined && ts.symbolName(psym) === "Http2Session") || + (psym !== undefined && ts.symbolName(psym) === "ClientHttp2Session") || + (psym !== undefined && ts.symbolName(psym) === "ServerHttp2Session") || + (psym !== undefined && ts.symbolName(psym) === "ClientHttp2Stream") || + (psym !== undefined && ts.symbolName(psym) === "ServerHttp2Stream")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http2"), ) ) { - return psym.name === "Http2Server" + return ts.symbolName(psym) === "Http2Server" ? { kind: "netServer" } - : psym.name === "ClientHttp2Stream" || psym.name === "ServerHttp2Stream" + : ts.symbolName(psym) === "ClientHttp2Stream" || ts.symbolName(psym) === "ServerHttp2Stream" ? { kind: "http2Stream" } : { kind: "http2Session" }; } // http.ClientRequest (http.request/http.get results) — module-checked // like IncomingMessage. if ( - psym?.name === "ClientRequest" && + psym !== undefined && + ts.symbolName(psym) === "ClientRequest" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http"), ) ) { @@ -3561,9 +3634,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // declarations' anonymous env type takes the pureIndexShape path below // and interns the SAME shape. if ( - psym?.name === "ProcessEnv" && + psym !== undefined && + ts.symbolName(psym) === "ProcessEnv" && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const value = withUndefinedArm(STRING, ctx.unions); @@ -3653,9 +3727,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { { const alias = widened.getAliasSymbol(); if ( - alias?.name === "IteratorResult" && + alias !== undefined && + ts.symbolName(alias) === "IteratorResult" && alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const targs = widened.getAliasTypeArguments() ?? []; @@ -3775,10 +3850,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ]; const fields: { name: string; type: IrType }[] = []; for (const property of checker.getPropertiesOfType(widened)) { - if (!known.includes(property.name)) return null; + if (!known.includes(ts.symbolName(property))) return null; const type = mapType(checker.getTypeOfSymbol(property), ctx); if (!type) return null; - fields.push({ name: property.name, type }); + fields.push({ name: ts.symbolName(property), type }); } const order = fields.map((field) => field.name); fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); @@ -3787,23 +3862,24 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Filesystem options are data records. Keep their declared optional fields // instead of allowing arbitrary standard-library interfaces into records. if ( - (psym?.name === "FileReadOptions" || - psym?.name === "ReadOptions" || - psym?.name === "ReadOptionsWithBuffer" || - psym?.name === "WriteOptions") && + ((psym !== undefined && ts.symbolName(psym) === "FileReadOptions") || + (psym !== undefined && ts.symbolName(psym) === "ReadOptions") || + (psym !== undefined && ts.symbolName(psym) === "ReadOptionsWithBuffer") || + (psym !== undefined && ts.symbolName(psym) === "WriteOptions")) && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "fs/promises") || isDeclaredInAmbientModule(d, "fs")), ) ) { const fields: { name: string; type: IrType }[] = []; for (const property of checker.getPropertiesOfType(widened)) { - if (!["buffer", "offset", "length", "position"].includes(property.name)) return null; + if (!["buffer", "offset", "length", "position"].includes(ts.symbolName(property))) + return null; const type = mapType(checker.getTypeOfSymbol(property), ctx); if (!type) return null; - fields.push({ name: property.name, type }); + fields.push({ name: ts.symbolName(property), type }); } const order = fields.map((field) => field.name); fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); @@ -3814,28 +3890,28 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // record does not model the prototype (unobservable through the lowered // surface); it preserves the caller buffer/string by reference. if ( - (psym?.name === "FileReadResult" || - psym?.name === "FileWriteResult" || - psym?.name === "FileReadvResult" || - psym?.name === "FileWritevResult" || - psym?.name === "ReadVResult" || - psym?.name === "WriteVResult") && + ((psym !== undefined && ts.symbolName(psym) === "FileReadResult") || + (psym !== undefined && ts.symbolName(psym) === "FileWriteResult") || + (psym !== undefined && ts.symbolName(psym) === "FileReadvResult") || + (psym !== undefined && ts.symbolName(psym) === "FileWritevResult") || + (psym !== undefined && ts.symbolName(psym) === "ReadVResult") || + (psym !== undefined && ts.symbolName(psym) === "WriteVResult")) && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "fs/promises") || - ((psym.name === "ReadVResult" || psym.name === "WriteVResult") && + ((ts.symbolName(psym) === "ReadVResult" || ts.symbolName(psym) === "WriteVResult") && isDeclaredInAmbientModule(d, "fs"))), ) ) { const args = checker.getTypeArguments(widened as ts.TypeReference); if (args.length !== 1) return null; const vector = - psym.name === "FileReadvResult" || - psym.name === "FileWritevResult" || - psym.name === "ReadVResult" || - psym.name === "WriteVResult"; + ts.symbolName(psym) === "FileReadvResult" || + ts.symbolName(psym) === "FileWritevResult" || + ts.symbolName(psym) === "ReadVResult" || + ts.symbolName(psym) === "WriteVResult"; const emptyVector = vector && checker.isArrayType(args[0]!) && @@ -3850,9 +3926,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return null; } const count = - psym.name === "FileReadResult" || - psym.name === "FileReadvResult" || - psym.name === "ReadVResult" + ts.symbolName(psym) === "FileReadResult" || + ts.symbolName(psym) === "FileReadvResult" || + ts.symbolName(psym) === "ReadVResult" ? "bytesRead" : "bytesWritten"; const payloadName = vector ? "buffers" : "buffer"; @@ -3884,11 +3960,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // declares it concrete (an interface; @types/node uses a class — both // provenance-checked, the Buffer/URL pattern). if ( - psym?.name === "Dirent" && + psym !== undefined && + ts.symbolName(psym) === "Dirent" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const args = checker.getTypeArguments(widened as ts.TypeReference); @@ -3945,7 +4022,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { d.parent.name.text === "times" && ts.isInterfaceDeclaration(d.parent.parent) && d.parent.parent.name.text === "CpuInfo" && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return cpuTimes(); @@ -3966,7 +4043,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { }; } if (isStdlibInterface("ParsedPath") || isStdlibInterface("FormatInputPathObject")) { - const field = psym!.name === "ParsedPath" ? STRING : withUndefinedArm(STRING, ctx.unions); + const field = + ts.symbolName(psym!) === "ParsedPath" ? STRING : withUndefinedArm(STRING, ctx.unions); if (!field) return null; return { kind: "record", @@ -4017,7 +4095,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { psym.declarations.some( (d) => ts.isTypeLiteralNode(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "child_process") || isDeclaredInAmbientModule(d, "util") || isDeclaredInAmbientModule(d, "dns")), @@ -4032,7 +4110,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.String) !== 0, ) ? props - .map((p) => p.name) + .map((p) => ts.symbolName(p)) .sort() .join(",") : ""; @@ -4060,11 +4138,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // JSON.stringify of one shows the three fields where Node's // internal-slot object shows {} — the Dirent %dtype edge's cousin. if ( - psym?.name === "X509Certificate" && + psym !== undefined && + ts.symbolName(psym) === "X509Certificate" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "crypto"), ) ) { @@ -4091,13 +4170,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node declares the classes in perf_hooks and global interfaces // over them; the shipped fallback declares the classes. if ( - (psym?.name === "PerformanceEntry" || - psym?.name === "PerformanceMark" || - psym?.name === "PerformanceMeasure") && + ((psym !== undefined && ts.symbolName(psym) === "PerformanceEntry") || + (psym !== undefined && ts.symbolName(psym) === "PerformanceMark") || + (psym !== undefined && ts.symbolName(psym) === "PerformanceMeasure")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "record", shapeId: perfEntryShape(ctx.shapes, ctx.unions) }; @@ -4254,7 +4333,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { isStdlibInterface("NetworkInterfaceInfoIPv4") || isStdlibInterface("NetworkInterfaceInfoIPv6") ) { - const v6 = psym!.name === "NetworkInterfaceInfoIPv6"; + const v6 = ts.symbolName(psym!) === "NetworkInterfaceInfoIPv6"; const cidrArms = [NULL_T, STRING].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); const cidr: IrType = { kind: "union", unionId: ctx.unions.intern(cidrArms) }; const scopeid = v6 ? F64 : withUndefinedArm(F64, ctx.unions); @@ -4402,7 +4481,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { psym !== undefined && psym.declarations.length > 0 && psym.declarations.every( - (d) => ts.isInterfaceDeclaration(d) && !ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && !ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const CHILD_SURFACE = new Set([ @@ -4437,24 +4516,26 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const props = checker.getPropertiesOfType(widened); let core = 0; const childMemberType = (p: ts.Symbol): boolean => { - if (["kill", "on", "once", "off", "removeListener", "unref", "ref"].includes(p.name)) { + if ( + ["kill", "on", "once", "off", "removeListener", "unref", "ref"].includes(ts.symbolName(p)) + ) { return checker.getCallSignatures(checker.getTypeOfSymbol(p)).length > 0; } const expected = - p.name === "stdout" || p.name === "stderr" + ts.symbolName(p) === "stdout" || ts.symbolName(p) === "stderr" ? "childStream" - : p.name === "stdin" + : ts.symbolName(p) === "stdin" ? "childWriter" - : p.name === "killed" + : ts.symbolName(p) === "killed" ? "bool" : "f64"; const unit = - p.name === "pid" + ts.symbolName(p) === "pid" ? "undefinedT" - : p.name === "exitCode" || - p.name === "stdin" || - p.name === "stdout" || - p.name === "stderr" + : ts.symbolName(p) === "exitCode" || + ts.symbolName(p) === "stdin" || + ts.symbolName(p) === "stdout" || + ts.symbolName(p) === "stderr" ? "nullT" : null; const mapped = mapType(checker.getTypeOfSymbol(p), ctx); @@ -4482,8 +4563,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const childShaped = props.length > 0 && props.every((p) => { - if (!CHILD_SURFACE.has(p.name) || !childMemberType(p)) return false; - if (CHILD_CORE.has(p.name)) core++; + if (!CHILD_SURFACE.has(ts.symbolName(p)) || !childMemberType(p)) return false; + if (CHILD_CORE.has(ts.symbolName(p))) core++; return true; }); if (childShaped && core >= 2) return CHILD_T; @@ -4536,7 +4617,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // to `RegExpMatchArray | []`: the empty tuple is one of the match // array's own values too. const matchArray = (part: ts.Type): boolean => { - const name = part.getSymbol()?.name; + // The names have no leading `__`, so the escaped name is the name. + const name = part.getSymbol()?.escapedName; return ( (name === "RegExpMatchArray" || name === "RegExpExecArray") && (part.getSymbol()?.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) ?? false) @@ -4698,7 +4780,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null // arity ABI selected by lambdaSignature. Treating an inferred any[] // declaration as a typed packed-array closure would bypass its boxed // spread/error semantics and change coverage classification. - if (rest && decl !== undefined && isJsSourceFile(decl.getSourceFile())) return null; + if (rest && decl !== undefined && isJsSourceFile(ts.getSourceFileOfNode(decl))) return null; // A fixed-tuple rest (`...args: [A, B]`, `...args: Parameters` over // a bound F) flattens to its positional parameters. Pattern rests keep // their packed-record ABI and stay unmapped as values. @@ -4732,8 +4814,8 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null ((ts.isArrowFunction(decl.parent) || ts.isFunctionExpression(decl.parent)) && decl.initializer !== undefined && decl.type === undefined && - !isJsSourceFile(decl.getSourceFile()))) && - !decl.getSourceFile().isDeclarationFile && + !isJsSourceFile(ts.getSourceFileOfNode(decl)))) && + !ts.getSourceFileOfNode(decl).isDeclarationFile && (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.Any) !== 0 ) pt = DYN; @@ -4741,7 +4823,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null decl && ts.isParameter(decl) && ts.isSetAccessorDeclaration(decl.parent) && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) pt = DYN; // An inferred JavaScript binding pattern consumes an iterable; it @@ -4761,20 +4843,43 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null ts.isIdentifier(construction.expression) && construction.expression.text === "Promise" && constructorSymbol && - constructorSymbol.declarations.some((site) => ctx.isStdlibFile(site.getSourceFile())); + constructorSymbol.declarations.some((site) => ctx.isStdlibFile(ts.getSourceFileOfNode(site))); if ( !nativeExecutor && decl && ts.isParameter(decl) && !decl.dotDotDotToken && !ts.isSetAccessorDeclaration(decl.parent) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !/@(?:param|type)\b/.test( - decl.getSourceFile().text.slice(decl.parent?.pos ?? decl.pos, decl.getStart()), + ts + .getSourceFileOfNode(decl) + .text.slice(decl.parent?.pos ?? decl.pos, ts.tokenStartOfNode(decl)), ) ) pt = DYN; + // A DEFAULTED program-source parameter of a function, arrow or function + // expression that the checker types `any` — a default under an + // `(...args: any[]) => any` wrapper slot (`wrap((limit = 5) => ...)`), + // where the contextual `any` wins over the default's type: its binding + // is already the checked-dynamic kind inside the body + // (dynFallbackType), so the callable's ABI slot is the same dyn value. + // Other `any` parameters keep their existing paths (an object literal + // holding such a callback stays a checked-dynamic object). + if ( + pt === null && + !rest && + decl !== undefined && + ts.isParameter(decl) && + decl.initializer !== undefined && + (ts.isArrowFunction(decl.parent) || + ts.isFunctionExpression(decl.parent) || + ts.isFunctionDeclaration(decl.parent)) && + !ts.getSourceFileOfNode(decl).isDeclarationFile && + (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.Any) !== 0 + ) + pt = DYN; if (pt) pt = jsOpenObjectType(decl, pt, ctx.shapes, ctx.unions); // Belt and braces for non-strict type worlds: an optional param's // ABI slot is always the undefined-armed union (strictNullChecks @@ -4796,7 +4901,8 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null if ( rest && decl !== undefined && - (!decl.getSourceFile().isDeclarationFile || isNodeTestDefaultFunctionParam(decl, ctx)) && + (!ts.getSourceFileOfNode(decl).isDeclarationFile || + isNodeTestDefaultFunctionParam(decl, ctx)) && (pt?.kind === "dyn" || (!pt && isAnyArrayType(checker.getTypeOfSymbol(p), checker))) ) { dynRest = true; @@ -4821,7 +4927,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null const sigDecl = sig.declaration; const jsUnitReturn = sigDecl !== undefined && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && (retT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; let ret = jsUnitReturn || (retT.flags & ts.TypeFlags.Any) !== 0 @@ -4829,24 +4935,33 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null : retT.flags & ts.TypeFlags.Never ? VOID : mapType(retT, ctx); - if (sigDecl && ts.isGetAccessorDeclaration(sigDecl) && isJsSourceFile(sigDecl.getSourceFile())) + if ( + sigDecl && + ts.isGetAccessorDeclaration(sigDecl) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) + ) + ret = DYN; + if (sigDecl && isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && ret?.kind === "array") ret = DYN; - if (sigDecl && isJsSourceFile(sigDecl.getSourceFile()) && ret?.kind === "array") ret = DYN; if ( sigDecl && ts.isFunctionLike(sigDecl) && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && !sigDecl.type && - !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && + !/@returns?\b/.test( + ts.getSourceFileOfNode(sigDecl).text.slice(sigDecl.pos, ts.tokenStartOfNode(sigDecl)), + ) && ret?.kind === "record" ) ret = DYN; if ( sigDecl && ts.isFunctionLike(sigDecl) && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && !sigDecl.type && - !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && + !/@returns?\b/.test( + ts.getSourceFileOfNode(sigDecl).text.slice(sigDecl.pos, ts.tokenStartOfNode(sigDecl)), + ) && (ret?.kind === "date" || (ret?.kind === "union" && ctx.unions.get(ret.unionId)?.arms.some((arm) => arm.kind === "func"))) @@ -4855,7 +4970,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null if ( ret && sigDecl && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && functionCanReturnUndefined(sigDecl) ) ret = withUndefinedArm(ret, ctx.unions) ?? ret; @@ -4888,12 +5003,12 @@ function unionDiscriminant( // no slot to compare (discriminantField refuses tuple shapes too). if (parts.some((part) => ctx.shapes.get(part.mapped.shapeId)?.tuple)) return undefined; for (const candidate of checker.getPropertiesOfType(parts[0]!.source)) { - if (candidate.name.startsWith("__@")) continue; + if (ts.symbolName(candidate).startsWith("__@")) continue; const byTag = new Map(); const owner = new Map(); let complete = true; for (const part of parts) { - const prop = checker.getPropertyOfType(part.source, candidate.name); + const prop = checker.getPropertyOfType(part.source, ts.symbolName(candidate)); if ( !prop || prop.flags & @@ -4924,7 +5039,7 @@ function unionDiscriminant( } if (complete) { return { - field: candidate.name, + field: ts.symbolName(candidate), cases: [...byTag] .sort(([a], [b]) => a - b) .map(([tag, values]) => ({ @@ -4966,7 +5081,8 @@ function mapClassView( if ( declarations.length === 0 || !declarations.every( - (decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(decl.getSourceFile()), + (decl) => + ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), ) ) return undefined; @@ -5004,7 +5120,7 @@ function mapClassView( const classBase = declaration && (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration)) && - !declaration.getSourceFile().isDeclarationFile; + !ts.getSourceFileOfNode(declaration).isDeclarationFile; const mapped = classBase ? mapType(base, viewCtx) : mapClassView(base, viewCtx, seen); if (mapped === null) return null; if (mapped === undefined) continue; @@ -5021,7 +5137,7 @@ function mapClassView( ) return null; for (const property of checker.getPropertiesOfType(type)) { - const original = checker.getPropertyOfType(baseType, property.name); + const original = checker.getPropertyOfType(baseType, ts.symbolName(property)); if (!original) return null; if (original === property) continue; const originalDeclarations = original.declarations; @@ -5067,7 +5183,7 @@ function mapCollectionView( if ( declarations.length === 0 || !declarations.every( - (decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(decl.getSourceFile()), + (decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), ) ) return null; @@ -5115,18 +5231,20 @@ function mapCollectionView( const baseSymbol = base.getSymbol(); const native = baseSymbol && - ["Map", "ReadonlyMap", "Set", "ReadonlySet"].includes(baseSymbol.name) && + ["Map", "ReadonlyMap", "Set", "ReadonlySet"].includes(ts.symbolName(baseSymbol)) && baseSymbol.declarations.some( - (decl) => ts.isInterfaceDeclaration(decl) && ctx.isStdlibFile(decl.getSourceFile()), + (decl) => ts.isInterfaceDeclaration(decl) && ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), ); const mapped = native ? mapType(base, baseCtx) : mapCollectionView(base, baseCtx, seen); if (mapped?.kind !== "map" && mapped?.kind !== "set") return null; const hasType = funcOf([mapped.kind === "map" ? mapped.key : mapped.elem], BOOL); for (const property of checker.getPropertiesOfType(type)) { - const own = property.declarations.filter((decl) => !ctx.isStdlibFile(decl.getSourceFile())); + const own = property.declarations.filter( + (decl) => !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), + ); if (own.length === 0) continue; if ( - property.name !== "has" || + ts.symbolName(property) !== "has" || property.flags & ts.SymbolFlags.Optional || !own.every((decl) => decl.kind === ts.SyntaxKind.MethodSignature) ) @@ -5236,12 +5354,13 @@ function boundSignatureAlias( const { checker, resolveTypeParamTs } = ctx; if (!resolveTypeParamTs) return null; const alias = type.getAliasSymbol(); - if (!alias || (alias.name !== "Parameters" && alias.name !== "ReturnType")) return null; + const aliasName = alias === undefined ? undefined : ts.symbolName(alias); + if (!alias || (aliasName !== "Parameters" && aliasName !== "ReturnType")) return null; const aliasArgs = type.getAliasTypeArguments(); const arg = aliasArgs[0]; if (aliasArgs.length !== 1 || !arg || !(arg.flags & ts.TypeFlags.TypeParameter)) return null; const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; const bound = resolveTypeParamTs(arg); @@ -5249,7 +5368,7 @@ function boundSignatureAlias( const sigs = checker.getCallSignatures(bound); const sig = sigs[0]; if (sigs.length !== 1 || !sig || sig.getTypeParameters().length > 0) return null; - return { name: alias.name, sig }; + return { name: aliasName, sig }; } /** One position of a fixed-arity parameter tuple. An optional position @@ -5452,7 +5571,7 @@ function mapGenericAwaitedAlias(type: ts.Type, ctx: TypeMapperCtx): IrType | nul const { resolveTypeParam } = ctx; if (!resolveTypeParam) return null; const alias = type.getAliasSymbol(); - if (!alias || alias.name !== "Awaited") return null; + if (!alias || ts.symbolName(alias) !== "Awaited") return null; const aliasArgs = type.getAliasTypeArguments(); const arg = aliasArgs[0]; if (aliasArgs.length !== 1 || !arg) return null; @@ -5463,7 +5582,7 @@ function mapGenericAwaitedAlias(type: ts.Type, ctx: TypeMapperCtx): IrType | nul if (!(arg.flags & ts.TypeFlags.TypeParameter) && viaSignature?.name !== "ReturnType") return null; // Provenance: the STANDARD LIBRARY's alias, not a user's shadowing one. const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; const bound = viaSignature ? mapBoundSignatureAlias(arg, ctx) : resolveTypeParam(arg); @@ -5607,7 +5726,7 @@ function mapBoundMappedType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null const fields: { name: string; type: IrType }[] = []; const order: string[] = []; for (const property of checker.getPropertiesOfType(bound)) { - const name = property.name; + const name = ts.symbolName(property); const evaluated = evalMappedTemplate(template, new Map([[key, name]]), new Map(), ctx); let type = evaluated ? mapType(evaluated, ctx) : null; if (!type) return null; @@ -5875,8 +5994,8 @@ function matchInferThenable( const symbol = t.getSymbol(); return ( symbol !== undefined && - names.includes(symbol.name) && - symbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + names.includes(ts.symbolName(symbol)) && + symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ); }; if (!stdlibNamed(extendsType, ["PromiseLike", "Promise"])) return undefined; @@ -5908,11 +6027,11 @@ function mentionsMappedParams( function readonlyClassType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const alias = type.getAliasSymbol(); - if (alias?.name !== "Readonly") return null; + if (alias === undefined || ts.symbolName(alias) !== "Readonly") return null; const args = type.getAliasTypeArguments(); if (args.length !== 1 || !args[0]) return null; const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; const mapped = mapType(args[0], ctx); @@ -5923,19 +6042,20 @@ function mapGenericUtilityAlias(widened: ts.Type, ctx: TypeMapperCtx): IrType | const { resolveTypeParam, shapes, unions } = ctx; if (!resolveTypeParam) return null; const alias = widened.getAliasSymbol(); - if (!alias || (alias.name !== "Partial" && alias.name !== "Readonly")) return null; + if (!alias || (ts.symbolName(alias) !== "Partial" && ts.symbolName(alias) !== "Readonly")) + return null; const aliasArgs = widened.getAliasTypeArguments(); const arg = aliasArgs[0]; if (aliasArgs.length !== 1 || !arg) return null; if (!(arg.flags & ts.TypeFlags.TypeParameter)) return null; // Provenance: the STANDARD LIBRARY's alias, not a user's shadowing one. const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; const bound = resolveTypeParam(arg); if (!bound || bound.kind !== "record") return null; - if (alias.name === "Readonly") return bound; + if (ts.symbolName(alias) === "Readonly") return bound; const shape = shapes.get(bound.shapeId); if (!shape) return null; // Partial over an index-signature shape would need `V | undefined` @@ -6061,7 +6181,7 @@ export function isUnitOnlyTsType(t: ts.Type, resolveTypeParam?: TypeParamResolve /** `ReturnType` over a type parameter bound to a void-returning * function (a variadic wrapper's result slot): a unit-only value. */ function boundVoidReturnType(p: ts.Type, resolveTypeParam?: TypeParamResolver): boolean { - if (!resolveTypeParam || p.getAliasSymbol()?.name !== "ReturnType") return false; + if (!resolveTypeParam || ts.symbolNameOf(p.getAliasSymbol()) !== "ReturnType") return false; const args = p.getAliasTypeArguments(); const arg = args[0]; if (args.length !== 1 || !arg || !(arg.flags & ts.TypeFlags.TypeParameter)) return false; @@ -6152,8 +6272,8 @@ function mapHybridCallableIntersection(widened: ts.Type, ctx: TypeMapperCtx): Ir const pt = mapType(checker.getTypeOfSymbol(p), ctx); if (!pt || pt.kind === "void") return null; - fields.push({ name: p.name, type: pt }); - declaredOrder.push(p.name); + fields.push({ name: ts.symbolName(p), type: pt }); + declaredOrder.push(ts.symbolName(p)); } if (fields.length === 1) return null; // no properties — it is just F fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); @@ -6195,8 +6315,8 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType "URIError", "AggregateError", "SuppressedError", - ].includes(symbol.name) && - symbol.declarations.some((decl) => ctx.isStdlibFile(decl.getSourceFile())) + ].includes(ts.symbolName(symbol)) && + symbol.declarations.some((decl) => ctx.isStdlibFile(ts.getSourceFileOfNode(decl))) ); }); if (!hasError) return null; @@ -6217,9 +6337,9 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType return null; for (const property of ctx.checker.getPropertiesOfType(part)) { const expected = - property.name === "errno" + ts.symbolName(property) === "errno" ? "f64" - : ["code", "syscall", "path", "dest"].includes(property.name) + : ["code", "syscall", "path", "dest"].includes(ts.symbolName(property)) ? "string" : null; if (!expected) { @@ -6247,11 +6367,11 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType for (const property of extra) { const field = mapType(ctx.checker.getTypeOfSymbol(property), ctx); if (!field || field.kind === "void") return null; - if (property.name === "message" || property.name === "name") { + if (ts.symbolName(property) === "message" || ts.symbolName(property) === "name") { if (field.kind !== "string") return null; continue; } - if (property.name === "stack" || property.name === "cause") return null; + if (ts.symbolName(property) === "stack" || ts.symbolName(property) === "cause") return null; if ( !canDynCheckTo( field, @@ -6371,7 +6491,8 @@ function immutableSourceType(type: ts.Type, ctx: TypeMapperCtx): ts.Type | null const { checker } = ctx; const symbol = type.getSymbol(); const decl = symbol ? symbol.declarations[0] : undefined; - if (decl === undefined || !CONTRACT_TYPES_FILE.test(decl.getSourceFile().fileName)) return null; + if (decl === undefined || !CONTRACT_TYPES_FILE.test(ts.getSourceFileOfNode(decl).fileName)) + return null; const props = checker.getPropertiesOfType(type); const seen = new Set(); for (const prop of props) { @@ -6451,14 +6572,14 @@ function recordProvenanceOk(t: ts.Type, ctx: TypeMapperCtx): boolean { .every((property) => property.declarations.every( (decl) => - !decl.getSourceFile().isDeclarationFile || - ctx.isExternalTypeFile(decl.getSourceFile()) || - packageTypeFile(decl.getSourceFile(), ctx), + !ts.getSourceFileOfNode(decl).isDeclarationFile || + ctx.isExternalTypeFile(ts.getSourceFileOfNode(decl)) || + packageTypeFile(ts.getSourceFileOfNode(decl), ctx), ), ); } return !decls.some((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }); } @@ -6535,7 +6656,7 @@ export function erasedGenericMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType const sigs = ctx.checker.getCallSignatures(t); if (sigs.length !== 1) return undefined; const sig = sigs[0]!; - const own = new Set(sig.getTypeParameters()); + const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; const erased = mapType(t, { ...ctx, @@ -6567,7 +6688,7 @@ export function isOverloadedCallableMemberType(t: ts.Type, checker: ts.TypeCheck if (decl === undefined) return false; if (!ts.isMethodSignatureDeclaration(decl) && !ts.isCallSignatureDeclaration(decl)) return false; - const file = decl.getSourceFile(); + const file = ts.getSourceFileOfNode(decl); return !file.isDeclarationFile && !isJsSourceFile(file); }); } @@ -6629,7 +6750,7 @@ function joinOverloadTypes(types: readonly IrType[], ctx: TypeMapperCtx): IrType export function erasedOverloadMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType | undefined { const funcs: (IrType & { kind: "func" })[] = []; for (const sig of ctx.checker.getCallSignatures(t)) { - const own = new Set(sig.getTypeParameters()); + const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; const mapped = mapCallSignature( sig, @@ -6675,7 +6796,7 @@ export function erasedOverloadMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType function describeOverloadSetBlocker(t: ts.Type, ctx: TypeMapperCtx): string { const { checker } = ctx; for (const sig of checker.getCallSignatures(t)) { - const own = new Set(sig.getTypeParameters()); + const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; const sigCtx: TypeMapperCtx = own.size === 0 @@ -6688,14 +6809,14 @@ function describeOverloadSetBlocker(t: ts.Type, ctx: TypeMapperCtx): string { const mapped = mapCallSignature(sig, sigCtx); if (mapped?.kind === "func" && mapped.rest !== true && mapped.argumentsAll !== true) continue; const decl = sig.declaration; - const spelled = decl ? decl.getText().replace(/\s+/g, " ").replace(/;$/, "") : ""; + const spelled = decl ? ts.getTextOfNode(decl).replace(/\s+/g, " ").replace(/;$/, "") : ""; const which = spelled ? `overload '${spelled}'` : "one overload"; if (mapped?.kind === "func") return `is an overload set, and its ${which} has a rest parameter (an overload set stores one fixed-arity function)`; for (const param of sig.getParameters()) { const paramTs = checker.getTypeOfSymbol(param); if (!mapType(paramTs, sigCtx)) - return `is an overload set, and its ${which} has parameter '${param.name}' of type '${checker.typeToString(paramTs)}', which does not compile`; + return `is an overload set, and its ${which} has parameter '${ts.symbolName(param)}' of type '${checker.typeToString(paramTs)}', which does not compile`; } const retTs = checker.getReturnTypeOfSignature(sig); return `is an overload set, and its ${which} returns '${checker.typeToString(retTs)}', which does not compile`; @@ -6881,9 +7002,7 @@ function dictSpreadLiteralShape( const infos = checker.getIndexInfosOfType(contextual); const unknownValued = infos.length > 0 && - infos.every( - (info) => (info.valueType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0, - ); + infos.every((info) => (info.type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0); if (!unknownValued) return null; } const kinds = props.map((p) => @@ -6895,7 +7014,7 @@ function dictSpreadLiteralShape( if (checker.getPropertiesOfType(t).length === 0) { const infos = checker.getIndexInfosOfType(t); if (infos.length !== 1 || !(infos[0]!.keyType.flags & ts.TypeFlags.String)) return null; - return mapType(infos[0]!.valueType, ctx); + return mapType(infos[0]!.type, ctx); } // Another such literal's type (`{ ...tags, repo }` over a binding // initialized by `{ ...base, plan }`) is stored as the same kind of @@ -7022,7 +7141,7 @@ function spreadMemberNamesTs(checker: ts.TypeChecker, e: ts.Expression): string[ const names: string[] = []; for (const l of literals) { const t = checker.getNonNullableType(checker.getTypeAtLocation(l)); - for (const property of checker.getPropertiesOfType(t)) names.push(property.name); + for (const property of checker.getPropertiesOfType(t)) names.push(ts.symbolName(property)); } return names; } @@ -7074,11 +7193,11 @@ function refinedConstituentShape( ): string | null { if (!widened.isIntersectionType()) return null; const { checker, shapes } = ctx; - const names = checker.getPropertiesOfType(widened).map((prop) => prop.name); + const names = checker.getPropertiesOfType(widened).map((prop) => ts.symbolName(prop)); let found: string | null = null; for (const part of ts.constituentTypes(widened)) { if ((part.flags & ts.TypeFlags.Object) === 0 || shapes.inProgress.has(part)) continue; - const own = new Set(checker.getPropertiesOfType(part).map((prop) => prop.name)); + const own = new Set(checker.getPropertiesOfType(part).map((prop) => ts.symbolName(prop))); if (own.size !== names.length || !names.every((name) => own.has(name))) continue; const mapped = mapType(part, ctx); if (mapped?.kind !== "record") continue; @@ -7101,6 +7220,30 @@ function refinedConstituentShape( return found; } +/** The optional `undefined` members widening adds to an object literal's + * type for properties only its sibling literals declare (`[{ a: 1 }, { b: 2 + * }]` gives the first `b?: undefined`). They are never own keys of the + * literal's value, so they go after the declared members, by name. Where + * the checker lists them is not observable: TypeScript 7.0.2 orders them + * by the literal its checker widened first, TypeScript 7.1 by the sibling + * that declares them. */ +function wideningAddedProperties( + type: ts.Type, + props: readonly ts.Symbol[], + ctx: TypeMapperCtx, +): Set { + const added = new Set(); + const literal = type.getSymbol()?.declarations.find(ts.isObjectLiteralExpression); + if (literal === undefined || literal.properties.nodes.some(ts.isSpreadAssignment)) return added; + for (const prop of props) { + if ((prop.flags & ts.SymbolFlags.Optional) === 0 || prop.declarations.length === 0) continue; + if (prop.declarations.some((d) => d.parent === literal)) continue; + if ((ctx.checker.getTypeOfSymbol(prop).flags & ts.TypeFlags.Undefined) === 0) continue; + added.add(ts.symbolName(prop)); + } + return added; +} + function mapRecordTypeInner( widened: ts.Type, ctx: TypeMapperCtx, @@ -7155,7 +7298,7 @@ function mapRecordTypeInner( let v: IrType | null = null; for (const info of indexInfos) { if (!stringKey(info.keyType) && !(info.keyType.flags & ts.TypeFlags.Number)) return null; - const iv = mapType(info.valueType, ctx); + const iv = mapType(info.type, ctx); // JSONValue>` (a package's own JSON alias) and `Record` @@ -7251,7 +7394,7 @@ function mapRecordTypeInner( if (ctx.classImplementedProtocol?.(widened)) return DYN; // Symbol-keyed records use the checked object's descriptor table. A // checker-internal __@ name is not a string field in a native struct. - if (props.some((property) => property.name.startsWith("__@"))) return DYN; + if (props.some((property) => ts.symbolName(property).startsWith("__@"))) return DYN; // A computed shape with NO members is not a real empty record: inside a // generic body `Partial` resolves to no members at all (keyof T is // still unknown there) — interning `{}` would give a wrong type. The @@ -7310,13 +7453,13 @@ function mapRecordTypeInner( (ts.isObjectLiteralExpression(d.parent) || ts.isInterfaceDeclaration(d.parent) || d.parent.kind === ts.SyntaxKind.TypeLiteral) && - !d.getSourceFile().isDeclarationFile && - !isJsSourceFile(d.getSourceFile()), + !ts.getSourceFileOfNode(d).isDeclarationFile && + !isJsSourceFile(ts.getSourceFileOfNode(d)), ); if (!accessorOwned || (!getDecl && !setDecl)) return null; // Symbol-keyed accessors have no foldable literal name to fill at // the literal — no slot to make. - if (p.name.startsWith("__@")) return null; + if (ts.symbolName(p).startsWith("__@")) return null; // An index-signature shape stores accessor NAMES nowhere the keyed // read/walk machinery can answer (the overflow would miss where // Node dispatches the getter) — those shapes stay unmapped. @@ -7338,8 +7481,9 @@ function mapRecordTypeInner( if (readT !== null && typeKey(readT) !== typeKey(writeT)) return null; } if (readT !== null || getDecl) - fields.push({ name: `%get:${p.name}`, type: funcOf([], readT ?? VOID) }); - if (writeT !== null) fields.push({ name: `%set:${p.name}`, type: funcOf([writeT], VOID) }); + fields.push({ name: `%get:${ts.symbolName(p)}`, type: funcOf([], readT ?? VOID) }); + if (writeT !== null) + fields.push({ name: `%set:${ts.symbolName(p)}`, type: funcOf([writeT], VOID) }); continue; } // Computed shapes carry provenance per MEMBER: a utility type over a @@ -7349,7 +7493,7 @@ function mapRecordTypeInner( if ( computed && p.declarations.some((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }) ) { @@ -7399,11 +7543,11 @@ function mapRecordTypeInner( // A DATA property spelled like a reserved accessor slot (`{ "%get:x": // v }` — a string-literal key): mapping it would collide with the // accessor dispatch, so the shape stays unmapped. - if (accessorSlotProp(p.name) !== null) return null; + if (accessorSlotProp(ts.symbolName(p)) !== null) return null; // an options record with `signal?: AbortSignal` must not turn its - fields.push({ name: p.name, type: pt }); + fields.push({ name: ts.symbolName(p), type: pt }); } // getPropertiesOfType yields DECLARATION order (interface/alias/literal // source order) — captured before the canonical sort so Object.keys/ @@ -7414,8 +7558,12 @@ function mapRecordTypeInner( // indices, from folded numeric computed keys and quoted numeric keys) // enumerate FIRST ascending, then string keys in insertion order // (OrdinaryOwnPropertyKeys) — Object.keys/JSON/inspect all read it. + const widenedAbsent = wideningAddedProperties(widened, props, ctx); const declaredOrder = esOwnKeyOrder( - fields.filter((f) => accessorSlotProp(f.name) === null).map((f) => f.name), + fields + .filter((f) => accessorSlotProp(f.name) === null && !widenedAbsent.has(f.name)) + .map((f) => f.name) + .concat([...widenedAbsent].sort()), ); // With accessors: every own key in that order, an accessor property // named once for its get/set pair (the checked-dynamic copy lists it). @@ -7498,7 +7646,7 @@ function isNodeTestDefaultFunctionParam(decl: ts.Declaration, ctx: TypeMapperCtx fnType.parent !== undefined && ts.isTypeParameterDeclaration(fnType.parent) && fnType.parent.defaultType === fnType && - ctx.isStdlibFile(decl.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(decl)) && isDeclaredInAmbientModule(decl, "test") ); } @@ -7574,11 +7722,11 @@ export function describeComponentBlocker(widened: ts.Type, ctx: TypeMapperCtx): const psym = widened.getSymbol(); const container = psym !== undefined && - Object.prototype.hasOwnProperty.call(STDLIB_CONTAINERS, psym.name) && + Object.prototype.hasOwnProperty.call(STDLIB_CONTAINERS, ts.symbolName(psym)) && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) - ? psym.name + ? ts.symbolName(psym) : undefined; if (container !== undefined) { const spec = STDLIB_CONTAINERS[container]!; @@ -7720,7 +7868,7 @@ export function describeComponentBlocker(widened: ts.Type, ctx: TypeMapperCtx): for (const p of sig.getParameters()) { const pTs = checker.getTypeOfSymbol(p); if (!mapType(pTs, ctx)) { - return `the function shape is supported, but its parameter '${p.name}' has type '${text(pTs)}', which does not compile`; + return `the function shape is supported, but its parameter '${ts.symbolName(p)}' has type '${text(pTs)}', which does not compile`; } } const retTs = checker.getReturnTypeOfSignature(sig); @@ -7752,14 +7900,14 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx // Accessor members and symbol-keyed members have their own multi-shaped // rules (mapRecordTypeInner) — no single-member claim is honest there. if ((p.flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor)) !== 0) continue; - if (p.name.startsWith("__@")) continue; + if (ts.symbolName(p).startsWith("__@")) continue; // Computed shapes over LIBRARY members (`Readonly`): the record // fence there is per-member PROVENANCE (mapRecordTypeInner's rule), not // any one member's type — that story stays with the residual fence. if ( computed && p.declarations.some((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }) ) { @@ -7768,7 +7916,7 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx const fieldTs = checker.getTypeOfSymbol(p); if (isOverloadedCallableMemberType(fieldTs, checker)) { if (erasedOverloadMemberType(fieldTs, ctx) !== undefined) continue; - return `the record shape is supported, but its member '${p.name}' ${describeOverloadSetBlocker(fieldTs, ctx)}`; + return `the record shape is supported, but its member '${ts.symbolName(p)}' ${describeOverloadSetBlocker(fieldTs, ctx)}`; } let pt = isGenericCallableMemberType(fieldTs, checker) ? erasedGenericMemberType(fieldTs, ctx) @@ -7777,7 +7925,7 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx if (pt?.kind === "void" && isUnitOnlyTsType(fieldTs, ctx.resolveTypeParam)) pt = unitOnlyUnion(ctx.unions); if (!pt || pt.kind === "void") { - return `the record shape is supported, but its member '${p.name}' has type '${checker.typeToString(fieldTs)}', which does not compile`; + return `the record shape is supported, but its member '${ts.symbolName(p)}' has type '${checker.typeToString(fieldTs)}', which does not compile`; } } return null; diff --git a/packages/compiler/src/frontend/worker-target.ts b/packages/compiler/src/frontend/worker-target.ts index 99c97dded..5193f0539 100644 --- a/packages/compiler/src/frontend/worker-target.ts +++ b/packages/compiler/src/frontend/worker-target.ts @@ -84,7 +84,8 @@ export function isWorkerConstructor(program: ts.Program, expression: ts.Expressi export function workerModulePath(program: ts.Program, expression: ts.Expression): string | null { if (ts.isIdentifier(expression) && expression.text === "__filename") { - if (isNodeGlobal(program, expression)) return resolve(expression.getSourceFile().fileName); + if (isNodeGlobal(program, expression)) + return resolve(ts.getSourceFileOfNode(expression).fileName); } const value = staticForkString(program, expression); if (value === null) return null; diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 9c02cdd8b..0f144ca2e 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -546,6 +546,7 @@ async function compileExecutableNative( dynAsync: features.dynAsync, ...(features.dynTier ? { dynTier: true } : {}), ...(features.streamDyn ? { streamDyn: true } : {}), + ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), events: features.events, emitter: features.emitter, symbol: features.symbol, @@ -553,6 +554,7 @@ async function compileExecutableNative( searchParams: features.searchParams, qs: features.qs, dynBuiltinClass: features.dynBuiltinClass, + dynBuiltinStream: features.dynBuiltinStream, parseArgs: features.parseArgs, stream: features.stream, net: features.net, diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 30f0f7365..8aa88f1d3 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3603,6 +3603,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * Transform (5) and PassThrough (6) as untyped constructor values, one * per thread (scr_dyn_builtin_class.c). */ "dyn.builtinClass": { argTypes: [F64], result: DYN }, + /** The stream classes (2-6 above) with their stream half: constructor + * functions over them get native stream state + * (scr_dyn_builtin_stream.c). */ + "dyn.builtinStreamClass": { argTypes: [F64], result: DYN }, /** `value instanceof C` for those constructors, over the value's * prototype chain. */ "dyn.builtinInstanceOf": { argTypes: [DYN, F64], result: BOOL }, @@ -4324,6 +4328,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * errorBoxer, listenForError] → a pending promise of any[]; the boxers * turn the event's and the 'error' tuple into that array. */ "events.once": { argTypes: [null, STRING, null, null, BOOL], result: VOID }, + /** events.once(emitter, name, { signal }) (scr_events_once_signal.c): + * events.once's arguments plus the checked-dynamic signal (an + * AbortSignal or undefined). */ + "events.onceSignal": { argTypes: [null, STRING, null, null, BOOL, DYN], result: VOID }, /** The resolve-value forms: [ms?, value, ref] → a pending promise of the * value's type that copies the borrowed already-resolved `value` promise * when the timer (or immediate) fires; ref false unrefs it. The result diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 0d680d7af..808b20fe4 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -130,6 +130,7 @@ const LEGACY_TEXT_DECODER_LIB_FNS: ReadonlySet = new Set([ /** Signal-taking readline, stream and timers/promises entry points, which register abort listeners. */ const ABORT_SIGNAL_LIB_FNS: ReadonlySet = new Set([ + "events.onceSignal", "http.clientSignal", "http.requestDyn", "http.requestDynCb", @@ -254,6 +255,8 @@ const CHILD_PROCESS_LIB_PREFIXES: readonly string[] = ["child.", "cp.", "spawnRe * emitter unit, so each of them links all five. */ const BUILTIN_CLASS_LIB_FNS: readonly IrLibFn[] = [ "dyn.builtinClass", + // the stream classes, through scr_dyn_builtin_stream.c + "dyn.builtinStreamClass", "dyn.builtinInstanceOf", "dyn.functionInstanceOf", ]; @@ -545,6 +548,10 @@ const RUNTIME_FEATURE_RULES = { dynBuiltinClass: rule({ fns: [...BUILTIN_CLASS_LIB_FNS], }), + /** The stream classes as untyped constructor values with their stream + * half (scr_dyn_builtin_stream.c): native stream state for constructor + * functions, over the stream unit. */ + dynBuiltinStream: rule({ fns: ["dyn.builtinStreamClass"] }), dynInvoke: rule({ initial: (mod) => mod.workers === true, fns: [...DYN_INVOKE_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS, ...BUILTIN_CLASS_LIB_FNS], @@ -580,6 +587,8 @@ const RUNTIME_FEATURE_RULES = { fns: ["events.addAbortListener", ...ABORT_SIGNAL_LIB_FNS], prefixes: ["event."], }), + /** events.once(emitter, name, { signal }) (scr_events_once_signal.c). */ + eventsOnceSignal: rule({ fns: ["events.onceSignal"] }), fetch: rule({ prefixes: ["fetch."] }), fileHandle: rule({ types: ["fileHandle"] }), fsWatch: rule({ prefixes: ["fs.watch", "watcher."], types: ["fsWatcher"] }), @@ -672,7 +681,7 @@ const RUNTIME_FEATURE_RULES = { }), stream: rule({ initial: (mod) => (mod.classes ?? []).some((c) => RUNTIME_STREAM_CLASSES.has(c.name)), - fns: [...STREAM_LIB_FNS], + fns: [...STREAM_LIB_FNS, "dyn.builtinStreamClass"], prefixes: STREAM_LIB_PREFIXES, }), /** A Dirent row converted to dyn carries Node's Symbol(type) key @@ -717,10 +726,12 @@ function noRuntimeFeatures(): RuntimeFeatures { dnsPromises: false, dynAsync: false, dynBuiltinClass: false, + dynBuiltinStream: false, dynInvoke: false, dynTier: false, emitter: false, eventTarget: false, + eventsOnceSignal: false, fetch: false, fileHandle: false, fsWatch: false, @@ -883,6 +894,14 @@ export function moduleUsesStream(mod: IrModule): boolean { return scanRuntimeFeatures(mod, "stream").stream; } +/** True when the program links scr_dyn_builtin_stream.c (stream classes + * as untyped constructor values): its runtime streams box through that + * unit's view and checked casts to the stream classes accept constructor- + * function streams. Programs without it keep their exact code. */ +export function moduleUsesDynBuiltinStream(mod: IrModule): boolean { + return scanRuntimeFeatures(mod, "dynBuiltinStream").dynBuiltinStream; +} + export const NPM_COMPRESS_MIN = 1024; export function moduleUsesZlib(mod: IrModule): boolean { diff --git a/packages/compiler/src/ir/traverse.test.ts b/packages/compiler/src/ir/traverse.test.ts index 874e7846e..fbe84644a 100644 --- a/packages/compiler/src/ir/traverse.test.ts +++ b/packages/compiler/src/ir/traverse.test.ts @@ -28,7 +28,8 @@ const program = createProgram([path], { noLib: true, noResolve: true, types: [] const source = program.getSourceFile(path)!; afterAll(() => program.dispose()); function childType(type: ts.TypeNode): boolean { - if (ts.isTypeReferenceNode(type)) return ["IrExpr", "IrStmt"].includes(type.typeName.getText()); + if (ts.isTypeReferenceNode(type)) + return ["IrExpr", "IrStmt"].includes(ts.getTextOfNode(type.typeName)); if (ts.isArrayTypeNode(type)) return childType(type.elementType); if (ts.isUnionTypeNode(type)) return type.types.nodes.some(childType); if (ts.isTypeLiteralNode(type)) @@ -47,15 +48,15 @@ for (const name of ["IrExpr", "IrStmt"]) { for (const variant of alias.type.types.nodes) { if (!ts.isTypeLiteralNode(variant)) throw new Error("IR variants must be records"); const members = variant.members.nodes.filter(ts.isPropertySignature); - const tag = members.find((member) => member.name.getText() === "kind")!.type!; - const kind = JSON.parse(tag.getText()) as string; + const tag = members.find((member) => ts.getTextOfNode(member.name) === "kind")!.type!; + const kind = JSON.parse(ts.getTextOfNode(tag)) as string; test(`${name}.${kind}: every declared child is visited and rewritten, metadata is preserved`, () => { for (const presence of [true, false]) { const expected: (IrExpr | IrStmt)[] = []; function value(type: ts.TypeNode): unknown { if (ts.isTypeReferenceNode(type)) { const node = - type.typeName.getText() === "IrExpr" + ts.getTextOfNode(type.typeName) === "IrExpr" ? num(expected.length) : statement(expected.length); expected.push(node); @@ -72,7 +73,7 @@ for (const name of ["IrExpr", "IrStmt"]) { }; for (const member of type.members.nodes) { if (ts.isPropertySignature(member) && member.type && childType(member.type)) - result[member.name.getText()] = value(member.type); + result[ts.getTextOfNode(member.name)] = value(member.type); } return result; } @@ -86,7 +87,7 @@ for (const name of ["IrExpr", "IrStmt"]) { }; for (const member of members) { if (member.type && childType(member.type) && (presence || !member.postfixToken)) - input[member.name.getText()] = value(member.type); + input[ts.getTextOfNode(member.name)] = value(member.type); } const before = structuredClone(input); const seen: (IrExpr | IrStmt)[] = []; @@ -135,7 +136,7 @@ for (const name of ["IrExpr", "IrStmt"]) { expect(output.loc).toBe(loc); expect((output as unknown as Record)["metadata"]).toBe(input["metadata"]); for (const member of members) { - const key = member.name.getText(); + const key = ts.getTextOfNode(member.name); if (member.postfixToken && !presence) expect(Object.hasOwn(output, key)).toBe(false); } } @@ -217,7 +218,7 @@ test("module traversal covers every declared type slot", () => { const active = new Set(); function slots(node: ts.TypeNode): unknown { if (ts.isTypeReferenceNode(node)) { - const name = node.typeName.getText(); + const name = ts.getTextOfNode(node.typeName); if (name === "IrType") { const value: IrType = { kind: "regex" }; expected.push(value); @@ -268,7 +269,7 @@ test("module traversal covers every declared type slot", () => { for (const member of members) { if (!ts.isPropertySignature(member) || !member.type) continue; const value = slots(member.type); - if (value !== undefined) result[member.name.getText()] = value; + if (value !== undefined) result[ts.getTextOfNode(member.name)] = value; } return result; } @@ -316,20 +317,20 @@ test("type traversal visits all structural children and leaves named shapes as r if (!ts.isTypeLiteralNode(variant)) throw new Error("IrType variants must be records"); const members = variant.members.nodes.filter(ts.isPropertySignature); const kind = JSON.parse( - members.find((member) => member.name.getText() === "kind")!.type!.getText(), + ts.getTextOfNode(members.find((member) => ts.getTextOfNode(member.name) === "kind")!.type!), ) as string; const node: Record = { kind }; const expected: IrType[] = []; for (const member of members) { if (!member.type) continue; const child = - ts.isTypeReferenceNode(member.type) && member.type.typeName.getText() === "IrType"; + ts.isTypeReferenceNode(member.type) && ts.getTextOfNode(member.type.typeName) === "IrType"; const array = - ts.isArrayTypeNode(member.type) && member.type.elementType.getText() === "IrType"; + ts.isArrayTypeNode(member.type) && ts.getTextOfNode(member.type.elementType) === "IrType"; if (!child && !array) continue; const value: IrType = { kind: "fileHandle" }; expected.push(value); - node[member.name.getText()] = array ? [value] : value; + node[ts.getTextOfNode(member.name)] = array ? [value] : value; } const seen: IrType[] = []; expect( diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 69a08fb5a..2a2ddbd2f 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -4159,7 +4159,7 @@ function validateFunction( if (!ok) err(`libCall ${e.fn} must be a promise of its ${cryptoPromise} value`, e.loc); return true; } - if (e.fn === "events.once") { + if (e.fn === "events.once" || e.fn === "events.onceSignal") { const [recv, , boxer, errorBoxer] = e.args; const arrayT = e.type.kind === "promise" ? e.type.inner : null; const boxes = (b: IrExpr | undefined) => @@ -4170,7 +4170,7 @@ function validateFunction( !boxes(boxer) || !boxes(errorBoxer) ) { - err(`libCall events.once must box its event tuples into the promised array`, e.loc); + err(`libCall ${e.fn} must box its event tuples into the promised array`, e.loc); } return true; } diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index 3fa934da1..a9056a093 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -90,6 +90,7 @@ async function main(): Promise { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index a2a15179f..a6b4be63c 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -144,6 +144,7 @@ function validFeatures(value: unknown): value is NativeLinkFeatures { "searchParams", "qs", "dynBuiltinClass", + "dynBuiltinStream", "parseArgs", "stream", "net", diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 35dcbddca..7607034b2 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -78,6 +78,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( wasm: false, dynTier: false, streamDyn: false, + eventsOnceSignal: false, warningReplace: false, stdioExtended: false, regex: true, @@ -98,6 +99,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/wasm/discover.ts b/packages/compiler/src/wasm/discover.ts index 931bc1dac..de38bf64f 100644 --- a/packages/compiler/src/wasm/discover.ts +++ b/packages/compiler/src/wasm/discover.ts @@ -159,7 +159,7 @@ class Discovery { if (!paths && !base64 && !bytes) return; const file = sf.fileName; const lineOf = (node: ts.Node): number => - ts.getLineAndCharacterOfPosition(sf, node.getStart()).line + 1; + ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(node)).line + 1; ts.walkPreorder( sf, (node) => { diff --git a/packages/compiler/test/speculative-hoisted-functions.test.ts b/packages/compiler/test/speculative-hoisted-functions.test.ts index a58310b2a..79a56dcde 100644 --- a/packages/compiler/test/speculative-hoisted-functions.test.ts +++ b/packages/compiler/test/speculative-hoisted-functions.test.ts @@ -9,7 +9,9 @@ import type { IrModule } from "../src/ir/ir.js"; // list above the constructor. The initializer lowers speculatively first, // which hoists the constructor and the helper it calls. Those bodies are // kept, so their failing statements must keep runtime fences; dropping -// them left `isNum` without its return (an SC9002 trap at load). +// them left `isNum` without its return (an SC9002 trap at load). isNum's +// return reads the prototype through Reflect.getPrototypeOf, which has no +// lowering, so it fences. const source = ` function clone(config) { var P = (Num.prototype = { constructor: Num, toString: null }), @@ -20,7 +22,7 @@ function clone(config) { x.v = isNum(v) ? 0 : v * unit; } function isNum(v) { - return v instanceof Num || (!!v && v._isNum === true); + return Reflect.getPrototypeOf(v) === P || (!!v && v._isNum === true); } P._isNum = true; return Num; @@ -59,6 +61,6 @@ test("speculatively hoisted JavaScript functions keep their statement fences", a const mod = await lower(); const isNum = mod.functions.find((fn) => fn.name.endsWith("_isNum")); expect(isNum).toBeDefined(); - expect(fenceCodes(isNum!.body)).toEqual(["SC1090"]); + expect(fenceCodes(isNum!.body)).toEqual(["SC2020"]); expect(mod.functions.flatMap((fn) => fenceCodes(fn.body))).not.toContain("SC9002"); }); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json new file mode 100644 index 000000000..eb1120cae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6470-any-destructuring.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json new file mode 100644 index 000000000..463bc3a67 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6471-any-method-chains.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json new file mode 100644 index 000000000..ec90234c8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6472-namespace-destructuring/lib.ts", + "/tests/corpus/6472-namespace-destructuring/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json new file mode 100644 index 000000000..1747655f0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6473-any-parameter-values.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json new file mode 100644 index 000000000..6a7e7a966 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6474-async-any-callbacks/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json new file mode 100644 index 000000000..d27bf5fd6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6475-js-promise-resolve-adoption/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json new file mode 100644 index 000000000..9c99e2ab0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6490-events-once-signal.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json new file mode 100644 index 000000000..dd95018f0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6490-js-named-promise-executor/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json new file mode 100644 index 000000000..bc69a66e3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6491-catch-any-bindings.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json new file mode 100644 index 000000000..4fdbe7a1d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6491-events-once-streams.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json new file mode 100644 index 000000000..8fa2ceb9a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6492-promise-race-combined.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json new file mode 100644 index 000000000..bb755b9a7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6493-stream-function-callbacks.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json new file mode 100644 index 000000000..77f43443e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6494-events-once-dyn-stream-slot/sources.js", + "/tests/corpus/6494-events-once-dyn-stream-slot/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json new file mode 100644 index 000000000..48964b0a6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6560-constructor-function-readable.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json new file mode 100644 index 000000000..603b083d1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6561-constructor-function-writable.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json new file mode 100644 index 000000000..c5d3d1a62 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6562-constructor-function-transform.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json new file mode 100644 index 000000000..6e7f693c1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6563-constructor-function-stream-errors.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json new file mode 100644 index 000000000..52d8e55ff --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6564-constructor-function-stream-late-hooks.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json new file mode 100644 index 000000000..aa907b2ae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/761-any-objects.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json deleted file mode 100644 index ed04bcd16..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/diagnostics/retired-engine-761-any-objects.ts" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json new file mode 100644 index 000000000..e5c444b78 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/events-once-signal-race.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/parity.test.ts b/packages/compiler/test/ts7/parity.test.ts index 4904f2720..978de5cc2 100644 --- a/packages/compiler/test/ts7/parity.test.ts +++ b/packages/compiler/test/ts7/parity.test.ts @@ -1,4 +1,5 @@ import { Ts7Host } from "../../src/frontend/ts7/program-adapter.js"; +import { symbolName, symbolNameOf } from "../../src/frontend/ts7/semantic-model.js"; /* The two-world agreement battery: every fixture program is parsed and * checked through BOTH worlds — typescript@5.9.3 in-process and the TS7 * adapter over tsgo — and the worlds must agree on structure (walk order, @@ -331,7 +332,7 @@ for (const battery of ALL_BATTERIES) { if (s5[a] !== undefined && isFixtureSymbol(w(), s5[a]!) && s5[a]!.name !== "export=") { // finding 5: the module.exports receiver's symbol NAME differs in // JS files (5.9.3 "export=", tsgo "module") — pinned separately. - expect(s7[a]!.name, `${file}#${ids[a]} symbol name`).toBe(s5[a]!.name); + expect(symbolName(s7[a]!), `${file}#${ids[a]} symbol name`).toBe(s5[a]!.name); } for (let b = a + 1; b < ids.length; b++) { if (s5[a] === undefined || s5[b] === undefined) continue; @@ -432,7 +433,7 @@ test("finding pinned: tsgo does not synthesize CJS expando members; module resol const moduleIdx = n5.findIndex((n) => ts5.isIdentifier(n) && n.getText() === "module"); expect(moduleIdx).toBeGreaterThanOrEqual(0); expect(w.c5.getSymbolAtLocation(n5[moduleIdx]!)?.name).toBe("export="); - expect(w.c7.getSymbolAtLocation(n7[moduleIdx]!)?.name).toBe("module"); + expect(symbolNameOf(w.c7.getSymbolAtLocation(n7[moduleIdx]!))).toBe("module"); }); test("getAwaitedType shim agrees with 5.9.3 across the async battery (pinned limitation aside)", () => { @@ -487,7 +488,7 @@ test("symbol declarations resolve back into the same client AST", () => { const sym7 = w.c7.getSymbolAtLocation(n7[i]!); expect(sym7).toBeDefined(); const aliased = w.c7.getAliasedSymbol(sym7!); - expect(aliased.name).toBe("answer"); + expect(symbolName(aliased)).toBe("answer"); const declHandle = aliased.declarationHandles[0]; expect(declHandle).toBeDefined(); const decl = declHandle!.resolve(w.p7.project); diff --git a/packages/compiler/test/ts7/rpc-api.test.ts b/packages/compiler/test/ts7/rpc-api.test.ts index a8b4aebce..a39c71321 100644 --- a/packages/compiler/test/ts7/rpc-api.test.ts +++ b/packages/compiler/test/ts7/rpc-api.test.ts @@ -14,7 +14,21 @@ import { SemanticSignature, SemanticSymbol, SemanticType, + symbolName, } from "../../src/frontend/ts7/semantic-model.js"; +import { ts7Diagnostic, type Ts7Diagnostic } from "../../src/frontend/ts7/diagnostic.js"; +import type { Ts7DiagnosticData } from "../../src/frontend/ts7/session-schema.generated.js"; + +// The SDK's symbols have `name`; scriptc's give it with symbolName, which reads `escapedName`, as the +// SDK's symbols also have it. +function nameOf(symbol: SdkSymbol): string { + return symbolName(symbol as unknown as SemanticSymbol); +} + +// scriptc's diagnostics have every member set; the SDK's leave absent ones out. +function diagnostics(list: readonly unknown[]): Ts7Diagnostic[] { + return list.map((item) => ts7Diagnostic(item as Ts7DiagnosticData)); +} // scriptc's symbols keep their declarations' node handles in // `declarationHandles`, and the SDK's in `declarations`. @@ -51,12 +65,12 @@ function programFacts(snapshot: Snapshot, config: string, path: string) { options: project.program.getCompilerOptions(), text: file.text, type: checker.typeToString(type), - symbol: symbol.name, + symbol: nameOf(symbol), declaration: declarationHandlesOf(symbol)[0]!.resolve()!.getText(file), - syntactic: project.program.getSyntacticDiagnostics(path), - semantic: project.program.getSemanticDiagnostics(path), - globals: project.program.getGlobalDiagnostics(), - config: project.program.getConfigFileParsingDiagnostics(), + syntactic: diagnostics(project.program.getSyntacticDiagnostics(path)), + semantic: diagnostics(project.program.getSemanticDiagnostics(path)), + globals: diagnostics(project.program.getGlobalDiagnostics()), + config: diagnostics(project.program.getConfigFileParsingDiagnostics()), defaultLibrary: project.program.isSourceFileDefaultLibrary(file), externalLibrary: project.program.isSourceFileFromExternalLibrary(file), }; @@ -233,7 +247,7 @@ test("native semantic registries preserve project scope and snapshot lifetimes", .declarationList.declarations, )[0], texts: [a.checker.typeToString(typeA), b.checker.typeToString(typeB)], - parameters: signatureA.getParameters().map((symbol) => symbol.name), + parameters: signatureA.getParameters().map(nameOf), }, symbolA, typeA, @@ -258,7 +272,7 @@ test("native semantic registries preserve project scope and snapshot lifetimes", first.dispose(); expect(() => actual.typeA.getSymbol()).toThrow("disposed"); expect(() => declarationHandlesOf(actual.symbolA)[0]!.resolve()).toThrow("disposed"); - expect(next.signatureA.getParameters()[0]!.name).toBe("value"); + expect(nameOf(next.signatureA.getParameters()[0]!)).toBe("value"); expect(second.getProject(firstConfig)!.checker.typeToString(next.typeA)).toBe("42"); second.dispose(); expect(() => next.signatureA.getParameters()).toThrow("disposed"); diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index a47ebc830..258df8dec 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -78,6 +78,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_readline.c", "events"], ["scr_readline_tty.c", all("events", "regex")], ["scr_events_emitter.c", "emitter"], + // events.once with options registers through the emitter and event-target units. + ["scr_events_once_signal.c", "eventsOnceSignal"], ["scr_dyn_handle.c", any("emitter", "netEffective")], ["scr_symbol.c", "symbol"], ["scr_bigint_assert.c", all("assert", "bigint")], @@ -94,6 +96,19 @@ export const OPTIONAL_RUNTIME_SOURCES = [ // through the dyn-invoke unit, inspect unhandled 'error' payloads and // create EventEmitter's private symbols. ["scr_dyn_builtin_class.c", all("dynBuiltinClass", "inspect", "dynInvoke", "symbol", "emitter")], + // Their stream half also references the stream unit. + [ + "scr_dyn_builtin_stream.c", + all( + "dynBuiltinStream", + "dynBuiltinClass", + "stream", + "inspect", + "dynInvoke", + "symbol", + "emitter", + ), + ], ["scr_stream.c", "stream"], // The zlib stream classes reference both the zlib engine and the stream unit. ["scr_zlib_stream.c", all("zlib", "stream")], diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 28d642501..20392e3a5 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1159,6 +1159,39 @@ ScrDyn *scr_dyn_new_class(ScrClassObj *cls, const char *type_key); ScrDyn *scr_dyn_builtin_class(double kind); bool scr_dyn_builtin_instanceof(ScrDyn *value, double kind); bool scr_dyn_function_instanceof(ScrDyn *value, ScrDyn *callee); +/* The stream half of those constructors (scr_dyn_builtin_stream.c, linked + * with the dynBuiltinStream feature), registered with + * scr_dyn_builtin_class_streams before the first stream class value is + * read: construct runs `Readable.call(this, opts)` and the other stream + * kinds (+1 or NULL with an exception pending), prototype installs a stream + * kind's methods, and event answers EventEmitter.prototype's methods for a + * receiver with stream state (true when it handled the call; *result +1 or + * NULL with an exception pending). */ +enum { + SCR_EE_M_ON, SCR_EE_M_PREPEND, SCR_EE_M_ONCE, SCR_EE_M_PREPEND_ONCE, SCR_EE_M_OFF, + SCR_EE_M_REMOVE_ALL, SCR_EE_M_EMIT, SCR_EE_M_LISTENERS, SCR_EE_M_RAW_LISTENERS, + SCR_EE_M_LISTENER_COUNT, SCR_EE_M_EVENT_NAMES, SCR_EE_M_SET_MAX, SCR_EE_M_GET_MAX, +}; +typedef struct ScrDynStreamOps { + ScrDyn *(*construct)(int kind, ScrDyn *self, ScrDyn *const *args, size_t argc); + void (*prototype)(int kind, ScrDyn *prototype); + bool (*event)(int method, ScrDyn *self, ScrDyn *const *args, size_t argc, ScrDyn **result); +} ScrDynStreamOps; +void scr_dyn_builtin_class_streams(const ScrDynStreamOps *ops); +/* A stream class value with its stream half (scr_dyn_builtin_stream.c), + * +1; the stream a JavaScript value stands for (a constructor-function + * stream or a boxed runtime stream), +1 or NULL; and the constructor- + * function object of a runtime stream, +1 or NULL. */ +ScrDyn *scr_dyn_builtin_stream_class(double kind); +struct ScrStream *scr_dyn_builtin_stream_of(const ScrDyn *value); +ScrDyn *scr_dyn_builtin_stream_object(struct ScrStream *s); +/* The untyped view of a runtime stream in programs that link that unit + * (+1), a checked cast to a runtime stream class kind (+1 or NULL), and + * the stream.unboxDyn libCall in those programs (scr_stream_unbox_dyn's + * contract, also taking constructor-function streams). */ +ScrDyn *scr_dyn_builtin_stream_view(struct ScrStream *s); +struct ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind); +struct ScrStream *scr_dyn_builtin_stream_unbox(const ScrDyn *value, const ScrStr *message); bool scr_dyn_class_is_key(const ScrDyn *value, const ScrStr *type_key); bool scr_dyn_class_is(const ScrDyn *value, const char *type_key); /* Calls a dyn value: a non-function kind throws the catchable TypeError diff --git a/packages/runtime/src/scr_dyn_builtin_class.c b/packages/runtime/src/scr_dyn_builtin_class.c index 6d894615b..2f947048a 100644 --- a/packages/runtime/src/scr_dyn_builtin_class.c +++ b/packages/runtime/src/scr_dyn_builtin_class.c @@ -15,9 +15,13 @@ * Stream is Node's legacy constructor: EventEmitter.call(this, opts) and a * prototype that inherits EventEmitter's. Readable, Writable, Duplex, * Transform and PassThrough have prototypes chained as in Node, so - * util.inherits onto them and instanceof work, but their native state has - * no dyn object form: calling one with a receiver throws the "not - * supported yet" Error where the construction runs. + * util.inherits onto them and instanceof work. Their stream half (the + * prototypes' methods and `Readable.call(this, opts)`) lives in + * scr_dyn_builtin_stream.c, which registers itself through + * scr_dyn_builtin_class_streams in programs that link it; without it, + * calling one with a receiver throws the "not supported yet" Error where + * the construction runs. EventEmitter's methods hand a receiver with + * stream state to that unit, whose listeners live in the runtime stream. * * One constructor per kind and thread, so every boxing of the same class * value is the same function. All arguments are borrowed. */ @@ -59,6 +63,14 @@ static SCR_TL ScrDyn *bc_symbols[BC_SYMBOLS]; static ScrDyn *bc_class(int kind); +/* The stream half, once registered (scr_dyn_builtin_stream.c). */ +static SCR_TL const ScrDynStreamOps *bc_stream_ops; + +/* EventEmitter.prototype's method on a receiver with stream state. */ +static bool bc_stream_event(int method, ScrDyn *self, ScrDyn *const *args, size_t argc, ScrDyn **result) { + return bc_stream_ops != NULL && bc_stream_ops->event(method, self, args, argc, result); +} + /* ── property helpers ───────────────────────────────────────────────── */ static ScrDyn *bc_key(const char *name) { @@ -585,7 +597,12 @@ static ScrDyn *bc_once_wrap(ScrDyn *target, ScrDyn *type, ScrDyn *listener) { static ScrDyn *bc_on(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); - ScrDyn *result = bc_require_object(self, "_events") + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_ON, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") ? bc_add_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1), false) : NULL; scr_dyn_release(self); return result; @@ -594,7 +611,12 @@ static ScrDyn *bc_on(ScrClosure *closure, ScrDyn *const *args, size_t argc) { static ScrDyn *bc_prepend(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); - ScrDyn *result = bc_require_object(self, "_events") + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_PREPEND, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") ? bc_add_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1), true) : NULL; scr_dyn_release(self); return result; @@ -605,6 +627,10 @@ static ScrDyn *bc_once_common(ScrDyn *const *args, size_t argc, bool prepend) { ScrDyn *type = bc_arg(args, argc, 0); ScrDyn *listener = bc_arg(args, argc, 1); ScrDyn *result = NULL; + if (bc_stream_event(prepend ? SCR_EE_M_PREPEND_ONCE : SCR_EE_M_ONCE, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } if (bc_check_listener(listener) && bc_require_object(self, "_events")) { ScrDyn *wrapper = bc_once_wrap(self, type, listener); if (wrapper) { @@ -629,7 +655,12 @@ static ScrDyn *bc_prepend_once(ScrClosure *closure, ScrDyn *const *args, size_t static ScrDyn *bc_off(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); - ScrDyn *result = bc_require_object(self, "_events") + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_OFF, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") ? bc_remove_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1)) : NULL; scr_dyn_release(self); return result; @@ -641,6 +672,11 @@ static ScrDyn *bc_off(ScrClosure *closure, ScrDyn *const *args, size_t argc) { static ScrDyn *bc_remove_all(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_REMOVE_ALL, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_events")) { scr_dyn_release(self); return NULL; @@ -759,6 +795,11 @@ fail: static ScrDyn *bc_emit(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_EMIT, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_events")) { scr_dyn_release(self); return NULL; @@ -844,6 +885,11 @@ fail_self: /* _listeners(target, type, unwrap): a fresh array. */ static ScrDyn *bc_listeners_of(bool unwrap, ScrDyn *const *args, size_t argc) { ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(unwrap ? SCR_EE_M_LISTENERS : SCR_EE_M_RAW_LISTENERS, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_events")) { scr_dyn_release(self); return NULL; @@ -937,7 +983,12 @@ static ScrDyn *bc_count_listeners(ScrDyn *self, ScrDyn *type, ScrDyn *only) { static ScrDyn *bc_listener_count(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); - ScrDyn *result = bc_require_object(self, "_events") + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_LISTENER_COUNT, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") ? bc_count_listeners(self, bc_arg(args, argc, 0), argc > 1 && !bc_undefined(args[1]) && args[1]->kind != SCR_DYN_NULL ? args[1] : NULL) : NULL; @@ -964,6 +1015,11 @@ static ScrDyn *bc_event_names(ScrClosure *closure, ScrDyn *const *args, size_t a (void)args; (void)argc; ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_EVENT_NAMES, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_eventsCount")) { scr_dyn_release(self); return NULL; @@ -1008,6 +1064,11 @@ static ScrDyn *bc_set_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) scr_dyn_release(self); return NULL; } + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_SET_MAX, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_maxListeners") || !bc_set(self, "_maxListeners", scr_dyn_retain(n))) { scr_dyn_release(self); return NULL; @@ -1056,6 +1117,11 @@ static ScrDyn *bc_get_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) (void)args; (void)argc; ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_GET_MAX, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } double max = bc_require_object(self, "_maxListeners") ? bc_max_listeners(self) : -1; scr_dyn_release(self); if (scr_exc_pending()) return NULL; @@ -1321,6 +1387,8 @@ static ScrDyn *bc_construct(ScrClosure *closure, ScrDyn *const *args, size_t arg ScrDyn *result = NULL; if (kind == BC_EMITTER || kind == BC_STREAM) { if (bc_emitter_init(self, bc_arg(args, argc, 0))) result = scr_dyn_retain(scr_dyn_undefined()); + } else if (bc_stream_ops) { + result = bc_stream_ops->construct(kind, self, args, argc); } else { char message[160]; snprintf(message, sizeof message, @@ -1414,6 +1482,8 @@ static ScrDyn *bc_new_class(int kind, ScrDyn *parent) { } else if (kind == BC_STREAM) { ScrDyn *pipe = scr_dyn_new_func(scr_closure_new(NULL, 0), bc_pipe, 2, "native:stream", "pipe"); bc_set(prototype, "pipe", pipe); + } else if (bc_stream_ops) { + bc_stream_ops->prototype(kind, prototype); } scr_dyn_release(prototype); if (scr_exc_pending()) scr_exc_clear(); @@ -1449,6 +1519,22 @@ static ScrDyn *bc_class(int kind) { return bc_ctors[kind]; } +/* Registers the stream half; constructors that already exist get their + * stream prototypes' methods now. */ +void scr_dyn_builtin_class_streams(const ScrDynStreamOps *ops) { + if (bc_stream_ops) return; + bc_stream_ops = ops; + for (int kind = BC_READABLE; kind < BC_COUNT; kind++) { + if (!bc_ctors[kind]) continue; + ScrDyn *prototype = bc_get(bc_ctors[kind], "prototype"); + if (prototype) { + ops->prototype(kind, prototype); + scr_dyn_release(prototype); + } + if (scr_exc_pending()) scr_exc_clear(); + } +} + /* The constructor value for kind (the BC_* order), +1. */ ScrDyn *scr_dyn_builtin_class(double kind) { int index = (int)kind; diff --git a/packages/runtime/src/scr_dyn_builtin_stream.c b/packages/runtime/src/scr_dyn_builtin_stream.c new file mode 100644 index 000000000..1926b0622 --- /dev/null +++ b/packages/runtime/src/scr_dyn_builtin_stream.c @@ -0,0 +1,1692 @@ +/* Native stream state for JavaScript constructor functions that inherit + * from the node:stream classes the pre-class way: + * + * function Source(opts) { Readable.call(this, opts); } + * util.inherits(Source, Readable); + * Source.prototype._read = function (n) { this.push(...); }; + * + * scr_dyn_builtin_class.c provides Readable, Writable, Duplex, Transform + * and PassThrough as untyped constructors with Node's prototype chain. + * This unit (the dynBuiltinStream feature) gives them their stream half: + * + * - `Readable.call(this, opts)` (and the other four) creates a runtime + * stream (scr_stream.c) over the receiver. The receiver keeps it in a + * hidden slot (an entry of its symbol table that no symbol can name), + * so JavaScript sees only the properties Node's constructor adds: + * `_readableState` / `_writableState` (live read-only views of the + * runtime state), `allowHalfOpen` on the duplex kinds, and the option + * callbacks copied to `_read`, `_write`, `_final`, `_destroy`, + * `_transform` and `_flush`. + * - The runtime stream calls the receiver's `_read`, `_write`, + * `_transform`, `_flush`, `_final` and `_destroy` through its property + * lookup, with the receiver as `this`, as Node's machinery does. + * - The stream prototypes carry Node's methods and accessors (push, read, + * write, end, pipe, destroy, readable, destroyed, ...) over the hidden + * stream, and EventEmitter's methods register listeners with the runtime + * stream's own emitter (scr_dyn_builtin_class.c forwards to them), so the + * runtime's events and pipes reach JavaScript listeners with the + * receiver as `this`. + * + * Lifetime: the receiver holds its stream through a traced capsule, and + * the stream's hook closures hold the receiver through traced boxes, so + * the pair lives while either is reachable and the cycle collector + * reclaims it afterwards. The stream's native record points back at the + * receiver without a reference; the hook closures outlive the record. + * + * Not supported here (each throws the "not supported yet" Error where it + * runs): objectMode and the other options scr_stream.c's dyn option walk + * refuses, `_writev` and `_construct`, symbol event names, listeners() / + * rawListeners(), `wrap()`, and pipes into values that are neither runtime + * streams nor constructor-function streams. */ +#include "scr_runtime.h" + +#include +#include +#include + +enum { BCS_READABLE = 2, BCS_WRITABLE, BCS_DUPLEX, BCS_TRANSFORM, BCS_PASSTHROUGH }; + +static const char bcs_hidden_key[] = "scriptc:stream"; +#define BCS_HIDDEN_LEN (sizeof bcs_hidden_key - 1) + +/* The runtime stream's native record. */ +struct BcsRec; +typedef struct BcsRec { + ScrDyn *object; /* borrowed: the hook closures keep it alive */ + ScrDyn **destroy_cbs; /* destroy(err, cb) callbacks waiting for _destroy */ + size_t destroy_n, destroy_cap; + char cls[64]; /* the constructor's name, for the leak warning */ +} BcsRec; + +static void bcs_rec_release(void *p) { + BcsRec *rec = p; + for (size_t i = 0; i < rec->destroy_n; i++) scr_dyn_release(rec->destroy_cbs[i]); + free(rec->destroy_cbs); + free(rec); +} + +static BcsRec *bcs_rec(ScrStream *s) { + return scr_stream_native(s, bcs_rec_release); +} + +/* ── small helpers ──────────────────────────────────────────────────── */ + +static ScrDyn *bcs_key(const char *name) { + ScrStr *text = scr_str_new(name, strlen(name)); + ScrDyn *key = scr_dyn_new_str(text); + scr_str_release(text); + return key; +} + +/* object[name] with object as the receiver: +1, or NULL with an exception + * pending. */ +static ScrDyn *bcs_get(ScrDyn *object, const char *name) { + ScrDyn *key = bcs_key(name); + ScrDyn *value = scr_dyn_reflect_get(object, key, object); + scr_dyn_release(key); + if (!value && !scr_exc_pending()) value = scr_dyn_retain(scr_dyn_undefined()); + return value; +} + +/* object[name] = value; the value moves in. */ +static bool bcs_set(ScrDyn *object, const char *name, ScrDyn *value) { + ScrDyn *key = bcs_key(name); + scr_dyn_key_set_computed(object, key, value); + scr_dyn_release(key); + scr_dyn_release(value); + return !scr_exc_pending(); +} + +static ScrDyn *bcs_arg(ScrDyn *const *args, size_t argc, size_t index) { + return index < argc ? args[index] : scr_dyn_undefined(); +} + +static bool bcs_nullish(const ScrDyn *value) { + return value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL; +} + +static ScrDyn *bcs_undefined(void) { + return scr_dyn_retain(scr_dyn_undefined()); +} + +static ScrDyn *bcs_str(const char *text) { + return bcs_key(text); +} + +static void bcs_unsupported(const char *message) { + scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020"); +} + +/* fn.call(self, ...args): +1 or NULL. */ +static ScrDyn *bcs_apply(ScrDyn *fn, ScrDyn *self, ScrDyn *const *args, size_t argc, const char *what) { + scr_dyn_this_push_dyn(self); + ScrDyn *result = scr_dyn_call(fn, args, argc, what); + scr_dyn_this_pop(); + return result; +} + +/* An error argument of a completion callback or destroy(): NULL for a + * falsy value (Node tests `if (err)`), else the runtime error that boxes + * back to the same JavaScript value (+1). */ +static ScrError *bcs_error_of(const ScrDyn *value) { + if (!scr_dyn_truthy(value)) return NULL; + if (value->kind == SCR_DYN_STR) return scr_error_new(SCR_ERR_ERROR, value->v.str); + return scr_error_from_dyn(value); +} + +/* ── the hidden slot ────────────────────────────────────────────────── */ + +static void *bcs_capsule_retain(void *p) { return scr_stream_retain(p); } +/* Also the capsule's tag. */ +static void bcs_capsule_release(void *p) { scr_stream_release(p); } + +static bool bcs_stream_vt(const ScrVt *vt) { + return (vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) || + (vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post); +} + +static void bcs_rec_release(void *p); + +/* The stream of a constructor-function stream or one of its state views, + * or of a typed capsule of such a stream (a typed stream value that + * crossed back into JavaScript), borrowed; NULL for other values. */ +static ScrStream *bcs_of(const ScrDyn *value) { + if (value != NULL && value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 && + memcmp(value->v.typed_ref.type_key, "object:", 7) == 0) { + ScrStream *stream = value->v.typed_ref.ptr; + return bcs_stream_vt(stream->vt) && scr_stream_native(stream, bcs_rec_release) ? stream : NULL; + } + if (value == NULL || value->kind != SCR_DYN_OBJ || value->symbol_properties == NULL) return NULL; + ScrDyn *capsule = scr_dyn_obj_get(value->symbol_properties, bcs_hidden_key, BCS_HIDDEN_LEN); + if (capsule == NULL || capsule->kind != SCR_DYN_TYPED_REF || + capsule->v.typed_ref.release != bcs_capsule_release) + return NULL; + return capsule->v.typed_ref.ptr; +} + +/* Stores the stream in the object's symbol table under a storage name no + * symbol produces (symbols store under their address), outside the key + * list, so no reflection reaches it. */ +static void bcs_hide(ScrDyn *object, ScrStream *s) { + scr_dyn_region_store(object, NULL); + if (!object->symbol_properties) object->symbol_properties = scr_dyn_new_obj_null_proto(); + if (!object->symbol_keys) object->symbol_keys = scr_dyn_new_arr(); + ScrDyn *capsule = scr_dyn_new_typed_ref_traced(s, bcs_capsule_retain, bcs_capsule_release, + bcs_hidden_key, BCS_HIDDEN_LEN, + scr_dyn_class_view_unavailable, NULL); + scr_dyn_obj_set(object->symbol_properties, bcs_hidden_key, BCS_HIDDEN_LEN, capsule); +} + +/* A runtime stream boxed as a typed capsule (a typed stream value that + * crossed into JavaScript): +1, or NULL. */ +static ScrStream *bcs_native_of(const ScrDyn *value) { + if (value->kind != SCR_DYN_TYPED_REF || value->v.typed_ref.type_key_len <= 7 || + memcmp(value->v.typed_ref.type_key, "object:", 7) != 0) + return NULL; + ScrStream *stream = scr_dyn_typed_ref_unbox(value); + if (bcs_stream_vt(stream->vt)) return stream; + value->v.typed_ref.release(stream); + return NULL; +} + +/* Any stream a JavaScript value stands for (+1), or NULL. */ +ScrStream *scr_dyn_builtin_stream_of(const ScrDyn *value) { + ScrStream *own = bcs_of(value); + if (own) return scr_stream_retain(own); + return bcs_native_of(value); +} + +/* The JavaScript value of a runtime stream: the constructor-function + * object it belongs to (+1), or NULL for other streams. */ +ScrDyn *scr_dyn_builtin_stream_object(ScrStream *s) { + BcsRec *rec = bcs_rec(s); + return rec ? scr_dyn_retain(rec->object) : NULL; +} + +/* A stream as a listener argument: its constructor-function object, or a + * live view of another runtime stream whose pipe and unpipe take those + * objects (scr_dyn_builtin_stream_view). +1. */ +static ScrDyn *bcs_stream_value(ScrStream *s) { + return scr_dyn_builtin_stream_view(s); +} + +/* The receiver's stream for a prototype method, borrowed; throws Node's + * TypeError (reading the state of a non-stream) and answers NULL. */ +static ScrStream *bcs_need(ScrDyn *self, const char *reading) { + ScrStream *s = bcs_of(self); + if (s) return s; + char message[128]; + if (bcs_nullish(self)) { + snprintf(message, sizeof message, "Cannot read properties of %s (reading '%s')", + self->kind == SCR_DYN_NULL ? "null" : "undefined", reading); + scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); + } else { + snprintf(message, sizeof message, + "stream method '%s' on an object without stream state is not supported yet", reading); + bcs_unsupported(message); + } + return NULL; +} + +/* ── completion callbacks ───────────────────────────────────────────── */ + +enum { BCS_DONE_WRITE, BCS_DONE_FINAL, BCS_DONE_DESTROY, BCS_DONE_TRANSFORM, BCS_DONE_FLUSH }; + +static void bcs_run_destroy_cbs(ScrStream *s, ScrError *err) { + BcsRec *rec = bcs_rec(s); + if (!rec || rec->destroy_n == 0) return; + ScrDyn **cbs = rec->destroy_cbs; + size_t n = rec->destroy_n; + rec->destroy_cbs = NULL; + rec->destroy_n = rec->destroy_cap = 0; + ScrDyn *arg = err ? scr_dyn_from_error(err) : scr_dyn_new_null(); + ScrDyn *object = rec->object; + scr_dyn_retain(object); + for (size_t i = 0; i < n; i++) { + if (!scr_exc_pending()) { + ScrDyn *result = bcs_apply(cbs[i], object, &arg, 1, "callback"); + if (result) scr_dyn_release(result); + } + scr_dyn_release(cbs[i]); + } + free(cbs); + scr_dyn_release(object); + scr_dyn_release(arg); +} + +static ScrDyn *bcs_done_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrStream *s = scr_box_get_ref(closure->caps[0]); + int which = (int)scr_box_get_f64(closure->caps[1]); + ScrError *err = bcs_error_of(bcs_arg(args, argc, 0)); + switch (which) { + case BCS_DONE_WRITE: + scr_stream_write_done(s, err); + break; + case BCS_DONE_FINAL: + scr_stream_final_done(s, err); + break; + case BCS_DONE_DESTROY: { + ScrError *kept = err ? scr_error_retain(err) : NULL; + scr_stream_destroy_done(s, err); + if (!scr_exc_pending()) bcs_run_destroy_cbs(s, kept); + if (kept) scr_error_release(kept); + break; + } + default: { + ScrDyn *data = bcs_arg(args, argc, 1); + ScrBytes *bytes = data->kind == SCR_DYN_BYTES ? scr_bytes_raw_view(data->v.bytes) : NULL; + ScrStr *text = data->kind == SCR_DYN_STR ? scr_str_retain(data->v.str) : NULL; + if (which == BCS_DONE_TRANSFORM) scr_stream_transform_done(s, err, bytes, text); + else scr_stream_flush_done(s, err, bytes, text); + break; + } + } + scr_stream_release(s); + if (scr_exc_pending()) return NULL; + return bcs_undefined(); +} + +static ScrDyn *bcs_done(ScrStream *s, int which) { + ScrClosure *closure = scr_closure_new((void *)bcs_done_call, 2); + closure->caps[0] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); + scr_box_set_ref(closure->caps[0], scr_stream_retain(s)); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], which); + uint32_t arity = which == BCS_DONE_TRANSFORM || which == BCS_DONE_FLUSH ? 2 : 1; + return scr_dyn_new_func(closure, bcs_done_call, arity, "native:stream", ""); +} + +/* ── the hooks: the runtime calls the receiver's underscore methods ─── */ + +static ScrBox *bcs_box(ScrDyn *value /* moves in */) { + ScrBox *box = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(box, value); + return box; +} + +static ScrClosure *bcs_hook(ScrDyn *object, void *inv) { + ScrClosure *closure = scr_closure_new(inv, 1); + closure->caps[0] = bcs_box(scr_dyn_retain(object)); + return closure; +} + +/* receiver[method](...args) with the receiver as `this`: +1 or NULL. */ +static ScrDyn *bcs_invoke(ScrDyn *object, const char *method, ScrDyn *const *args, size_t argc) { + ScrDyn *fn = bcs_get(object, method); + if (!fn) return NULL; + ScrDyn *result = NULL; + if (scr_dyn_is_callable(fn)) { + result = bcs_apply(fn, object, args, argc, method); + } else { + char message[96]; + snprintf(message, sizeof message, "this.%s is not a function", method); + scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); + } + scr_dyn_release(fn); + return result; +} + +/* The thrown value of a hook Node calls inside try/catch, as the runtime + * error its catch hands on (+1); clears the exception. */ +static ScrError *bcs_take_error(void) { + ScrCaught *caught = scr_exc_take(); + ScrDyn *value = scr_caught_to_dyn(caught); + scr_caught_release(caught); + ScrError *err = NULL; + if (value) { + err = scr_dyn_truthy(value) ? bcs_error_of(value) : NULL; + scr_dyn_release(value); + } + if (!err) { + ScrStr *empty = scr_str_new("", 0); + err = scr_error_new(SCR_ERR_ERROR, empty); + scr_str_release(empty); + } + return err; +} + +/* Readable.read: `try { this._read(hwm) } catch (err) { errorOrDestroy }` */ +static void bcs_read_inv(ScrClosure *cb, ScrStream *s, double size) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *n = scr_dyn_new_num(size); + ScrDyn *result = bcs_invoke(object, "_read", &n, 1); + scr_dyn_release(n); + if (result) scr_dyn_release(result); + else if (scr_exc_pending()) { + ScrError *err = bcs_take_error(); + scr_stream_fail(s, err); + scr_error_release(err); + } + scr_dyn_release(object); +} + +/* _write(chunk, 'buffer', cb) and _transform(chunk, 'buffer', cb): a + * throw propagates to the write() caller, as in Node. */ +static void bcs_chunk_call(ScrClosure *cb, ScrStream *s, ScrBytes *chunk, const char *method, int done) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *args[3]; + args[0] = scr_dyn_new_buffer(chunk); + args[1] = bcs_str("buffer"); + args[2] = bcs_done(s, done); + ScrDyn *result = bcs_invoke(object, method, args, 3); + for (int i = 0; i < 3; i++) scr_dyn_release(args[i]); + if (result) scr_dyn_release(result); + scr_dyn_release(object); +} + +static void bcs_write_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) { + bcs_chunk_call(cb, s, chunk, "_write", BCS_DONE_WRITE); +} + +static void bcs_transform_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) { + bcs_chunk_call(cb, s, chunk, "_transform", BCS_DONE_TRANSFORM); +} + +/* callFinal: `try { stream._final(onFinish) } catch (err) { onFinish(err) }`; + * a Transform's _flush runs inside the same try. */ +static void bcs_plain_call(ScrClosure *cb, ScrStream *s, const char *method, int done) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *callback = bcs_done(s, done); + ScrDyn *result = bcs_invoke(object, method, &callback, 1); + scr_dyn_release(callback); + if (result) scr_dyn_release(result); + else if (scr_exc_pending()) { + ScrError *err = bcs_take_error(); + if (done == BCS_DONE_FINAL) scr_stream_final_done(s, err); + else scr_stream_flush_done(s, err, NULL, NULL); + } + scr_dyn_release(object); +} + +static bool bcs_is_function(ScrDyn *object, const char *name, bool *failed); + +/* prefinish reads `stream._final` when the writable side finishes, so a + * method assigned after the constructor runs; without one the finish goes + * on as Node's does when `typeof stream._final !== 'function'`. */ +static void bcs_final_inv(ScrClosure *cb, ScrStream *s) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + bool failed = false; + bool present = bcs_is_function(object, "_final", &failed); + scr_dyn_release(object); + if (failed) return; + if (present) bcs_plain_call(cb, s, "_final", BCS_DONE_FINAL); + else scr_stream_final_done(s, NULL); +} + +/* Transform's final reads `this._flush` the same way: without one it ends + * the readable side. */ +static void bcs_flush_inv(ScrClosure *cb, ScrStream *s) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + bool failed = false; + bool present = bcs_is_function(object, "_flush", &failed); + if (!failed && bcs_is_function(object, "_final", &failed)) { + bcs_unsupported("_final on a constructor-function Transform is not supported yet"); + failed = true; + } + scr_dyn_release(object); + if (failed) return; + if (present) bcs_plain_call(cb, s, "_flush", BCS_DONE_FLUSH); + else scr_stream_flush_done(s, NULL, NULL, NULL); +} + +static ScrDyn *bcs_default_method(int kind, const char *name); + +/* _destroy(err || null, onDestroy) inside try/catch. The prototype's + * default (cb(err)) completes without a call. */ +static void bcs_destroy_inv(ScrClosure *cb, ScrStream *s, ScrError *err) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *fn = bcs_get(object, "_destroy"); + ScrDyn *fallback = bcs_default_method(BCS_READABLE, "_destroy"); + if (fn && fallback && scr_dyn_strict_eq(fn, fallback)) { + ScrError *kept = err ? scr_error_retain(err) : NULL; + scr_stream_destroy_done(s, err ? scr_error_retain(err) : NULL); + if (!scr_exc_pending()) bcs_run_destroy_cbs(s, kept); + if (kept) scr_error_release(kept); + } else if (fn) { + ScrDyn *args[2]; + args[0] = err ? scr_dyn_from_error(err) : scr_dyn_new_null(); + args[1] = bcs_done(s, BCS_DONE_DESTROY); + ScrDyn *result = NULL; + if (scr_dyn_is_callable(fn)) { + result = bcs_apply(fn, object, args, 2, "_destroy"); + } else { + static const char message[] = "this._destroy is not a function"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } + scr_dyn_release(args[0]); + scr_dyn_release(args[1]); + if (result) scr_dyn_release(result); + else if (scr_exc_pending()) { + ScrError *thrown = bcs_take_error(); + ScrError *kept = scr_error_retain(thrown); + scr_stream_destroy_done(s, thrown); + if (!scr_exc_pending()) bcs_run_destroy_cbs(s, kept); + scr_error_release(kept); + } + } + if (fallback) scr_dyn_release(fallback); + if (fn) scr_dyn_release(fn); + scr_dyn_release(object); +} + +/* ── listeners: the runtime emitter, the receiver as `this` ─────────── */ + +enum { BCS_L_FN, BCS_L_SELF, BCS_L_CAPS }; + +static void bcs_listener_call(ScrClosure *adapter, ScrDyn *const *args, size_t argc) { + ScrDyn *fn = scr_box_get_ref(adapter->caps[BCS_L_FN]); + ScrDyn *self = scr_box_get_ref(adapter->caps[BCS_L_SELF]); + ScrDyn *result = bcs_apply(fn, self, args, argc, "listener"); + if (result) scr_dyn_release(result); + scr_dyn_release(self); + scr_dyn_release(fn); +} + +static void bcs_listener0(ScrClosure *adapter) { + bcs_listener_call(adapter, NULL, 0); +} + +static void bcs_listener_data(ScrClosure *adapter, ScrBytes *bytes, ScrStr *text) { + ScrDyn *arg = text ? scr_dyn_new_str(text) : scr_dyn_new_buffer(bytes); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +static void bcs_listener_error(ScrClosure *adapter, ScrError *error) { + ScrDyn *arg = scr_dyn_from_error(error); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +static void bcs_listener_stream(ScrClosure *adapter, ScrStream *stream) { + ScrDyn *arg = bcs_stream_value(stream); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +static void bcs_listener_name(ScrClosure *adapter, ScrStr *name) { + ScrDyn *arg = scr_dyn_new_str(name); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +/* Computed events: the listener's checked-dynamic call, with every + * argument emit passed. */ +static ScrDyn *bcs_listener_flex(ScrClosure *adapter, ScrDyn *const *args, size_t argc) { + bcs_listener_call(adapter, args, argc); + if (scr_exc_pending()) return NULL; + return bcs_undefined(); +} + +static ScrClosure *bcs_adapter(ScrDyn *fn, ScrDyn *self, void *call) { + ScrClosure *adapter = scr_closure_new(call, BCS_L_CAPS); + adapter->caps[BCS_L_FN] = bcs_box(scr_dyn_retain(fn)); + adapter->caps[BCS_L_SELF] = bcs_box(scr_dyn_retain(self)); + scr_dyn_adopt_identity(adapter, fn); + return adapter; +} + +static bool bcs_is(const ScrStr *name, const char *text) { + size_t length = strlen(text); + return name->len == length && memcmp(name->data, text, length) == 0; +} + +/* The runtime's fixed event tuples: 0 none, 1 data, 2 error, 3 a stream + * (pipe/unpipe), 4 an event name (the meta events); -1 computed. */ +static int bcs_event_shape(const ScrStr *name) { + if (bcs_is(name, "data")) return 1; + if (bcs_is(name, "error")) return 2; + if (bcs_is(name, "pipe") || bcs_is(name, "unpipe")) return 3; + if (bcs_is(name, "newListener") || bcs_is(name, "removeListener")) return 4; + static const char *const empty[] = { + "end", "close", "finish", "drain", "readable", "resume", "pause", "prefinish", + }; + for (size_t i = 0; i < sizeof empty / sizeof *empty; i++) + if (bcs_is(name, empty[i])) return 0; + return -1; +} + +/* The event name argument as a string, or NULL with an exception + * pending. */ +static ScrStr *bcs_event_name(const ScrDyn *type) { + if (type->kind == SCR_DYN_STR) return scr_str_retain(type->v.str); + if (type->kind == SCR_DYN_SYMBOL) { + bcs_unsupported("symbol event names on a constructor-function stream are not supported yet"); + return NULL; + } + return scr_dyn_string_coerce_js(type); +} + +static bool bcs_check_listener(const ScrDyn *listener) { + if (listener->kind == SCR_DYN_FUNC) return true; + if (scr_dyn_is_callable(listener)) { + bcs_unsupported("listeners other than functions on a constructor-function stream are not supported yet"); + return false; + } + scr_dyn_arg_type_fail("listener", "of type function", listener); + return false; +} + +static bool bcs_add_listener(ScrStream *s, ScrDyn *self, ScrDyn *type, ScrDyn *listener, bool once, + bool prepend) { + if (!bcs_check_listener(listener)) return false; + ScrStr *name = bcs_event_name(type); + if (!name) return false; + ScrEmitter *em = (ScrEmitter *)s; + ScrEmitter *chained = NULL; + switch (bcs_event_shape(name)) { + case 0: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener0), + scr_ee_inv_fixed0, once, prepend); + break; + case 1: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_data), + scr_ee_inv_fixed2, once, prepend); + break; + case 2: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_error), + scr_ee_inv_fixed1, once, prepend); + break; + case 3: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_stream), + scr_ee_inv_fixed1, once, prepend); + break; + case 4: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_name), + scr_ee_inv_fixed1, once, prepend); + break; + default: { + ScrClosure *adapter = bcs_adapter(listener, self, (void *)bcs_listener_flex); + ScrDyn *wrapper = scr_dyn_new_func(adapter, bcs_listener_flex, 0, "native:events", "listener"); + chained = scr_emitter_on_flex(em, name, wrapper, once, prepend); + scr_dyn_release(wrapper); + break; + } + } + if (chained) scr_emitter_release(chained); + scr_str_release(name); + return !scr_exc_pending(); +} + +/* this.emit(type, ...args) on the runtime emitter: the fixed tuples the + * runtime's own listeners read, computed events with every argument. */ +static ScrDyn *bcs_emit(ScrStream *s, ScrDyn *const *args, size_t argc) { + ScrStr *name = bcs_event_name(bcs_arg(args, argc, 0)); + if (!name) return NULL; + ScrEmitter *em = (ScrEmitter *)s; + ScrDyn *const *rest = argc > 1 ? args + 1 : NULL; + size_t restc = argc > 1 ? argc - 1 : 0; + ScrDyn *first = restc ? rest[0] : scr_dyn_undefined(); + bool had = false; + switch (bcs_event_shape(name)) { + case 0: + had = scr_emitter_emit(em, name); + break; + case 1: + if (first->kind == SCR_DYN_BYTES) { + ScrBytes *bytes = scr_bytes_raw_view(first->v.bytes); + had = scr_emitter_emit(em, name, bytes, (ScrStr *)NULL); + scr_bytes_release(bytes); + } else if (first->kind == SCR_DYN_STR) { + had = scr_emitter_emit(em, name, (ScrBytes *)NULL, first->v.str); + } else { + bcs_unsupported("emit('data') with a chunk other than a Buffer or string on a constructor-function stream is not supported yet"); + } + break; + case 2: + if (!scr_emitter_has(em, "error")) { + /* Node's unhandled 'error': an Error throws itself, anything else + * ERR_UNHANDLED_ERROR. */ + if (scr_dyn_err_instanceof(first, SCR_ERR_ERROR)) { + scr_dyn_throw(scr_dyn_retain(first)); + } else { + ScrStr *shown = scr_insp_dyn(first, 0, 2); + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "Unhandled error. ("); + scr_jb_put_str(&text, shown); + scr_jb_putc(&text, ')'); + scr_str_release(shown); + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "ERR_UNHANDLED_ERROR"); + scr_str_release(message); + } + } else { + ScrError *err = bcs_error_of(first); + if (!err) { + ScrStr *empty = scr_str_new("", 0); + err = scr_error_new(SCR_ERR_ERROR, empty); + scr_str_release(empty); + } + had = scr_emitter_emit_error(em, name, err); + scr_error_release(err); + } + break; + case 3: { + ScrStream *stream = scr_dyn_builtin_stream_of(first); + if (stream) { + had = scr_emitter_emit(em, name, stream); + scr_stream_release(stream); + } else { + bcs_unsupported("emit('pipe') or emit('unpipe') with a value other than a stream on a constructor-function stream is not supported yet"); + } + break; + } + case 4: + if (first->kind == SCR_DYN_STR) had = scr_emitter_emit(em, name, first->v.str); + else bcs_unsupported("emit('newListener') or emit('removeListener') with a name other than a string on a constructor-function stream is not supported yet"); + break; + default: + had = scr_emitter_emit_flex(em, name, rest, restc); + break; + } + scr_str_release(name); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_bool(had); +} + +static ScrDyn *bcs_event_names(ScrStream *s) { + ScrArr *names = scr_emitter_event_names((ScrEmitter *)s); + ScrDyn *result = scr_dyn_new_arr(); + double length = scr_arr_len(names); + for (double i = 0; i < length; i++) { + ScrStr *name = scr_arr_get_ref(names, i); + scr_dyn_arr_push(result, scr_dyn_new_str(name)); + scr_str_release(name); + } + scr_arr_release(names); + return result; +} + +/* EventEmitter.prototype's methods on a constructor-function stream + * (called by scr_dyn_builtin_class.c): true when self has a stream. */ +static bool bcs_event(int method, ScrDyn *self, ScrDyn *const *args, size_t argc, ScrDyn **result) { + ScrStream *s = bcs_of(self); + if (!s) return false; + ScrDyn *type = bcs_arg(args, argc, 0); + ScrEmitter *em = (ScrEmitter *)s; + ScrDyn *out = NULL; + switch (method) { + case SCR_EE_M_ON: + case SCR_EE_M_PREPEND: + case SCR_EE_M_ONCE: + case SCR_EE_M_PREPEND_ONCE: + if (bcs_add_listener(s, self, type, bcs_arg(args, argc, 1), + method == SCR_EE_M_ONCE || method == SCR_EE_M_PREPEND_ONCE, + method == SCR_EE_M_PREPEND || method == SCR_EE_M_PREPEND_ONCE)) + out = scr_dyn_retain(self); + break; + case SCR_EE_M_OFF: { + ScrDyn *listener = bcs_arg(args, argc, 1); + if (!bcs_check_listener(listener)) break; + ScrStr *name = bcs_event_name(type); + if (!name) break; + scr_emitter_release(scr_emitter_off_dyn(em, name, listener)); + scr_str_release(name); + if (!scr_exc_pending()) out = scr_dyn_retain(self); + break; + } + case SCR_EE_M_REMOVE_ALL: { + ScrStr *name = argc == 0 || type->kind == SCR_DYN_UNDEF ? NULL : bcs_event_name(type); + if (argc > 0 && type->kind != SCR_DYN_UNDEF && !name) break; + if (!name) name = scr_str_new("", 0); + scr_emitter_release(scr_emitter_remove_all(em, name, argc == 0 || type->kind == SCR_DYN_UNDEF)); + scr_str_release(name); + if (!scr_exc_pending()) out = scr_dyn_retain(self); + break; + } + case SCR_EE_M_EMIT: + out = bcs_emit(s, args, argc); + break; + case SCR_EE_M_LISTENER_COUNT: { + ScrStr *name = bcs_event_name(type); + if (!name) break; + ScrDyn *only = bcs_arg(args, argc, 1); + double count = bcs_nullish(only) ? scr_emitter_listener_count(em, name) + : only->kind == SCR_DYN_FUNC ? scr_emitter_listener_count_dyn(em, name, only) : 0; + scr_str_release(name); + if (!scr_exc_pending()) out = scr_dyn_new_num(count); + break; + } + case SCR_EE_M_EVENT_NAMES: + out = bcs_event_names(s); + break; + case SCR_EE_M_SET_MAX: + scr_emitter_release(scr_emitter_set_max(em, bcs_arg(args, argc, 0)->v.num)); + out = scr_dyn_retain(self); + break; + case SCR_EE_M_GET_MAX: + out = scr_dyn_new_num(scr_emitter_get_max(em)); + break; + default: + bcs_unsupported("listeners() and rawListeners() on a constructor-function stream are not supported yet"); + break; + } + *result = scr_exc_pending() ? (out ? (scr_dyn_release(out), NULL) : NULL) : out; + return true; +} + +/* ── construction ───────────────────────────────────────────────────── */ + +static bool bcs_is_function(ScrDyn *object, const char *name, bool *failed) { + ScrDyn *value = bcs_get(object, name); + if (!value) { + *failed = true; + return false; + } + bool callable = scr_dyn_is_callable(value); + scr_dyn_release(value); + return callable; +} + +/* Node's constructors copy these options onto the instance. */ +static bool bcs_copy_option(ScrDyn *self, ScrDyn *opts, const char *option, const char *property) { + ScrDyn *value = bcs_get(opts, option); + if (!value) return false; + if (scr_dyn_is_callable(value)) return bcs_set(self, property, value); + scr_dyn_release(value); + return true; +} + +static void bcs_class_name(ScrDyn *self, char *out, size_t size) { + snprintf(out, size, "%s", "Object"); + ScrDyn *ctor = bcs_get(self, "constructor"); + if (!ctor) { + scr_exc_clear(); + return; + } + if (scr_dyn_is_callable(ctor)) { + ScrDyn *name = bcs_get(ctor, "name"); + if (name && name->kind == SCR_DYN_STR && name->v.str->len > 0) + snprintf(out, size, "%.*s", (int)name->v.str->len, name->v.str->data); + if (name) scr_dyn_release(name); + else scr_exc_clear(); + } + scr_dyn_release(ctor); +} + +static ScrDyn *bcs_state_proto(bool readable); + +/* Listeners added before the constructor ran (bl registers its 'pipe' + * listener first) sit in the instance's own _events, where Node's + * constructor keeps them: they move to the runtime emitter, in order. */ +static bool bcs_adopt_listeners(ScrStream *s, ScrDyn *self) { + ScrDyn *events = bcs_get(self, "_events"); + if (!events) return false; + if (events->kind != SCR_DYN_OBJ) { + scr_dyn_release(events); + return true; + } + ScrDyn *keys = scr_dyn_own_keys(events); + bool ok = keys != NULL; + size_t count = ok ? (size_t)scr_dyn_arr_len(keys) : 0; + for (size_t i = 0; ok && i < count; i++) { + ScrDyn *key = scr_dyn_index_get(keys, (double)i); + ScrDyn *list = key ? scr_dyn_reflect_get(events, key, events) : NULL; + if (!list) { + if (key) scr_dyn_release(key); + ok = false; + break; + } + if (list->kind == SCR_DYN_ARR) { + size_t length = (size_t)scr_dyn_arr_len(list); + for (size_t j = 0; ok && j < length; j++) { + ScrDyn *listener = scr_dyn_index_get(list, (double)j); + ok = listener && bcs_add_listener(s, self, key, listener, false, false); + if (listener) scr_dyn_release(listener); + } + } else if (scr_dyn_is_callable(list)) { + ok = bcs_add_listener(s, self, key, list, false, false); + } + scr_dyn_release(list); + scr_dyn_release(key); + } + if (keys) scr_dyn_release(keys); + scr_dyn_release(events); + if (ok && count > 0) + ok = bcs_set(self, "_events", scr_dyn_new_obj_null_proto()) && bcs_set(self, "_eventsCount", scr_dyn_new_num(0)); + return ok; +} + +static bool bcs_state_view(ScrDyn *self, ScrStream *s, bool readable) { + ScrDyn *view = scr_dyn_obj_create(bcs_state_proto(readable)); + bcs_hide(view, s); + return bcs_set(self, readable ? "_readableState" : "_writableState", view); +} + +static ScrDyn *bcs_construct(int kind, ScrDyn *self, ScrDyn *const *args, size_t argc) { + static const char *const names[] = { "", "", "Readable", "Writable", "Duplex", "Transform", "PassThrough" }; + char message[192]; + if (self->kind != SCR_DYN_OBJ || !scr_dyn_builtin_instanceof(self, kind)) { + if (scr_exc_pending()) return NULL; + snprintf(message, sizeof message, + "%s.call() on a receiver that does not inherit %s.prototype is not supported yet", + names[kind], names[kind]); + bcs_unsupported(message); + return NULL; + } + if (bcs_of(self)) { + snprintf(message, sizeof message, + "%s.call() on a receiver that already has stream state is not supported yet", names[kind]); + bcs_unsupported(message); + return NULL; + } + bool failed = false; + bool writes = kind != BCS_READABLE; + if (writes && bcs_is_function(self, "_writev", &failed)) { + bcs_unsupported("_writev on a constructor-function stream is not supported yet"); + return NULL; + } + if (!failed && bcs_is_function(self, "_construct", &failed)) { + bcs_unsupported("_construct on a constructor-function stream is not supported yet"); + return NULL; + } + if (failed) return NULL; + ScrDyn *opts = bcs_arg(args, argc, 0); + ScrStream *s = kind == BCS_READABLE ? scr_stream_new_readable_dyn(opts) + : kind == BCS_WRITABLE ? scr_stream_new_writable_dyn(opts) + : kind == BCS_DUPLEX ? scr_stream_new_duplex_dyn(opts) + : kind == BCS_TRANSFORM ? scr_stream_new_transform_dyn(opts) + : scr_stream_new_passthrough_dyn(opts); + if (!s) return NULL; + bool ok = true; + if (opts->kind == SCR_DYN_OBJ) { + bool transformish = kind == BCS_TRANSFORM || kind == BCS_PASSTHROUGH; + if (!transformish && kind != BCS_WRITABLE) ok = ok && bcs_copy_option(self, opts, "read", "_read"); + if (!transformish && kind != BCS_READABLE) { + ok = ok && bcs_copy_option(self, opts, "write", "_write"); + ok = ok && bcs_copy_option(self, opts, "final", "_final"); + } + ok = ok && bcs_copy_option(self, opts, "destroy", "_destroy"); + if (transformish) { + ok = ok && bcs_copy_option(self, opts, "transform", "_transform"); + ok = ok && bcs_copy_option(self, opts, "flush", "_flush"); + } + } + if (ok && kind >= BCS_DUPLEX) + ok = bcs_set(self, "allowHalfOpen", scr_dyn_new_bool(scr_stream_prop(s, "allowHalfOpen") != 0)); + if (!ok) { + scr_stream_release(s); + return NULL; + } + BcsRec *rec = calloc(1, sizeof *rec); + if (!rec) scr_trap("out of memory"); + rec->object = self; + bcs_class_name(self, rec->cls, sizeof rec->cls); + scr_stream_set_native(s, rec, bcs_rec_release); + s->cls = rec->cls; + /* The runtime calls the instance's underscore methods. */ + if (kind == BCS_READABLE || kind == BCS_DUPLEX) + scr_stream_set_read(s, bcs_hook(self, (void *)bcs_read_inv), bcs_read_inv); + if (kind == BCS_WRITABLE || kind == BCS_DUPLEX) { + scr_stream_set_write(s, bcs_hook(self, (void *)bcs_write_inv), bcs_write_inv); + /* _final is read when the writable side finishes (bcs_final_inv). */ + scr_stream_set_final(s, bcs_hook(self, (void *)bcs_final_inv), bcs_final_inv); + } + if (kind == BCS_TRANSFORM || kind == BCS_PASSTHROUGH) { + /* _transform is read per chunk (a PassThrough's default passes the + * chunk on) and _flush when the writable side finishes. */ + scr_stream_set_transform(s, bcs_hook(self, (void *)bcs_transform_inv), bcs_transform_inv); + scr_stream_set_flush(s, bcs_hook(self, (void *)bcs_flush_inv), bcs_flush_inv); + if (!failed && bcs_is_function(self, "_final", &failed)) { + bcs_unsupported("_final on a constructor-function Transform is not supported yet"); + failed = true; + } + } + scr_stream_set_destroy(s, bcs_hook(self, (void *)bcs_destroy_inv), bcs_destroy_inv); + bcs_hide(self, s); + if (!failed) failed = !bcs_adopt_listeners(s, self); + if (!failed && kind != BCS_WRITABLE) failed = !bcs_state_view(self, s, true); + if (!failed && kind != BCS_READABLE) failed = !bcs_state_view(self, s, false); + scr_stream_release(s); /* the capsule holds it */ + if (failed || scr_exc_pending()) return NULL; + return bcs_undefined(); +} + +/* ── prototype methods ──────────────────────────────────────────────── */ + +/* Node's encoding names, canonical; NULL with ERR_UNKNOWN_ENCODING. */ +static ScrStr *bcs_encoding(const ScrDyn *value) { + static const struct { const char *from, *to; } map[] = { + {"utf8", "utf8"}, {"utf-8", "utf8"}, {"hex", "hex"}, {"base64", "base64"}, + {"base64url", "base64url"}, {"latin1", "latin1"}, {"binary", "latin1"}, + {"ascii", "ascii"}, {"utf16le", "utf16le"}, {"utf-16le", "utf16le"}, + {"ucs2", "utf16le"}, {"ucs-2", "utf16le"}, + }; + ScrStr *raw = value->kind == SCR_DYN_STR ? scr_str_retain(value->v.str) : scr_dyn_string_coerce_js(value); + if (!raw) return NULL; + for (size_t i = 0; i < sizeof map / sizeof *map; i++) { + if (bcs_is(raw, map[i].from)) { + scr_str_release(raw); + return scr_str_new(map[i].to, strlen(map[i].to)); + } + } + char message[128]; + snprintf(message, sizeof message, "Unknown encoding: %.*s", (int)(raw->len < 64 ? raw->len : 64), raw->data); + scr_str_release(raw); + scr_throw_error_msg_code(SCR_ERR_TYPE, message, strlen(message), "ERR_UNKNOWN_ENCODING"); + return NULL; +} + +enum { + BCS_M_PUSH, BCS_M_UNSHIFT, BCS_M_READ, BCS_M_SET_ENCODING, BCS_M_PAUSE, BCS_M_RESUME, + BCS_M_IS_PAUSED, BCS_M_PIPE, BCS_M_UNPIPE, BCS_M_DESTROY, BCS_M_WRITE, BCS_M_END, + BCS_M_CORK, BCS_M_UNCORK, BCS_M_SET_DEFAULT_ENCODING, BCS_M_WRITABLE_PIPE, BCS_M_WRAP, + BCS_M_READ_DEFAULT, BCS_M_WRITE_DEFAULT, BCS_M_TRANSFORM_DEFAULT, BCS_M_DESTROY_DEFAULT, + BCS_M_PASSTHROUGH_TRANSFORM, BCS_M_ON, BCS_M_OFF, BCS_M_REMOVE_ALL, +}; + +enum { BCS_CB_STREAM = BCS_L_CAPS, BCS_CB_CAPS }; + +/* A write or end callback: null after a successful write and at 'finish'; + * once the stream failed, its error, or Node's ERR_STREAM_DESTROYED for a + * write a destroy cut off. */ +static void bcs_callback_fire(ScrClosure *closure) { + ScrDyn *fn = scr_box_get_ref(closure->caps[BCS_L_FN]); + ScrDyn *self = scr_box_get_ref(closure->caps[BCS_L_SELF]); + ScrStream *s = scr_box_get_ref(closure->caps[BCS_CB_STREAM]); + ScrError *err = scr_stream_errored(s); + ScrDyn *arg; + if (err) { + arg = scr_dyn_from_error(err); + scr_error_release(err); + } else if (scr_stream_prop(s, "destroyed") != 0) { + ScrStr *message = scr_str_new("Cannot call write after a stream was destroyed", 46); + ScrError *destroyed = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(message); + scr_error_set_code(destroyed, "ERR_STREAM_DESTROYED"); + arg = scr_dyn_from_error(destroyed); + scr_error_release(destroyed); + } else { + arg = scr_dyn_new_null(); + } + scr_stream_release(s); + ScrDyn *result = bcs_apply(fn, self, &arg, 1, "callback"); + if (result) scr_dyn_release(result); + scr_dyn_release(arg); + scr_dyn_release(self); + scr_dyn_release(fn); +} + +static ScrClosure *bcs_callback(ScrDyn *fn, ScrDyn *self, ScrStream *s) { + if (!scr_dyn_is_callable(fn)) return NULL; + ScrClosure *closure = scr_closure_new((void *)bcs_callback_fire, BCS_CB_CAPS); + closure->caps[BCS_L_FN] = bcs_box(scr_dyn_retain(fn)); + closure->caps[BCS_L_SELF] = bcs_box(scr_dyn_retain(self)); + closure->caps[BCS_CB_STREAM] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); + scr_box_set_ref(closure->caps[BCS_CB_STREAM], scr_stream_retain(s)); + return closure; +} + +/* Strings in another encoding than utf8 have no write path here. */ +static bool bcs_utf8_only(const ScrDyn *encoding) { + if (encoding->kind != SCR_DYN_STR) return true; + ScrStr *canonical = bcs_encoding(encoding); + if (!canonical) return false; + bool utf8 = bcs_is(canonical, "utf8"); + scr_str_release(canonical); + if (!utf8) bcs_unsupported("writing strings in an encoding other than utf8 to a constructor-function stream is not supported yet"); + return utf8; +} + +static ScrDyn *bcs_write(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t argc) { + ScrDyn *chunk = bcs_arg(args, argc, 0); + ScrDyn *encoding = bcs_arg(args, argc, 1); + ScrDyn *cb = bcs_arg(args, argc, 2); + if (scr_dyn_is_callable(encoding)) { + cb = encoding; + encoding = scr_dyn_undefined(); + } + if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL; + bool below = scr_stream_write_dyn(s, chunk, bcs_callback(cb, self, s)); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_bool(below); +} + +static ScrDyn *bcs_end(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t argc) { + ScrDyn *chunk = bcs_arg(args, argc, 0); + ScrDyn *encoding = bcs_arg(args, argc, 1); + ScrDyn *cb = bcs_arg(args, argc, 2); + if (scr_dyn_is_callable(chunk)) { + cb = chunk; + chunk = scr_dyn_undefined(); + encoding = scr_dyn_undefined(); + } else if (scr_dyn_is_callable(encoding)) { + cb = encoding; + encoding = scr_dyn_undefined(); + } + if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL; + ScrStream *chained = NULL; + if (bcs_nullish(chunk)) { + chained = scr_stream_end(s, NULL, NULL, bcs_callback(cb, self, s)); + } else if (chunk->kind == SCR_DYN_STR) { + chained = scr_stream_end(s, NULL, chunk->v.str, bcs_callback(cb, self, s)); + } else if (chunk->kind == SCR_DYN_BYTES) { + ScrBytes *bytes = scr_bytes_raw_view(chunk->v.bytes); + chained = scr_stream_end(s, bytes, NULL, bcs_callback(cb, self, s)); + scr_bytes_release(bytes); + } else { + scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer, TypedArray, or DataView", chunk); + } + if (chained) scr_stream_release(chained); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(self); +} + +static ScrDyn *bcs_destroy(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t argc) { + ScrDyn *errv = bcs_arg(args, argc, 0); + ScrDyn *cb = bcs_arg(args, argc, 1); + if (scr_stream_prop(s, "destroyed") != 0) { + if (scr_dyn_is_callable(cb)) { + ScrDyn *result = bcs_apply(cb, self, NULL, 0, "callback"); + if (!result) return NULL; + scr_dyn_release(result); + } + return scr_dyn_retain(self); + } + if (scr_dyn_is_callable(cb)) { + BcsRec *rec = bcs_rec(s); + if (rec->destroy_n == rec->destroy_cap) { + rec->destroy_cap = rec->destroy_cap ? rec->destroy_cap * 2 : 2; + rec->destroy_cbs = realloc(rec->destroy_cbs, rec->destroy_cap * sizeof *rec->destroy_cbs); + if (!rec->destroy_cbs) scr_trap("out of memory"); + } + rec->destroy_cbs[rec->destroy_n++] = scr_dyn_retain(cb); + } + ScrError *err = bcs_error_of(errv); + ScrStream *chained = scr_stream_destroy(s, err); + if (err) scr_error_release(err); + scr_stream_release(chained); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(self); +} + +/* pipe(dest, { end }): runtime streams and constructor-function streams + * pipe through the runtime's pipe. */ +static bool bcs_overrides(ScrDyn *dest, const char *name); + +static ScrDyn *bcs_pipe(ScrStream *s, ScrDyn *const *args, size_t argc) { + ScrDyn *dest = bcs_arg(args, argc, 0); + ScrDyn *options = bcs_arg(args, argc, 1); + ScrStream *d = scr_dyn_builtin_stream_of(dest); + if (!d) { + if (!scr_exc_pending()) bcs_unsupported("pipe destinations other than streams are not supported yet"); + return NULL; + } + /* The runtime's pipe writes and ends the destination's stream itself; + * Node calls the destination's write() and end(). */ + if (bcs_of(dest) && (bcs_overrides(dest, "write") || bcs_overrides(dest, "end"))) { + scr_stream_release(d); + if (!scr_exc_pending()) + bcs_unsupported("pipe into a constructor-function stream with its own write() or end() is not supported yet"); + return NULL; + } + bool end = true; + if (options->kind == SCR_DYN_OBJ) { + ScrDyn *option = bcs_get(options, "end"); + if (!option) { + scr_stream_release(d); + return NULL; + } + end = !(option->kind == SCR_DYN_BOOL && !option->v.b); + scr_dyn_release(option); + } + ScrStream *chained = scr_stream_pipe(s, d, end); + if (chained) scr_stream_release(chained); + scr_stream_release(d); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(dest); +} + +static ScrDyn *bcs_not_implemented(const char *method) { + char message[64]; + snprintf(message, sizeof message, "The %s method is not implemented", method); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "ERR_METHOD_NOT_IMPLEMENTED"); + return NULL; +} + +static ScrDyn *bcs_method_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + int method = (int)scr_box_get_f64(closure->caps[0]); + switch (method) { + case BCS_M_READ_DEFAULT: + return bcs_not_implemented("_read()"); + case BCS_M_WRITE_DEFAULT: + return bcs_not_implemented("_write()"); + case BCS_M_TRANSFORM_DEFAULT: + return bcs_not_implemented("_transform()"); + case BCS_M_DESTROY_DEFAULT: + case BCS_M_PASSTHROUGH_TRANSFORM: { + /* _destroy(err, cb) => cb(err); _transform(chunk, enc, cb) => cb(null, chunk) */ + ScrDyn *cb = bcs_arg(args, argc, method == BCS_M_DESTROY_DEFAULT ? 1 : 2); + ScrDyn *pass[2]; + size_t passc = 1; + if (method == BCS_M_DESTROY_DEFAULT) { + pass[0] = bcs_arg(args, argc, 0); + } else { + pass[0] = scr_dyn_new_null(); + pass[1] = bcs_arg(args, argc, 0); + passc = 2; + } + ScrDyn *answer = scr_dyn_call(cb, pass, passc, "callback"); + if (method != BCS_M_DESTROY_DEFAULT) scr_dyn_release(pass[0]); + return answer; + } + default: + break; + } + ScrDyn *self = scr_dyn_this_get(); + static const char *const reading[] = { + "push", "unshift", "read", "setEncoding", "pause", "resume", "isPaused", "pipe", "unpipe", + "destroy", "write", "end", "cork", "uncork", "setDefaultEncoding", "pipe", "wrap", + "", "", "", "", "", "on", "removeListener", "removeAllListeners", + }; + ScrDyn *result = NULL; + if (method == BCS_M_WRITABLE_PIPE) { + ScrStream *s = bcs_need(self, "pipe"); + if (s) { + ScrStr *message = scr_str_new("Cannot pipe, not readable", 25); + ScrError *err = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(message); + scr_error_set_code(err, "ERR_STREAM_CANNOT_PIPE"); + scr_stream_fail(s, err); + scr_error_release(err); + if (!scr_exc_pending()) result = scr_dyn_retain(bcs_arg(args, argc, 0)); + } + scr_dyn_release(self); + return result; + } + if (method == BCS_M_ON || method == BCS_M_OFF || method == BCS_M_REMOVE_ALL) { + int ee = method == BCS_M_ON ? SCR_EE_M_ON : method == BCS_M_OFF ? SCR_EE_M_OFF : SCR_EE_M_REMOVE_ALL; + if (!bcs_event(ee, self, args, argc, &result)) { + /* Before the constructor ran (bl adds listeners first), Node's + * Readable.prototype.on is Stream.prototype.on over _events. */ + ScrDyn *emitter = scr_dyn_builtin_class(0); + ScrDyn *prototype = bcs_get(emitter, "prototype"); + scr_dyn_release(emitter); + ScrDyn *fn = prototype ? bcs_get(prototype, reading[method]) : NULL; + result = fn ? bcs_apply(fn, self, args, argc, reading[method]) : NULL; + if (fn) scr_dyn_release(fn); + if (prototype) scr_dyn_release(prototype); + } + scr_dyn_release(self); + return result; + } + ScrStream *s = bcs_need(self, reading[method]); + if (!s) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *first = bcs_arg(args, argc, 0); + switch (method) { + case BCS_M_PUSH: + case BCS_M_UNSHIFT: { + ScrDyn *encoding = bcs_arg(args, argc, 1); + if (method == BCS_M_PUSH && first->kind == SCR_DYN_STR && encoding->kind == SCR_DYN_STR) { + ScrStr *canonical = bcs_encoding(encoding); + if (canonical) { + result = scr_dyn_new_bool(scr_stream_push_str_enc(s, first->v.str, canonical)); + scr_str_release(canonical); + } + } else if (method == BCS_M_PUSH) { + if (first->kind == SCR_DYN_UNDEF) { + ScrStr *empty = scr_str_new("", 0); + result = scr_dyn_new_bool(scr_stream_push_str(s, empty)); + scr_str_release(empty); + } else { + result = scr_dyn_new_bool(scr_stream_push_dyn(s, first)); + } + } else if (first->kind == SCR_DYN_STR) { + scr_stream_unshift_str(s, first->v.str); + result = bcs_undefined(); + } else if (first->kind == SCR_DYN_BYTES) { + ScrBytes *bytes = scr_bytes_raw_view(first->v.bytes); + scr_stream_unshift(s, bytes); + scr_bytes_release(bytes); + result = bcs_undefined(); + } else if (first->kind == SCR_DYN_NULL) { + scr_stream_push_null(s); + result = bcs_undefined(); + } else { + scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", first); + } + break; + } + case BCS_M_READ: + result = scr_stream_read_dyn(s, first); + break; + case BCS_M_SET_ENCODING: { + ScrStr *canonical = bcs_encoding(first->kind == SCR_DYN_UNDEF ? scr_dyn_undefined() : first); + if (canonical) { + scr_stream_release(scr_stream_set_encoding(s, canonical)); + scr_str_release(canonical); + result = scr_dyn_retain(self); + } + break; + } + case BCS_M_PAUSE: + scr_stream_release(scr_stream_pause(s)); + result = scr_dyn_retain(self); + break; + case BCS_M_RESUME: + scr_stream_release(scr_stream_resume(s)); + result = scr_dyn_retain(self); + break; + case BCS_M_IS_PAUSED: + result = scr_dyn_new_bool(scr_stream_is_paused(s)); + break; + case BCS_M_PIPE: + result = bcs_pipe(s, args, argc); + break; + case BCS_M_UNPIPE: { + ScrStream *d = first->kind == SCR_DYN_UNDEF ? NULL : scr_dyn_builtin_stream_of(first); + if (first->kind == SCR_DYN_UNDEF || d) { + scr_stream_release(scr_stream_unpipe(s, d)); + if (d) scr_stream_release(d); + } + if (!scr_exc_pending()) result = scr_dyn_retain(self); + break; + } + case BCS_M_DESTROY: + result = bcs_destroy(s, self, args, argc); + break; + case BCS_M_WRITE: + result = bcs_write(s, self, args, argc); + break; + case BCS_M_END: + result = bcs_end(s, self, args, argc); + break; + case BCS_M_CORK: + scr_stream_cork(s); + result = bcs_undefined(); + break; + case BCS_M_UNCORK: + scr_stream_uncork(s); + result = bcs_undefined(); + break; + case BCS_M_SET_DEFAULT_ENCODING: + if (bcs_utf8_only(first)) result = scr_dyn_retain(self); + break; + default: + bcs_unsupported("Readable.prototype.wrap on a constructor-function stream is not supported yet"); + break; + } + scr_dyn_release(self); + if (scr_exc_pending()) { + if (result) scr_dyn_release(result); + return NULL; + } + return result; +} + +static ScrDyn *bcs_function(int method, const char *name, uint32_t arity) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], method); + return scr_dyn_new_func(closure, bcs_method_call, arity, "native:stream", name); +} + +static void bcs_method(ScrDyn *prototype, const char *name, int method, uint32_t arity) { + bcs_set(prototype, name, bcs_function(method, name, arity)); +} + +/* ── accessors ──────────────────────────────────────────────────────── */ + +enum { + BCS_P_READABLE, BCS_P_READABLE_ENDED, BCS_P_READABLE_LENGTH, BCS_P_READABLE_HWM, + BCS_P_READABLE_OBJECT_MODE, BCS_P_READABLE_FLOWING, BCS_P_WRITABLE, BCS_P_WRITABLE_ENDED, + BCS_P_WRITABLE_FINISHED, BCS_P_WRITABLE_LENGTH, BCS_P_WRITABLE_HWM, BCS_P_WRITABLE_NEED_DRAIN, + BCS_P_WRITABLE_CORKED, BCS_P_WRITABLE_OBJECT_MODE, BCS_P_DESTROYED, BCS_P_CLOSED, BCS_P_ERRORED, + BCS_P_COUNT +}; + +static const char *const bcs_property_names[BCS_P_COUNT] = { + "readable", "readableEnded", "readableLength", "readableHighWaterMark", "readableObjectMode", + "readableFlowing", "writable", "writableEnded", "writableFinished", "writableLength", + "writableHighWaterMark", "writableNeedDrain", "writableCorked", "writableObjectMode", + "destroyed", "closed", "errored", +}; + +static ScrDyn *bcs_flowing(ScrStream *s) { + double flowing = scr_stream_flowing(s); + return flowing < 0 ? scr_dyn_new_null() : scr_dyn_new_bool(flowing != 0); +} + +static ScrDyn *bcs_errored(ScrStream *s) { + ScrError *err = scr_stream_errored(s); + if (!err) return scr_dyn_new_null(); + ScrDyn *value = scr_dyn_from_error(err); + scr_error_release(err); + return value; +} + +static ScrDyn *bcs_getter_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)args; + (void)argc; + int property = (int)scr_box_get_f64(closure->caps[0]); + ScrDyn *self = scr_dyn_this_get(); + ScrStream *s = bcs_of(self); + scr_dyn_release(self); + /* Node's getters answer undefined (or false) before the constructor ran. */ + if (!s) return property == BCS_P_DESTROYED ? scr_dyn_new_bool(false) : bcs_undefined(); + const char *name = bcs_property_names[property]; + switch (property) { + case BCS_P_READABLE_FLOWING: + return bcs_flowing(s); + case BCS_P_ERRORED: + return bcs_errored(s); + case BCS_P_WRITABLE_OBJECT_MODE: + return scr_dyn_new_bool(false); + case BCS_P_READABLE_LENGTH: + case BCS_P_READABLE_HWM: + case BCS_P_WRITABLE_LENGTH: + case BCS_P_WRITABLE_HWM: + case BCS_P_WRITABLE_CORKED: + return scr_dyn_new_num(scr_stream_prop(s, name)); + default: + return scr_dyn_new_bool(scr_stream_prop(s, name) != 0); + } +} + +/* The destroyed setter: Node sets the states' flag without destroying. + * The readable and writable setters store a flag the runtime state does + * not keep (legacy code assigns `this.readable = true`), so they are + * accepted and have no effect. */ +static ScrDyn *bcs_setter_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + int property = (int)scr_box_get_f64(closure->caps[0]); + ScrDyn *self = scr_dyn_this_get(); + ScrStream *s = bcs_of(self); + scr_dyn_release(self); + if (s && property == BCS_P_DESTROYED) scr_stream_set_destroyed(s, scr_dyn_truthy(bcs_arg(args, argc, 0))); + return bcs_undefined(); +} + +static void bcs_define(ScrDyn *target, const char *name, ScrDyn *getter, ScrDyn *setter) { + ScrDyn *descriptor = scr_dyn_new_obj(); + bcs_set(descriptor, "get", getter); + if (setter) bcs_set(descriptor, "set", setter); + bcs_set(descriptor, "enumerable", scr_dyn_new_bool(false)); + bcs_set(descriptor, "configurable", scr_dyn_new_bool(true)); + ScrDyn *key = bcs_key(name); + ScrDyn *defined = scr_dyn_define_property(target, key, descriptor); + if (defined) scr_dyn_release(defined); + scr_dyn_release(key); + scr_dyn_release(descriptor); +} + +static void bcs_accessor(ScrDyn *prototype, int property) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], property); + const char *name = bcs_property_names[property]; + ScrDyn *getter = scr_dyn_new_func(closure, bcs_getter_call, 0, "native:stream", name); + ScrDyn *setter = NULL; + if (property == BCS_P_DESTROYED || property == BCS_P_READABLE || property == BCS_P_WRITABLE) { + ScrClosure *set = scr_closure_new(NULL, 1); + set->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(set->caps[0], property); + setter = scr_dyn_new_func(set, bcs_setter_call, 1, "native:stream", name); + } + bcs_define(prototype, name, getter, setter); +} + +/* ── _readableState / _writableState views ──────────────────────────── */ + +static const char *const bcs_rs_names[] = { + "highWaterMark", "length", "ended", "endEmitted", "reading", "destroyed", "objectMode", + "needReadable", "emittedReadable", "readableListening", "resumeScheduled", "errorEmitted", + "closed", "constructed", "flowing", "errored", +}; +static const char *const bcs_ws_names[] = { + "highWaterMark", "length", "ended", "ending", "finished", "prefinished", "destroyed", + "objectMode", "needDrain", "corked", "closed", "constructed", "errorEmitted", + "bufferedRequestCount", "errored", +}; +#define BCS_RS_COUNT (sizeof bcs_rs_names / sizeof *bcs_rs_names) +#define BCS_WS_COUNT (sizeof bcs_ws_names / sizeof *bcs_ws_names) + +static SCR_TL ScrDyn *bcs_state_protos[2]; +static SCR_TL bool bcs_teardown_registered; + +static ScrDyn *bcs_state_get(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)args; + (void)argc; + bool readable = scr_box_get_f64(closure->caps[0]) != 0; + size_t index = (size_t)scr_box_get_f64(closure->caps[1]); + const char *name = readable ? bcs_rs_names[index] : bcs_ws_names[index]; + ScrDyn *self = scr_dyn_this_get(); + ScrStream *s = bcs_of(self); + scr_dyn_release(self); + if (!s) return bcs_undefined(); + if (!strcmp(name, "flowing")) return bcs_flowing(s); + if (!strcmp(name, "errored")) return bcs_errored(s); + char key[48]; + snprintf(key, sizeof key, "%s:%s", readable ? "rs" : "ws", name); + double value = scr_stream_prop(s, key); + bool number = !strcmp(name, "highWaterMark") || !strcmp(name, "length") || !strcmp(name, "corked") || + !strcmp(name, "bufferedRequestCount"); + return number ? scr_dyn_new_num(value) : scr_dyn_new_bool(value != 0); +} + +static void bcs_teardown(void) { + for (int i = 0; i < 2; i++) { + if (bcs_state_protos[i]) scr_dyn_release(bcs_state_protos[i]); + bcs_state_protos[i] = NULL; + } + if (scr_exc_pending()) scr_exc_clear(); +} + +static ScrDyn *bcs_state_proto(bool readable) { + ScrDyn **slot = &bcs_state_protos[readable ? 0 : 1]; + if (!*slot) { + if (!bcs_teardown_registered) { + bcs_teardown_registered = true; + scr_atexit(bcs_teardown); + } + ScrDyn *proto = scr_dyn_new_obj(); + size_t count = readable ? BCS_RS_COUNT : BCS_WS_COUNT; + for (size_t i = 0; i < count; i++) { + ScrClosure *closure = scr_closure_new(NULL, 2); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], readable ? 1 : 0); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], (double)i); + const char *name = readable ? bcs_rs_names[i] : bcs_ws_names[i]; + bcs_define(proto, name, scr_dyn_new_func(closure, bcs_state_get, 0, "native:stream", name), NULL); + } + *slot = proto; + } + return *slot; +} + +/* ── the prototypes ─────────────────────────────────────────────────── */ + +static ScrDyn *bcs_default_method(int kind, const char *name) { + ScrDyn *ctor = scr_dyn_builtin_class(kind); + ScrDyn *prototype = bcs_get(ctor, "prototype"); + scr_dyn_release(ctor); + if (!prototype) return NULL; + ScrDyn *method = bcs_get(prototype, name); + scr_dyn_release(prototype); + return method; +} + +static void bcs_writable_members(ScrDyn *prototype) { + bcs_method(prototype, "write", BCS_M_WRITE, 3); + bcs_method(prototype, "cork", BCS_M_CORK, 0); + bcs_method(prototype, "uncork", BCS_M_UNCORK, 0); + bcs_method(prototype, "setDefaultEncoding", BCS_M_SET_DEFAULT_ENCODING, 1); + bcs_method(prototype, "_write", BCS_M_WRITE_DEFAULT, 3); + bcs_set(prototype, "_writev", scr_dyn_new_null()); + bcs_method(prototype, "end", BCS_M_END, 3); + for (int p = BCS_P_WRITABLE; p <= BCS_P_WRITABLE_OBJECT_MODE; p++) bcs_accessor(prototype, p); +} + +/* Node's stream prototypes: Readable and Writable carry their methods, + * Duplex adds Writable's, Transform and PassThrough their _transform. */ +static void bcs_prototype(int kind, ScrDyn *prototype) { + switch (kind) { + case BCS_READABLE: + bcs_method(prototype, "destroy", BCS_M_DESTROY, 2); + bcs_method(prototype, "_destroy", BCS_M_DESTROY_DEFAULT, 2); + bcs_method(prototype, "push", BCS_M_PUSH, 2); + bcs_method(prototype, "unshift", BCS_M_UNSHIFT, 2); + bcs_method(prototype, "isPaused", BCS_M_IS_PAUSED, 0); + bcs_method(prototype, "setEncoding", BCS_M_SET_ENCODING, 1); + bcs_method(prototype, "read", BCS_M_READ, 1); + bcs_method(prototype, "_read", BCS_M_READ_DEFAULT, 1); + bcs_method(prototype, "pipe", BCS_M_PIPE, 2); + bcs_method(prototype, "on", BCS_M_ON, 2); + { + ScrDyn *on = bcs_get(prototype, "on"); + if (on) bcs_set(prototype, "addListener", on); + } + bcs_method(prototype, "removeListener", BCS_M_OFF, 2); + { + ScrDyn *off = bcs_get(prototype, "removeListener"); + if (off) bcs_set(prototype, "off", off); + } + bcs_method(prototype, "removeAllListeners", BCS_M_REMOVE_ALL, 1); + bcs_method(prototype, "resume", BCS_M_RESUME, 0); + bcs_method(prototype, "pause", BCS_M_PAUSE, 0); + bcs_method(prototype, "unpipe", BCS_M_UNPIPE, 1); + bcs_method(prototype, "wrap", BCS_M_WRAP, 1); + for (int p = BCS_P_READABLE; p <= BCS_P_READABLE_FLOWING; p++) bcs_accessor(prototype, p); + bcs_accessor(prototype, BCS_P_DESTROYED); + bcs_accessor(prototype, BCS_P_CLOSED); + bcs_accessor(prototype, BCS_P_ERRORED); + break; + case BCS_WRITABLE: + bcs_method(prototype, "pipe", BCS_M_WRITABLE_PIPE, 0); + bcs_writable_members(prototype); + bcs_method(prototype, "destroy", BCS_M_DESTROY, 2); + bcs_method(prototype, "_destroy", BCS_M_DESTROY_DEFAULT, 2); + bcs_accessor(prototype, BCS_P_DESTROYED); + bcs_accessor(prototype, BCS_P_CLOSED); + bcs_accessor(prototype, BCS_P_ERRORED); + break; + case BCS_DUPLEX: + bcs_writable_members(prototype); + bcs_accessor(prototype, BCS_P_DESTROYED); + break; + case BCS_TRANSFORM: + bcs_method(prototype, "_transform", BCS_M_TRANSFORM_DEFAULT, 3); + break; + case BCS_PASSTHROUGH: + bcs_method(prototype, "_transform", BCS_M_PASSTHROUGH_TRANSFORM, 3); + break; + default: + break; + } + if (scr_exc_pending()) scr_exc_clear(); +} + +static const ScrDynStreamOps bcs_ops = { bcs_construct, bcs_prototype, bcs_event }; + +/* A stream class value (the BC_* kinds of scr_dyn_builtin_class.c) with + * its stream half attached, +1. */ +ScrDyn *scr_dyn_builtin_stream_class(double kind) { + scr_dyn_builtin_class_streams(&bcs_ops); + return scr_dyn_builtin_class(kind); +} + +/* Whether dest's method is its own rather than the stream prototypes'. */ +static bool bcs_overrides(ScrDyn *dest, const char *name) { + ScrDyn *own = bcs_get(dest, name); + if (!own) return true; + ScrDyn *fallback = bcs_default_method(BCS_WRITABLE, name); + ScrDyn *duplex = bcs_default_method(BCS_DUPLEX, name); + bool overridden = !(fallback && scr_dyn_strict_eq(own, fallback)) && !(duplex && scr_dyn_strict_eq(own, duplex)); + if (fallback) scr_dyn_release(fallback); + if (duplex) scr_dyn_release(duplex); + scr_dyn_release(own); + return overridden; +} + +/* pipe and unpipe on a runtime stream's untyped view, which also take + * constructor-function streams. */ +static ScrDyn *bcs_view_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrStream *s = scr_box_get_ref(closure->caps[0]); + bool pipe = scr_box_get_f64(closure->caps[1]) != 0; + ScrDyn *result = NULL; + if (pipe) { + result = bcs_pipe(s, args, argc); + } else { + ScrDyn *first = bcs_arg(args, argc, 0); + ScrStream *d = first->kind == SCR_DYN_UNDEF ? NULL : scr_dyn_builtin_stream_of(first); + if (first->kind == SCR_DYN_UNDEF || d) { + scr_stream_release(scr_stream_unpipe(s, d)); + if (d) scr_stream_release(d); + } else { + scr_dyn_arg_type_fail("destination", "an instance of Stream", first); + } + if (!scr_exc_pending()) result = scr_dyn_this_get(); + } + scr_stream_release(s); + return result; +} + +/* The untyped view of a runtime stream crossing into JavaScript, used in + * place of scr_stream_dyn_view by programs that link this unit: a + * constructor-function stream's own object, or the runtime view with pipe + * and unpipe that take those objects. +1. */ +ScrDyn *scr_dyn_builtin_stream_view(ScrStream *s) { + ScrDyn *object = scr_dyn_builtin_stream_object(s); + if (object) return object; + ScrDyn *view = scr_stream_dyn_view(s); + ScrDyn *prototype = scr_dyn_get_prototype(view); + if (prototype && prototype->kind == SCR_DYN_OBJ) { + for (int pipe = 0; pipe < 2; pipe++) { + ScrClosure *closure = scr_closure_new((void *)bcs_view_call, 2); + closure->caps[0] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); + scr_box_set_ref(closure->caps[0], scr_stream_retain(s)); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], pipe); + const char *name = pipe ? "pipe" : "unpipe"; + bcs_set(prototype, name, scr_dyn_new_func(closure, bcs_view_call, 0, "native stream method", name)); + } + } + if (prototype) scr_dyn_release(prototype); + if (scr_exc_pending()) scr_exc_clear(); + return view; +} + +/* A checked cast of an untyped value to a runtime stream class (kind: + * the BC_* numbering): the stream of a constructor-function stream of a + * matching class, +1, or NULL. */ +ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind) { + if (value->kind != SCR_DYN_OBJ) return NULL; + ScrStream *s = bcs_of(value); + if (!s) return NULL; + const ScrVt *vt = s->vt; + bool readable = vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post; + bool writable = vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post; + bool duplex = vt->pre >= scr_duplex_vt.pre && vt->pre <= scr_duplex_vt.post; + bool ok = false; + switch ((int)kind) { + case BCS_READABLE: ok = readable; break; + case BCS_WRITABLE: ok = writable || duplex; break; + case BCS_DUPLEX: ok = duplex; break; + case BCS_TRANSFORM: ok = vt->pre >= scr_transform_vt.pre && vt->pre <= scr_transform_vt.post; break; + case BCS_PASSTHROUGH: ok = vt->pre >= scr_passthrough_vt.pre && vt->pre <= scr_passthrough_vt.post; break; + default: break; + } + return ok ? scr_stream_retain(s) : NULL; +} + +/* stream.unboxDyn (an untyped pipe destination or pipeline stage) in + * programs that link this unit: a constructor-function stream answers its + * runtime stream (+1), unless it overrides write() or end(), which the + * runtime's pipe would bypass; other values take scr_stream_unbox_dyn. */ +ScrStream *scr_dyn_builtin_stream_unbox(const ScrDyn *value, const ScrStr *message) { + ScrStream *s = bcs_of(value); + if (s && value->kind == SCR_DYN_OBJ) { + ScrDyn *dest = (ScrDyn *)value; + if (bcs_overrides(dest, "write") || bcs_overrides(dest, "end")) { + if (!scr_exc_pending()) + bcs_unsupported("pipes into a constructor-function stream with its own write() or end() are not supported yet"); + return NULL; + } + return scr_stream_retain(s); + } + return scr_stream_unbox_dyn(value, message); +} diff --git a/packages/runtime/src/scr_events_emitter.c b/packages/runtime/src/scr_events_emitter.c index e0e305bde..4ac7abdf1 100644 --- a/packages/runtime/src/scr_events_emitter.c +++ b/packages/runtime/src/scr_events_emitter.c @@ -518,8 +518,25 @@ static ScrClosure *scr_ee_once_closure(void *adapter, ScrClosure *boxer, ScrEeOn return c; } +static ScrBox *scr_ee_once_promise_box(ScrPromise *p) { + ScrBox *box = scr_box_new_obj(&scr_promise_retain_v, &scr_promise_release_v, &scr_promise_trace_v); + scr_box_set_ref(box, scr_promise_retain(p)); + return box; +} + +static void scr_ee_once_adopt_job(ScrClosure *job) { + ScrPromise *outer = scr_box_get_ref(job->caps[0]); + ScrPromise *inner = scr_box_get_ref(job->caps[1]); + scr_promise_race_join(outer, inner, &scr_promise_adapt_copy); + scr_promise_release(inner); + scr_promise_release(outer); +} + /* boxer and err_boxer are borrowed; the result is +1. err_boxer is NULL - * when the awaited event is 'error' itself. */ + * when the awaited event is 'error' itself. Node's once is an async + * function returning the executor's promise, so the result adopts that + * promise through a thenable job queued here, whose reaction settles the + * result one turn after the executor's promise settles. */ ScrPromise *scr_events_once(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, void *adapter, ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, ScrEeInvoke err_inv) { @@ -558,9 +575,13 @@ ScrPromise *scr_events_once(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, voi scr_caught_release(thrown); scr_promise_reject_pending(st->promise); } - ScrPromise *p = scr_promise_retain(st->promise); + ScrPromise *outer = scr_promise_new(); + ScrClosure *job = scr_closure_new((void *)&scr_ee_once_adopt_job, 2); + job->caps[0] = scr_ee_once_promise_box(outer); + job->caps[1] = scr_ee_once_promise_box(st->promise); + scr_queue_microtask(job); scr_ee_once_release(st); - return p; + return outer; } /* The adapter's tail: `cb` is the firing listener closure, `args` the @@ -1130,3 +1151,9 @@ double scr_emitter_get_default_max(void) { return scr_emitter_default_max; } static SCR_TL double scr_emitter_esm_default_max = 10; double scr_emitter_esm_default_max_get(void) { return scr_emitter_esm_default_max; } void scr_emitter_esm_sync(void) { scr_emitter_esm_default_max = scr_emitter_default_max; } + +/* events.once with a signal (scr_events_once_signal.c) undoes an add whose + * 'newListener' announcement threw. */ +void scr_emitter_unadd_quiet(ScrEmitter *em, ScrStr *name, ScrClosure *cb) { + scr_ee_unadd(em, name, cb); +} diff --git a/packages/runtime/src/scr_events_emitter.h b/packages/runtime/src/scr_events_emitter.h index a92ae1864..39a7f4b5b 100644 --- a/packages/runtime/src/scr_events_emitter.h +++ b/packages/runtime/src/scr_events_emitter.h @@ -64,6 +64,20 @@ ScrPromise *scr_events_once(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, voi ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, ScrEeInvoke err_inv); void scr_events_once_fire(ScrClosure *cb, ScrArr *args); +/* events.once(emitter, name, { signal }) (scr_events_once_signal.c): the + * scr_events_once arguments plus the checked-dynamic signal (borrowed; + * undefined for the plain form). Never throws: a non-AbortSignal value, + * an aborted signal or a throwing 'newListener' listener rejects the +1 + * result. Its adapters end in scr_events_once_signal_fire. */ +typedef struct ScrDyn ScrDyn; /* full definition below (C11 repeat) */ +ScrPromise *scr_events_once_signal(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, void *adapter, + ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, + ScrEeInvoke err_inv, ScrDyn *signal); +void scr_events_once_signal_fire(ScrClosure *cb, ScrArr *args); +/* Removes the listener entry whose closure is `cb` (borrowed) without a + * 'removeListener' event: the undo of an add whose 'newListener' + * announcement threw. */ +void scr_emitter_unadd_quiet(ScrEmitter *em, ScrStr *name, ScrClosure *cb); ScrEmitter *scr_emitter_on(ScrEmitter *em, ScrStr *name, ScrClosure *cb /*moves*/, ScrEeInvoke inv, bool once, bool prepend); /* The dyn-adapted registration family (JS-lane checked-dynamic diff --git a/packages/runtime/src/scr_events_once_signal.c b/packages/runtime/src/scr_events_once_signal.c new file mode 100644 index 000000000..c3c2db37d --- /dev/null +++ b/packages/runtime/src/scr_events_once_signal.c @@ -0,0 +1,303 @@ +/* events.once(emitter, name, { signal }) over typed listeners — Node 24's + * lib/events.js `once` with an options object. Linked only by programs + * that pass the options (the emitter and event-target units together). + * + * The signal is validated first: undefined is the plain form, a value that + * is not an AbortSignal rejects with ERR_INVALID_ARG_TYPE, and an aborted + * signal rejects with an AbortError whose cause is signal.reason, all + * without registering a listener. Otherwise the event's one-shot listener, + * the 'error' one (unless the event is 'error') and a once, + * resist-stop-propagation 'abort' listener on the signal share the state + * below. The event or the error removes the other emitter listener and the + * abort listener, then settles; the abort removes both emitter listeners + * and rejects with the AbortError. + * + * Ownership: the state owns the promise, the name and the signal. The + * emitter is weak in the state; the abort listener holds it strongly (the + * abort is the one settlement that does not come from the emitter itself, + * like the closure Node's abortListener keeps). The three listener closures + * reach the state through link objects; a link clears the state's pointer + * to its closure when that closure is freed, so removals never compare a + * stale identity. The cycle through the signal's listener ends when the + * promise settles or the signal aborts (or at the exit teardown of the + * signal's listeners). */ +#include "scr_runtime.h" + +#include + +typedef struct ScrEosState { + size_t rc; + ScrPromise *promise; /* owned */ + ScrEmitter *em; /* weak: alive whenever a listener fires */ + ScrStr *name; /* owned */ + ScrDyn *signal; /* owned; undefined without an abort listener */ + ScrClosure *event_cb; /* weak, NULL once freed */ + ScrClosure *error_cb; /* weak, NULL once freed or for 'error' */ + ScrDyn *listener; /* weak abort listener, NULL once freed */ + bool settled; +} ScrEosState; + +enum { SCR_EOS_EVENT, SCR_EOS_ERROR, SCR_EOS_ABORT }; + +typedef struct ScrEosLink { + size_t rc; + ScrEosState *st; /* owned */ + int slot; +} ScrEosLink; + +static void scr_eos_oom(void) { scr_trap("scriptc: out of memory in events.once\n"); } + +static void scr_eos_state_release(ScrEosState *st) { + if (--st->rc > 0) return; + scr_promise_release(st->promise); + scr_str_release(st->name); + scr_dyn_release(st->signal); + free(st); +} + +static void *scr_eos_link_retain(void *p) { + ((ScrEosLink *)p)->rc++; + return p; +} + +static void scr_eos_link_release(void *p) { + ScrEosLink *link = p; + if (--link->rc > 0) return; + ScrEosState *st = link->st; + if (link->slot == SCR_EOS_EVENT) st->event_cb = NULL; + else if (link->slot == SCR_EOS_ERROR) st->error_cb = NULL; + else st->listener = NULL; + scr_eos_state_release(st); + free(link); +} + +static ScrBox *scr_eos_link_box(ScrEosState *st, int slot) { + ScrEosLink *link = calloc(1, sizeof *link); + if (!link) scr_eos_oom(); + link->rc = 1; + link->st = st; + link->slot = slot; + st->rc++; + ScrBox *box = scr_box_new_obj(&scr_eos_link_retain, &scr_eos_link_release, NULL); + scr_box_set_ref(box, link); + return box; +} + +/* An emitter listener: caps[0] its boxer (the compiled adapter calls it), + * caps[1] its link. */ +static ScrClosure *scr_eos_closure(void *adapter, ScrClosure *boxer, ScrEosState *st, int slot) { + ScrClosure *c = scr_closure_new(adapter, 2); + ScrBox *bx = scr_box_new(SCR_BOX_FUNC); + scr_box_set_ref(bx, scr_closure_retain(boxer)); + c->caps[0] = bx; + c->caps[1] = scr_eos_link_box(st, slot); + return c; +} + +/* new AbortError(undefined, { cause: signal.reason }), thrown. */ +static void scr_eos_throw_abort(ScrDyn *signal) { + ScrStr *message = scr_str_new("The operation was aborted", 25); + ScrError *e = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(message); + scr_str_release(e->name); + e->name = scr_str_new("AbortError", 10); + /* AbortError assigns code, then name: own enumerable properties */ + scr_error_set_code(e, "ABORT_ERR"); + e->name_present = true; + e->name_enumerable = true; + ScrStr *k = scr_str_new("reason", 6); + ScrDyn *reason = scr_dyn_handle_key_get(signal, k); + scr_str_release(k); + scr_error_define_cause(e, reason ? reason : scr_dyn_undefined()); + if (reason) scr_dyn_release(reason); + scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg()); +} + +/* removeEventListener('abort', listener) on the signal, when the listener + * is still registered. */ +static void scr_eos_remove_abort(ScrEosState *st) { + if (st->listener == NULL) return; + bool aborted; + ScrEventTargetCore *core = scr_et_signal_core_fn ? scr_et_signal_core_fn(st->signal, &aborted) : NULL; + if (!core) return; + ScrStr *abort = scr_str_new("abort", 5); + ScrDyn *type = scr_dyn_new_str(abort); + scr_str_release(abort); + ScrDyn *listener = scr_dyn_retain(st->listener); + ScrDyn *remove_args[2] = {type, listener}; + ScrDyn *r = scr_et_remove_listener(core, remove_args, 2); + if (r) scr_dyn_release(r); + scr_dyn_release(type); + scr_dyn_release(listener); +} + +/* emitter.removeListener(name, cb) for a listener closure still alive. */ +static bool scr_eos_off(ScrEmitter *em, ScrStr *name, ScrClosure *cb) { + if (cb == NULL) return true; + scr_emitter_release(scr_emitter_off(em, name, cb)); + return !scr_exc_pending(); +} + +/* The compiled adapter's tail: `cb` is the firing emitter listener, `args` + * the boxed event arguments (moves in). */ +void scr_events_once_signal_fire(ScrClosure *cb, ScrArr *args) { + ScrEosLink *link = scr_box_get_ref(cb->caps[1]); + ScrEosState *st = link->st; + if (!st->settled) { + st->settled = true; + bool error = link->slot == SCR_EOS_ERROR; + /* Node's errorListener removes the event's listener and resolver the + * error's, each through removeListener; then the abort listener. */ + bool ok; + if (error) { + ok = scr_eos_off(st->em, st->name, st->event_cb); + } else { + ScrStr *error_name = scr_str_new("error", 5); + ok = scr_eos_off(st->em, error_name, st->error_cb); + scr_str_release(error_name); + } + if (ok) { + scr_eos_remove_abort(st); + if (error) { + ScrDyn *reason = (ScrDyn *)scr_arr_get_ref(args, 0); + scr_dyn_throw(reason); /* moves reason */ + scr_promise_reject_pending(st->promise); + } else { + scr_promise_fulfill_ref(st->promise, scr_arr_retain(args), scr_arr_retain_v, + scr_arr_release_v, args->cyc ? scr_arr_trace_v : NULL); + } + } + } + scr_arr_release(args); + scr_eos_link_release(link); +} + +/* The signal's 'abort' listener: caps[0] its link, caps[1] the emitter. */ +static ScrDyn *scr_eos_abort_thunk(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + (void)args; + (void)argc; + ScrEosLink *link = scr_box_get_ref(clo->caps[0]); + ScrEmitter *em = scr_box_get_ref(clo->caps[1]); + ScrEosState *st = link->st; + bool ok = true; + if (!st->settled) { + st->settled = true; + ok = scr_eos_off(em, st->name, st->event_cb); + if (ok) { + ScrStr *error_name = scr_str_new("error", 5); + ok = scr_eos_off(em, error_name, st->error_cb); + scr_str_release(error_name); + } + if (ok) { + scr_eos_throw_abort(st->signal); + scr_promise_reject_pending(st->promise); + } + } + scr_emitter_release(em); + scr_eos_link_release(link); + return ok ? scr_dyn_retain(scr_dyn_undefined()) : NULL; +} + +static ScrBox *scr_eos_promise_box(ScrPromise *p) { + ScrBox *box = scr_box_new_obj(&scr_promise_retain_v, &scr_promise_release_v, &scr_promise_trace_v); + scr_box_set_ref(box, scr_promise_retain(p)); + return box; +} + +static void scr_eos_adopt_job(ScrClosure *job) { + ScrPromise *outer = scr_box_get_ref(job->caps[0]); + ScrPromise *inner = scr_box_get_ref(job->caps[1]); + scr_promise_race_join(outer, inner, &scr_promise_adapt_copy); + scr_promise_release(inner); + scr_promise_release(outer); +} + +/* Node's once is an async function returning the executor's promise: its + * result adopts that promise through a thenable job queued now, whose + * reaction settles the result one turn after the inner promise settles. + * Moves `inner` in; the result is +1. */ +static ScrPromise *scr_eos_adopt(ScrPromise *inner) { + ScrPromise *outer = scr_promise_new(); + ScrClosure *job = scr_closure_new((void *)&scr_eos_adopt_job, 2); + job->caps[0] = scr_eos_promise_box(outer); + job->caps[1] = scr_eos_promise_box(inner); + scr_promise_release(inner); + scr_queue_microtask(job); + return outer; +} + +/* boxer and err_boxer are borrowed (err_boxer is NULL when the awaited + * event is 'error'); signal is borrowed. The result is +1 and never throws: + * every failure rejects it, as Node's async function does. */ +ScrPromise *scr_events_once_signal(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, void *adapter, + ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, + ScrEeInvoke err_inv, ScrDyn *signal) { + ScrPromise *result = scr_promise_new(); + bool with_signal = signal->kind != SCR_DYN_UNDEF; + if (with_signal) { + bool aborted = false; + ScrEventTargetCore *core = + scr_et_signal_core_fn ? scr_et_signal_core_fn(signal, &aborted) : NULL; + if (!core) { + scr_dyn_prop_type_fail("options.signal", "an instance of AbortSignal", signal); + scr_promise_reject_pending(result); + return result; + } + if (aborted) { + scr_eos_throw_abort(signal); + scr_promise_reject_pending(result); + return result; + } + } + ScrEosState *st = calloc(1, sizeof *st); + if (!st) scr_eos_oom(); + st->rc = 1; + st->promise = result; + st->em = em; + st->name = scr_str_retain(name); + st->signal = scr_dyn_retain(signal); + ScrClosure *event_cb = scr_eos_closure(adapter, boxer, st, SCR_EOS_EVENT); + ScrClosure *error_cb = + err_boxer != NULL ? scr_eos_closure(err_adapter, err_boxer, st, SCR_EOS_ERROR) : NULL; + st->event_cb = event_cb; + st->error_cb = error_cb; + /* Node registers inside the promise's executor: a throwing 'newListener' + * listener rejects the promise, and the listener it announced is never + * added (the event's listener stays when only the 'error' announcement + * throws). No abort listener follows a throw. */ + ScrCaught *thrown = NULL; + scr_emitter_release(scr_emitter_on(em, name, event_cb, inv, true, false)); + if (scr_exc_pending()) { + thrown = scr_exc_take(); + scr_emitter_unadd_quiet(em, name, event_cb); + if (error_cb != NULL) scr_closure_release(error_cb); /* never registered */ + } else if (error_cb != NULL) { + ScrStr *error = scr_str_new("error", 5); + scr_emitter_release(scr_emitter_on(em, error, error_cb, err_inv, true, false)); + if (scr_exc_pending()) { + thrown = scr_exc_take(); + scr_emitter_unadd_quiet(em, error, error_cb); + } + scr_str_release(error); + } + if (thrown != NULL) { + st->settled = true; + scr_rethrow(thrown); + scr_caught_release(thrown); + scr_promise_reject_pending(st->promise); + } else if (with_signal) { + ScrClosure *clo = scr_closure_new((void *)&scr_eos_abort_thunk, 2); + clo->caps[0] = scr_eos_link_box(st, SCR_EOS_ABORT); + clo->caps[1] = scr_box_new_obj(&scr_emitter_retain_v, &scr_emitter_release_v, NULL); + scr_box_set_ref(clo->caps[1], scr_emitter_retain(em)); + ScrDyn *listener = scr_dyn_new_func(clo, &scr_eos_abort_thunk, 0, "()", "abortListener"); + st->listener = listener; + ScrDyn *dispose = scr_events_add_abort_listener(signal, listener); + if (dispose) scr_dyn_release(dispose); + /* The signal's listener entry keeps it; the state's pointer is weak. */ + scr_dyn_release(listener); + } + ScrPromise *inner = scr_promise_retain(st->promise); + scr_eos_state_release(st); + return scr_eos_adopt(inner); +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 56f9fd54b..7c501f20c 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -2254,6 +2254,8 @@ void scr_stream_set_flush(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamPlain void scr_stream_set_flush_on_prefinish(ScrStream *s) { s->st->flush_on_prefinish = true; } +void scr_stream_set_destroyed(ScrStream *s, bool destroyed) { s->st->destroyed = destroyed; } + void scr_stream_set_native(ScrStream *s, void *state /*moves*/, void (*release)(void *)) { ScrStreamState *st = s->st; if (st->native) st->native_release(st->native); diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index 69da3dc0e..78290d8c4 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -136,6 +136,13 @@ ScrStream *scr_stream_pipe_general(ScrStream *src, ScrStream *dst, bool end); /* A boxed native stream out of a checked-dynamic pipeline stage (+1), or * NULL with `message` thrown as the not-supported Error. */ ScrStream *scr_stream_unbox_dyn(const struct ScrDyn *value, const ScrStr *message); +/* Checked-dynamic stream slots (scr_stream_dyn.c): the native stream a + * value boxes with the requested side (1 readable, 2 writable; +1, or + * NULL), and readable.pipe()/unpipe() for a destination that is not a + * native stream. */ +ScrStream *scr_stream_dyn_native(const struct ScrDyn *value, double side); +void scr_stream_pipe_dyn(ScrStream *src, struct ScrDyn *src_box, struct ScrDyn *dest, bool end); +void scr_stream_unpipe_dyn(ScrStream *src, struct ScrDyn *dest); ScrStream *scr_stream_new_duplex_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_transform_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_passthrough_dyn(struct ScrDyn *opts); @@ -442,6 +449,9 @@ void scr_stream_set_flush(ScrStream *s, ScrClosure *cb, ScrStreamPlainInv inv); * released when the stream is freed; scr_stream_native answers it * (borrowed) when it was set with `release`, else NULL. */ void scr_stream_set_flush_on_prefinish(ScrStream *s); +/* `stream.destroyed = value` (scr_dyn_builtin_stream.c): Node's setter + * stores the states' flag without destroying the stream. */ +void scr_stream_set_destroyed(ScrStream *s, bool destroyed); void scr_stream_set_native(ScrStream *s, void *state, void (*release)(void *)); void *scr_stream_native(ScrStream *s, void (*release)(void *)); diff --git a/packages/runtime/src/scr_wasm.c b/packages/runtime/src/scr_wasm.c index 8f9244c28..92d556272 100644 --- a/packages/runtime/src/scr_wasm.c +++ b/packages/runtime/src/scr_wasm.c @@ -90,25 +90,6 @@ #define SCR_WASM_ASAN 1 #endif -ScrPromise *scr_wasm_compile(ScrDyn *bytes); -bool scr_wasm_validate(ScrDyn *bytes); -ScrPromise *scr_wasm_instantiate(ScrDyn *source, ScrDyn *imports); -ScrDyn *scr_wasm_module_new(ScrDyn *bytes); -ScrDyn *scr_wasm_instance_new(ScrDyn *module, ScrDyn *imports); -bool scr_wasm_is(const ScrDyn *value, double kind); -ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options); -ScrDyn *scr_wasm_function(double kind); -ScrDyn *scr_wasm_namespace(void); -ScrDyn *scr_wasm_global_view(void); -ScrDyn *scr_wasm_memory_construct(ScrDyn *descriptor); -ScrDyn *scr_wasm_table_construct(ScrDyn *descriptor, ScrDyn *value); -ScrDyn *scr_wasm_global_construct(ScrDyn *descriptor, ScrDyn *value); -ScrDyn *scr_wasm_construct_without_new(double kind, ScrDyn *arg0, ScrDyn *arg1); -ScrDyn *scr_wasm_module_imports(ScrDyn *module); -ScrDyn *scr_wasm_module_exports(ScrDyn *module); -ScrDyn *scr_wasm_module_custom_sections(ScrDyn *module, ScrDyn *name); -ScrDyn *scr_wasm_value_of(ScrDyn *value); - /* Entry points of translated code. */ /* Sanitized builds do not declare it noreturn: AddressSanitizer would * call __asan_handle_no_return before every call, which warns on the diff --git a/packages/runtime/src/scr_wasm.h b/packages/runtime/src/scr_wasm.h index 7b0a29689..f7a791a26 100644 --- a/packages/runtime/src/scr_wasm.h +++ b/packages/runtime/src/scr_wasm.h @@ -19,5 +19,13 @@ ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options); ScrDyn *scr_wasm_function(double kind); ScrDyn *scr_wasm_namespace(void); ScrDyn *scr_wasm_global_view(void); +ScrDyn *scr_wasm_memory_construct(ScrDyn *descriptor); +ScrDyn *scr_wasm_table_construct(ScrDyn *descriptor, ScrDyn *value); +ScrDyn *scr_wasm_global_construct(ScrDyn *descriptor, ScrDyn *value); +ScrDyn *scr_wasm_construct_without_new(double kind, ScrDyn *arg0, ScrDyn *arg1); +ScrDyn *scr_wasm_module_imports(ScrDyn *module); +ScrDyn *scr_wasm_module_exports(ScrDyn *module); +ScrDyn *scr_wasm_module_custom_sections(ScrDyn *module, ScrDyn *name); +ScrDyn *scr_wasm_value_of(ScrDyn *value); #endif /* SCR_WASM_H */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbe529577..9c42ad00c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,8 +107,8 @@ importers: specifier: workspace:* version: link:../runtime tsc-ts: - specifier: 0.0.3 - version: 0.0.3 + specifier: 0.0.5 + version: 0.0.5 typescript: specifier: 7.0.2 version: 7.0.2 @@ -1357,6 +1357,10 @@ packages: cpu: [x64] os: [win32] + '@scriptc/threads@0.3.6': + resolution: {integrity: sha512-XZb4JhXM/ivhx4MMLxqLLF2mCI28nUK+VVKlCnELO83tacUuGlq7YCe/8LwvzO7fZwy/rCH4+genAYYEc6BF6Q==} + engines: {node: '>=24.0.0'} + '@sinclair/typebox@0.25.24': resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} @@ -1370,42 +1374,37 @@ packages: '@ts-morph/common@0.11.1': resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} - '@tsc-ts/darwin-arm64@0.0.3': - resolution: {integrity: sha512-NSP4IhLx658tY21ub3ruraZwUwAC3GCbIf1eM0eOKUHfOBBXqq7LU/Loj3zvlvn/tJUyv2VdIDmzrWI/mxp5sA==} - cpu: [arm64] - os: [darwin] - - '@tsc-ts/darwin-x64@0.0.3': - resolution: {integrity: sha512-+BNvqyygkMx0/oHkjK8AmsPrE2+OcE/hGzT0wa2dndiH8lCSDBs+77C7Kmy/FSTWi9ItJShp1+AuWKgQPEOztg==} + '@tsc-ts/darwin-x64@0.0.5': + resolution: {integrity: sha512-CEeHOFfpx5H9V3WSq+V7XGU8tfc06nRN818auiRVwuwPHvpti9gHq0HYkvf/43PH1pIGcgf68sQYdIxJhrQDKQ==} cpu: [x64] os: [darwin] - '@tsc-ts/linux-arm64-gnu@0.0.3': - resolution: {integrity: sha512-I3v0J0L387TEkau0BTGiVx2GdstYfOrwVYwb0pJ/LbFURhrcGcsUODP5bdSOIYCtsp3aAX6e2afdqSIlpsxtbw==} + '@tsc-ts/linux-arm64-gnu@0.0.5': + resolution: {integrity: sha512-qwybOqGDEa35eV9x/3WO81ExAUbUMY8288ATC83kUfYGmOAVStaO+JM2IdrsiI8hLWVC7/f0RFgZ+7dyWh9/nA==} cpu: [arm64] os: [linux] libc: [glibc] - '@tsc-ts/linux-arm64-musl@0.0.3': - resolution: {integrity: sha512-f7wD9D6fC1xL7+ifvMAbJ0TqxOuZP04R2/7c8lb7Gor5SFKe85udJoxf0gocK7mbzWzmEiOJ53J1PujJLBVIxA==} + '@tsc-ts/linux-arm64-musl@0.0.5': + resolution: {integrity: sha512-6oSw3NbQIrnxxCRuEe190MbMFoOrM+P+u1usascQMgSjsMcVRnJ/ztvTnRrCDtfPEHJ5EC8drgWIpcdrhGsB0w==} cpu: [arm64] os: [linux] libc: [musl] - '@tsc-ts/linux-x64-gnu@0.0.3': - resolution: {integrity: sha512-BL9zownW4tHv/qqQkF8OlurHNDXi7xtGtJ6PV4qMeRYPMKsivTIGFRoZLW023KcLOr1Zptaof6X3zpv1nJEIpA==} + '@tsc-ts/linux-x64-gnu@0.0.5': + resolution: {integrity: sha512-KrlBLUcG7M6p7lN+ke+kHVLqy7IiY/H0FxFZzq5slXanhrps/BfjdkUahsyREg7KVprr08xFuXqJTBtLEI4eow==} cpu: [x64] os: [linux] libc: [glibc] - '@tsc-ts/linux-x64-musl@0.0.3': - resolution: {integrity: sha512-g4/lE5sULNGUVLM0Px/jrsjLsQQAYSiL+Rl+JZ+l5JE2k1dXroCejT2+IRB/NucVcz6N8EOUiCRZ13zl+Wtpcg==} + '@tsc-ts/linux-x64-musl@0.0.5': + resolution: {integrity: sha512-fr8yGzkocCcTYoE+NwC+/WGPNteQ2GmOPJTy/z9NcsLchnpSIZ1fzFll15UYPKQlwQdNRJXxKK3npVBtLRdHTg==} cpu: [x64] os: [linux] libc: [musl] - '@tsc-ts/win32-x64-msvc@0.0.3': - resolution: {integrity: sha512-oGtboSHgpuQP1dHAHqWyGjAWCpvHDpciYCi/2ywcp6MT+oOHxC99X48H/PZr29fzYVxCVeaEqWZ/VFU9qLZaKg==} + '@tsc-ts/win32-x64-msvc@0.0.5': + resolution: {integrity: sha512-SQwufJafzsiOBYncHOxYkyELWjLlBijcKB+xOs6hr6Lxxp5MMLoPtvDFNG88c7G5xv2TDb1XbvyxnjQltr6SHw==} cpu: [x64] os: [win32] @@ -2566,8 +2565,8 @@ packages: ts-toolbelt@6.15.5: resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} - tsc-ts@0.0.3: - resolution: {integrity: sha512-Qr0i/BxP3ULIPDfr38vRb02x7sj6Q0r70Gubgu4ty2J4CEDCUKzT8RT2KMNqLXI3ZAhl7cncJq7a4LjKYZ1+0g==} + tsc-ts@0.0.5: + resolution: {integrity: sha512-Eea+zNv7y696pqNZtXBd0AX7/6PXIhR7aaEb0EhgpHadbk7VEiURD94/NngeLpYWts2tPjetkpwFLJY0+6yOnQ==} engines: {node: '>=20'} hasBin: true @@ -3428,6 +3427,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true + '@scriptc/threads@0.3.6': {} + '@sinclair/typebox@0.25.24': {} '@standard-schema/spec@1.1.0': {} @@ -3441,25 +3442,22 @@ snapshots: mkdirp: 1.0.4 path-browserify: 1.0.1 - '@tsc-ts/darwin-arm64@0.0.3': + '@tsc-ts/darwin-x64@0.0.5': optional: true - '@tsc-ts/darwin-x64@0.0.3': + '@tsc-ts/linux-arm64-gnu@0.0.5': optional: true - '@tsc-ts/linux-arm64-gnu@0.0.3': + '@tsc-ts/linux-arm64-musl@0.0.5': optional: true - '@tsc-ts/linux-arm64-musl@0.0.3': + '@tsc-ts/linux-x64-gnu@0.0.5': optional: true - '@tsc-ts/linux-x64-gnu@0.0.3': + '@tsc-ts/linux-x64-musl@0.0.5': optional: true - '@tsc-ts/linux-x64-musl@0.0.3': - optional: true - - '@tsc-ts/win32-x64-msvc@0.0.3': + '@tsc-ts/win32-x64-msvc@0.0.5': optional: true '@tybys/wasm-util@0.10.3': @@ -4798,15 +4796,16 @@ snapshots: ts-toolbelt@6.15.5: {} - tsc-ts@0.0.3: + tsc-ts@0.0.5: + dependencies: + '@scriptc/threads': 0.3.6 optionalDependencies: - '@tsc-ts/darwin-arm64': 0.0.3 - '@tsc-ts/darwin-x64': 0.0.3 - '@tsc-ts/linux-arm64-gnu': 0.0.3 - '@tsc-ts/linux-arm64-musl': 0.0.3 - '@tsc-ts/linux-x64-gnu': 0.0.3 - '@tsc-ts/linux-x64-musl': 0.0.3 - '@tsc-ts/win32-x64-msvc': 0.0.3 + '@tsc-ts/darwin-x64': 0.0.5 + '@tsc-ts/linux-arm64-gnu': 0.0.5 + '@tsc-ts/linux-arm64-musl': 0.0.5 + '@tsc-ts/linux-x64-gnu': 0.0.5 + '@tsc-ts/linux-x64-musl': 0.0.5 + '@tsc-ts/win32-x64-msvc': 0.0.5 tslib@2.8.1: optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d4d370721..a3e992b0a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,14 +7,14 @@ minimumReleaseAgeExclude: - "effect@4.0.0" - "@effect/platform-node@4.0.0" - "@effect/platform-node-shared@4.0.0" - - "@tsc-ts/darwin-arm64@0.0.3" - - "@tsc-ts/darwin-x64@0.0.3" - - "@tsc-ts/linux-arm64-gnu@0.0.3" - - "@tsc-ts/linux-arm64-musl@0.0.3" - - "@tsc-ts/linux-x64-gnu@0.0.3" - - "@tsc-ts/linux-x64-musl@0.0.3" - - "@tsc-ts/win32-x64-msvc@0.0.3" - - "tsc-ts@0.0.3" + - "@tsc-ts/darwin-arm64@0.0.5" + - "@tsc-ts/darwin-x64@0.0.5" + - "@tsc-ts/linux-arm64-gnu@0.0.5" + - "@tsc-ts/linux-arm64-musl@0.0.5" + - "@tsc-ts/linux-x64-gnu@0.0.5" + - "@tsc-ts/linux-x64-musl@0.0.5" + - "@tsc-ts/win32-x64-msvc@0.0.5" + - "tsc-ts@0.0.5" allowBuilds: esbuild: true overrides: diff --git a/scripts/ci-lanes.mjs b/scripts/ci-lanes.mjs index 9edc48a1c..b020f9de0 100644 --- a/scripts/ci-lanes.mjs +++ b/scripts/ci-lanes.mjs @@ -7,18 +7,20 @@ // own exemptions; a change to this script runs every lane until it is on the base branch. It uses only Node and git. // // Lanes (each an output, true or false): -// - linux: the Linux build and test shards, the TypeScript 7 parity sweep and the smaller Linux, Windows and mobile +// - linux: the Linux build and test shards, the TypeScript 7 parity sweep and the smaller Linux, Windows and Android // jobs. Off only when every change is documentation. -// - macos: the macOS native build, the darwin test shards and the macOS LLVM artifact jobs. On when the change touches -// a darwin package, a path naming darwin, macOS, iOS or Mach-O, the darwin test files of scripts/ci-test-plan.mjs, -// the LLVM helper (native/, packages/llvm-*) or the shared runtime pack code, or changed lines in packages/{runtime,compiler,cli} that mention darwin-specific APIs (__APPLE__, kqueue, Mach-O -// and similar). +// - macos: the macOS native build, the darwin test shards, the macOS LLVM artifact jobs and the iOS runtime packs. On +// when the change touches a darwin package, a path naming darwin, macOS, iOS, Mach-O or kqueue, the runtime's +// platform header, the darwin suite's test files (scripts/ci-test-plan.mjs), the LLVM helper (native/, +// packages/llvm-*) or the shared runtime pack code, or changed lines in packages/{runtime,compiler,cli} that mention +// darwin-specific APIs (__APPLE__, kqueue, Mach-O and similar). // - effects: the Effect suites. On when the change touches their harness or fixtures. // - bootstrap: the native bootstrap seed and self-rebuild contracts. On when the change touches the bootstrap scripts, // the native CLI build, the cli-* packages or the self-hosting tests. // Every lane is on when the change touches CI configuration, the workspace manifests or lockfile, vitest -// configuration, the shared test harness modules or this script, or when the diff cannot be computed. Pushes to main -// and the nightly sweep do not use this script: they run every lane. +// configuration, the shared test harness modules or this script, or when the diff cannot be computed. Pushes to main, +// the nightly sweep and manual runs do not use this script: pushes to main run every lane except macos, and the others +// run every lane. import { execFileSync } from "node:child_process"; import { appendFileSync } from "node:fs"; @@ -38,9 +40,11 @@ const everything = [ /^packages\/[^/]+\/package\.json$/, ]; const documentation = [/^docs\//, /\.mdx?$/, /^LICENSE$/, /^CHANGELOG\.md$/]; -const darwinPath = /darwin|macos|ios|apple|mach-?o/i; +const darwinPath = /darwin|macos|ios|apple|mach-?o|kqueue/i; // Native code generation and runtime packs every platform shares. const sharedNative = /^native\/|^packages\/(llvm-[^/]+|runtime-pack-common)\//; +// The darwin suite's files (darwinCorpusFiles and darwinFiles in scripts/ci-test-plan.mjs), the LLVM differential +// the macOS artifact jobs run, and the runtime's platform header. const darwinFiles = [ "packages/cli/test/bootstrap.test.ts", "packages/cli/test/native-link-info.test.ts", @@ -49,7 +53,10 @@ const darwinFiles = [ "packages/compiler/test/debug-variables.test.ts", "packages/compiler/test/native-codegen-integration.test.ts", "packages/compiler/test/source-debugging.test.ts", + "packages/runtime-darwin-arm64/test/archive.test.ts", + "packages/runtime/src/scr_platform.h", "packages/runtime/test/string.test.ts", + "tests/harness/differential.test.ts", "tests/harness/ffi-callback-pool.test.ts", "tests/harness/ffi-catalog.test.ts", "tests/harness/ffi-frameworks.test.ts", diff --git a/scripts/ci-test-plan.mjs b/scripts/ci-test-plan.mjs index 55eb65e72..2b76b52d6 100644 --- a/scripts/ci-test-plan.mjs +++ b/scripts/ci-test-plan.mjs @@ -69,13 +69,14 @@ export function sideOwners(flavor, count) { return owners; } -// Pull requests and pushes to main run the full suite on Linux. Their macOS -// lane runs the darwin suite, which keeps the darwin clang/libc/kqueue -// oracles: the Node differential corpus, plus every file whose assertions run -// only on darwin or take darwin-specific branches (including the Mach-O -// library localization arm and the arm64 runtime string oracle). -// The nightly schedule and manual runs with macos_full run the full suite on -// macOS. +// Pull requests and pushes to main run the full suite on Linux. Pull requests +// that change darwin code (scripts/ci-lanes.mjs) also run the darwin suite on +// macOS, which keeps the darwin clang/libc/kqueue oracles: the Node +// differential corpus, plus every file whose assertions run only on darwin or +// take darwin-specific branches (including the Mach-O library localization arm +// and the arm64 runtime string oracle). Pushes to main run no macOS jobs. The +// nightly schedule and manual runs run the full suite on macOS, or the darwin +// suite with macos_suite set to darwin. export const darwinCorpusFiles = ["tests/harness/differential.test.ts"]; export const darwinFiles = [ "packages/cli/test/bootstrap.test.ts", diff --git a/scripts/generate-ts7-ast-schema.mjs b/scripts/generate-ts7-ast-schema.mjs index 45a01c213..30c506869 100644 --- a/scripts/generate-ts7-ast-schema.mjs +++ b/scripts/generate-ts7-ast-schema.mjs @@ -268,9 +268,10 @@ if (process.argv.includes("--check")) { // declarations, and require every wire property to have a declared getter. const declarations = readFileSync(join(packageRoot, "dist/api/node/node.generated.d.ts"), "utf8"); const names = [...new Set(Object.values(protocol.childProperties).flat())].sort(); -// These views adapt TS7's shared postfix slot and reparsed JSDoc bodies. -// Their implementations live outside the generated region in AstNode. -const adaptedGetters = new Set(["body", "questionToken"]); +// These views adapt TS7's shared postfix slot and reparsed JSDoc bodies, and +// return modifiers as a modifier list. Their implementations live outside the +// generated region in AstNode. +const adaptedGetters = new Set(["body", "modifiers", "questionToken"]); const getters = names .filter((name) => !adaptedGetters.has(name)) .map((name) => { diff --git a/scripts/generate-ts7-ast-views.mjs b/scripts/generate-ts7-ast-views.mjs index 1525f6246..91e7ac2f7 100644 --- a/scripts/generate-ts7-ast-views.mjs +++ b/scripts/generate-ts7-ast-views.mjs @@ -65,6 +65,8 @@ export function generateAstViews(root, packageRoot, version, check) { if (key === "jsdocPropertyTags") type = "JSDocPropertyTag"; // A list's children are a node list, as every other list field. if (name === "SyntaxList" && key === "children") type = "NodeArray"; + // A declaration's modifiers are a modifier list, with their flags. + if (key === "modifiers") type = type.replace("NodeArray<", "ModifierArray<"); if (member.questionToken) type += " | undefined"; fields.set(key, ` readonly ${key}: ${type};`); } @@ -74,7 +76,7 @@ export function generateAstViews(root, packageRoot, version, check) { ...header, "", 'import type { AstNode } from "./ast-node.js";', - 'import type { JsxTagNamePropertyAccess, NodeArray } from "./ast-types.js";', + 'import type { JsxTagNamePropertyAccess, ModifierArray, NodeArray } from "./ast-types.js";', 'import { SyntaxKind, NodeFlags, ModifierFlags, TokenFlags } from "./enums.js";', "type Node = AstNode;", ]; @@ -99,6 +101,9 @@ export function generateAstViews(root, packageRoot, version, check) { ? `<${node.typeParameters.map((param) => param.getText(generated)).join(", ")}>` : ""; const fields = members(node.name.text); + // A default clause has no expression; the declaration shares the case clause's. + if (node.name.text === "DefaultClause" && fields.has("expression")) + fields.set("expression", " readonly expression: undefined;"); if (!fields.has("kind")) { const kinds = new Set(); for (const [name, child] of interfaces) { @@ -131,6 +136,7 @@ export function generateAstViews(root, packageRoot, version, check) { ...header, "", 'import { SyntaxKind, NodeFlags, ScriptKind, OuterExpressionKinds } from "./enums.js";', + 'import { getSourceFileOfNode } from "./node-functions.js";', ]; for (const stem of ["is.generated", "is"]) { const declarations = parse(`${stem}.d.ts`); @@ -173,10 +179,9 @@ export function generateAstViews(root, packageRoot, version, check) { } let body = fn.body.getText(source); if (name === "isJSDocTypeAssertion") - body = body.replace( - "expression.kind !==", - "expression === undefined || expression.kind !==", - ); + body = body + .replace("expression.kind !==", "expression === undefined || expression.kind !==") + .replace("node.getSourceFile()", "getSourceFileOfNode(node)"); if (head.includes("node: Node") && name.startsWith("is")) { head = head.replace("node: Node", "node: Node | undefined"); body = body.replace("{", "{\n if (node === undefined) return false;"); diff --git a/tests/corpus/6470-any-destructuring.ts b/tests/corpus/6470-any-destructuring.ts new file mode 100644 index 000000000..09d7f45c4 --- /dev/null +++ b/tests/corpus/6470-any-destructuring.ts @@ -0,0 +1,124 @@ +// Destructuring `any` values in TypeScript: a function typed to return +// `any` (an untyped command runner), an awaited `any` promise, JSON.parse, +// and an `any` table read by key all destructure through the +// checked-dynamic value, with renames, defaults, nesting, rest, holes and +// Node's TypeError for a null source, including empty patterns, awaited +// sources, element reads, `this` receivers and string, numeric and +// computed first keys. +function run(command: string): any { + return { stdout: ` ${command} output \n`, stderr: "", exitCode: 0, meta: { pid: 42 } }; +} + +const { stdout, stderr } = run("echo"); +console.log(JSON.stringify(stdout.toString().trim()), stderr.length); + +const { exitCode: code, missing, meta: { pid }, ...rest } = run("ls"); +console.log(code, missing, pid, Object.keys(rest).join(",")); + +const { signal = "none", exitCode = 9 } = run("true"); +console.log(signal, exitCode); + +const [first, , third = "default", ...others] = JSON.parse('["a", "b", null, "d", "e"]'); +console.log(first, third, others.length); + +const { list: [head, ...tail] } = JSON.parse('{"list":[1,2,3]}'); +console.log(head, tail); + +async function fetchAny(): Promise { + return { body: "payload", status: 200 }; +} + +async function main(): Promise { + const { body, status } = await fetchAny(); + console.log(body, status); + const pending = fetchAny(); + const { status: again } = await pending; + console.log(again + 1); +} + +const table: any = { a: { mode: 0o755, path: "/bin" }, b: { mode: 0o644, path: "/etc" } }; +for (const key in table) { + const { mode, path } = table[key]; + console.log(key, mode.toString(8), path); +} + +function lookup(found: boolean): any { + return found ? { anything: 1 } : null; +} +for (const found of [true, false]) { + try { + const { anything } = lookup(found); + console.log(anything); + } catch (e) { + console.log(e instanceof TypeError, (e as Error).message); + } +} + +async function nothing(): Promise { + return null; +} + +function nothingSync(): any { + return undefined; +} + +class Holder { + value(): any { + return null; + } + read(): void { + const { a } = this.value(); + console.log(a); + } +} + +const sparse: any = { k: null, list: [null] }; +const key = "k"; + +async function nullSources(): Promise { + const attempts: Array<() => Promise> = [ + async () => { + const {} = await nothing(); + }, + async () => { + const { a } = await nothing(); + console.log(a); + }, + async () => { + const {} = nothingSync(); + }, + async () => { + const {} = (await nothing()) as any; + }, + async () => { + const { "a-b": x } = nothingSync()!; + console.log(x); + }, + async () => { + const { 1.5: x } = sparse[key]; + console.log(x); + }, + async () => { + const { [key]: x } = sparse.list[0]; + console.log(x); + }, + async () => { + new Holder().read(); + }, + async () => { + let a: any; + ({ a } = await nothing()); + console.log(a); + }, + ]; + for (const attempt of attempts) { + try { + await attempt(); + console.log("no error"); + } catch (e) { + console.log(e instanceof TypeError, (e as Error).message); + } + } +} + +main().then(nullSources); diff --git a/tests/corpus/6471-any-method-chains.ts b/tests/corpus/6471-any-method-chains.ts new file mode 100644 index 000000000..3bfce5fba --- /dev/null +++ b/tests/corpus/6471-any-method-chains.ts @@ -0,0 +1,24 @@ +// Method chains over `any`-derived strings: each step that answers a fresh +// native value (split's string[]) keeps chaining through the +// checked-dynamic value — map, filter(Boolean), pop, join — and the result +// checks back into typed slots. +function exec(): any { + return { stdout: " 3f2a\n 9c1b \n\n77de\n", stderr: "warn: shallow\n" }; +} + +const { stdout, stderr } = exec(); +let ids: string[] = []; +ids = stdout + .split("\n") + .map((s: string) => s.trim()) + .filter(Boolean); +console.log(ids.length, ids.join(", ")); + +const last: string = stdout.trim().split("\n").pop() ?? ""; +console.log(JSON.stringify(last)); + +const firstWord: string = stderr.trim().split(":")[0]; +console.log(firstWord, stderr.trim().split(" ").length); + +const words: string[] = stdout.trim().split(/\s+/); +console.log(words.length, stdout.split("\n").length, stdout.trim().split("\n").join("|")); diff --git a/tests/corpus/6472-namespace-destructuring/lib.ts b/tests/corpus/6472-namespace-destructuring/lib.ts new file mode 100644 index 000000000..d25d53d74 --- /dev/null +++ b/tests/corpus/6472-namespace-destructuring/lib.ts @@ -0,0 +1,21 @@ +export function add(a: number, b: number): number { + return a + b; +} + +export const label = "lib"; + +export let counter = 1; + +export function bump(): void { + counter++; +} + +export class Shape { + side: number; + constructor(side: number) { + this.side = side; + } + area(): number { + return this.side * this.side; + } +} diff --git a/tests/corpus/6472-namespace-destructuring/main.ts b/tests/corpus/6472-namespace-destructuring/main.ts new file mode 100644 index 000000000..7aa00a2d1 --- /dev/null +++ b/tests/corpus/6472-namespace-destructuring/main.ts @@ -0,0 +1,29 @@ +// Destructuring a program module's namespace, statically +// (`import * as ns`) and from a dynamic import: each element reads the +// export at the pattern's position — renames and defaults included — and +// binds a copy of its value (a later reassignment of an exported `let` is +// visible through the namespace, not through the binding). An annotation +// naming another module or an object type does not change which module's +// exports are read. +import * as ns from "./lib.ts"; +import type * as shape from "./shape.ts"; + +const { add, label, counter, bump, Shape: Ctor } = ns; +console.log(add(2, 3), label, counter); +bump(); +console.log(counter, ns.counter); +console.log(new Ctor(4).area()); + +async function main(): Promise { + const { add: plus, label: name = "unused" } = await import("./lib.ts"); + console.log(plus(10, 20), name); + const { add: annotatedAdd, label: annotated }: typeof shape = await import("./lib.ts"); + console.log(annotatedAdd(10, 20), annotated); +} + +const { add: shapeAdd, label: shapeLabel }: typeof shape = ns; +console.log(shapeAdd(2, 3), shapeLabel); +const { label: viaObject }: { label: string } = ns; +console.log(viaObject); + +main(); diff --git a/tests/corpus/6472-namespace-destructuring/shape.ts b/tests/corpus/6472-namespace-destructuring/shape.ts new file mode 100644 index 000000000..96adaffd3 --- /dev/null +++ b/tests/corpus/6472-namespace-destructuring/shape.ts @@ -0,0 +1,7 @@ +// A module whose exports have the same names and types as some of +// lib.ts's, so `typeof import("./shape.ts")` accepts lib.ts's namespace. +export const label: string = "shape"; + +export function add(a: number, b: number): number { + return a * b; +} diff --git a/tests/corpus/6473-any-parameter-values.ts b/tests/corpus/6473-any-parameter-values.ts new file mode 100644 index 000000000..399332551 --- /dev/null +++ b/tests/corpus/6473-any-parameter-values.ts @@ -0,0 +1,23 @@ +// Function values whose defaulted parameter the checker types `any`: under +// an `(...args: any[]) => any` wrapper slot the contextual `any` wins over +// the default's type. The callable's slot is the checked-dynamic value the +// body already reads. +function traced any>(name: string, fn: F): F { + console.log("wrap", name); + return fn; +} + +function configured(raw = process.env["SCRIPTC_TEST_UNSET_WIDTH"]): number | undefined { + const width = Number.parseInt(raw ?? "", 10); + return Number.isSafeInteger(width) && width > 0 ? width : undefined; +} + +const layout = traced("layout", (width = configured() ?? 80) => { + return { width, gutter: 2 }; +}); +console.log(JSON.stringify(layout()), JSON.stringify(layout(120))); + +const scaled = traced("scaled", function (factor = 2, base = 10) { + return base * factor; +}); +console.log(scaled(), scaled(3), scaled(undefined, 1)); diff --git a/tests/corpus/6474-async-any-callbacks/main.ts b/tests/corpus/6474-async-any-callbacks/main.ts new file mode 100644 index 000000000..c84ce37ba --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/main.ts @@ -0,0 +1,41 @@ +// Async callbacks whose result the checker types `Promise` handed to +// a JavaScript library typed `(...) => A | Promise` (A = any collapses +// the slot to `any`): the callback value still returns a promise, and its +// payload is the checked-dynamic value the body produced. +import retry from "retry-lite"; + +function loose(attempt: number): any { + if (attempt < 3) throw new Error(`attempt ${attempt} failed`); + return { text: `ok after ${attempt}`, size: attempt * 10 }; +} + +async function main(): Promise { + const got = await retry(async (_bail, attempt) => loose(attempt), { + retries: 5, + onRetry: (e: Error) => console.log("retry:", e.message), + }); + console.log(got.text, got.size); + + const parsed = await retry( + async () => { + const value = JSON.parse('{"items":[1,2,3]}'); + return value; + }, + { retries: 1 }, + ); + console.log(parsed.items.length); + + try { + await retry( + async (bail) => { + bail(new Error("gave up")); + throw new Error("ignored"); + }, + { retries: 2 }, + ); + } catch (e) { + console.log("caught:", (e as Error).message); + } +} + +main(); diff --git a/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts new file mode 100644 index 000000000..896f81f9d --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts @@ -0,0 +1,15 @@ +declare function retryLite( + fn: retryLite.RetryFunction, + opts: retryLite.Options +): Promise; + +declare namespace retryLite { + interface Options { + retries?: number; + onRetry?: (e: Error) => any; + } + + type RetryFunction = (bail: (e: Error) => void, attempt: number) => A | Promise; +} + +export = retryLite; diff --git a/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js new file mode 100644 index 000000000..a1ae718ab --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js @@ -0,0 +1,36 @@ +"use strict"; + +function retryLite(fn, opts) { + const retries = opts.retries === undefined ? 3 : opts.retries; + return new Promise((resolve, reject) => { + let attempt = 0; + let bailed = false; + const bail = (err) => { + bailed = true; + reject(err); + }; + const onError = (err) => { + if (bailed) return; + if (attempt > retries) { + reject(err); + return; + } + if (opts.onRetry) opts.onRetry(err); + runAttempt(); + }; + const runAttempt = () => { + attempt++; + let val; + try { + val = fn(bail, attempt); + } catch (err) { + onError(err); + return; + } + Promise.resolve(val).then(resolve).catch(onError); + }; + runAttempt(); + }); +} + +module.exports = retryLite; diff --git a/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json new file mode 100644 index 000000000..251988634 --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json @@ -0,0 +1,6 @@ +{ + "name": "retry-lite", + "version": "1.0.0", + "main": "./index.js", + "types": "./index.d.ts" +} diff --git a/tests/corpus/6474-async-any-callbacks/tsconfig.json b/tests/corpus/6474-async-any-callbacks/tsconfig.json new file mode 100644 index 000000000..9297bdb5b --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/tsconfig.json @@ -0,0 +1,3 @@ +{ + "compilerOptions": { "strict": true, "types": ["node"], "esModuleInterop": true } +} diff --git a/tests/corpus/6475-js-promise-resolve-adoption/main.ts b/tests/corpus/6475-js-promise-resolve-adoption/main.ts new file mode 100644 index 000000000..61a58dbe2 --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/main.ts @@ -0,0 +1,23 @@ +// JavaScript's Promise.resolve over a checked-dynamic value that holds a +// promise (a TypeScript async callback's result handed back into a +// JavaScript library) answers that same promise — adopted, not wrapped — +// and wraps plain values. +import { sameAfterResolve, viaExecutor, viaResolve, viaThen } from "calls"; + +async function main(): Promise { + console.log(await viaResolve(async (): Promise => "resolved")); + console.log(await viaThen(async (): Promise => 42)); + console.log(await viaExecutor(async (): Promise => "executor")); + console.log(await viaExecutor((): string => "plain")); + try { + await viaExecutor(async (): Promise => { + throw new Error("callback failed"); + }); + } catch (e) { + console.log("caught:", (e as Error).message); + } + const pending = Promise.resolve("p"); + console.log(sameAfterResolve(pending), sameAfterResolve("p")); +} + +main(); diff --git a/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts new file mode 100644 index 000000000..d1040a227 --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts @@ -0,0 +1,4 @@ +export declare function viaResolve(fn: () => A | Promise): Promise; +export declare function viaThen(fn: () => A | Promise): Promise; +export declare function viaExecutor(fn: () => A | Promise): Promise; +export declare function sameAfterResolve(value: unknown): boolean; diff --git a/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js new file mode 100644 index 000000000..fb55d867c --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js @@ -0,0 +1,21 @@ +"use strict"; + +function viaResolve(fn) { + return Promise.resolve(fn()); +} + +function viaThen(fn) { + return Promise.resolve(fn()).then((value) => value); +} + +function viaExecutor(fn) { + return new Promise((resolve, reject) => { + Promise.resolve(fn()).then(resolve).catch(reject); + }); +} + +function sameAfterResolve(value) { + return Promise.resolve(value) === value; +} + +module.exports = { viaResolve, viaThen, viaExecutor, sameAfterResolve }; diff --git a/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json new file mode 100644 index 000000000..c85cd1f4b --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json @@ -0,0 +1,6 @@ +{ + "name": "calls", + "version": "1.0.0", + "main": "./index.js", + "types": "./index.d.ts" +} diff --git a/tests/corpus/6490-events-once-signal.ts b/tests/corpus/6490-events-once-signal.ts new file mode 100644 index 000000000..8535e1fec --- /dev/null +++ b/tests/corpus/6490-events-once-signal.ts @@ -0,0 +1,120 @@ +// events.once(emitter, name, { signal }) follows Node's lib/events.js: an +// abort removes the event's and the 'error' listener and rejects with an +// AbortError whose cause is signal.reason, an aborted signal rejects +// without registering anything, and a settled once removes its abort +// listener. The result settles on Node's microtask turn: once is an async +// function returning the executor's promise. +import { EventEmitter, once } from "node:events"; + +async function main(): Promise { + const ee = new EventEmitter(); + const ac = new AbortController(); + setTimeout(() => ee.emit("ready", 1, "two"), 1); + const args = await once(ee, "ready", { signal: ac.signal }); + console.log("fulfilled", JSON.stringify(args), ee.listenerCount("ready"), ee.listenerCount("error")); + + const ac2 = new AbortController(); + const pending = once(ee, "ready", { signal: ac2.signal }); + console.log("before abort", ee.listenerCount("ready"), ee.listenerCount("error")); + ac2.abort(); + console.log("after abort", ee.listenerCount("ready"), ee.listenerCount("error")); + try { + await pending; + } catch (err) { + const e = err as NodeJS.ErrnoException; + console.log("rejected", e.name, e.message, e.code, (e.cause as Error).name); + } + + const ac3 = new AbortController(); + ac3.abort(new Error("already")); + try { + await once(ee, "ready", { signal: ac3.signal }); + } catch (err) { + const e = err as Error; + console.log("pre-aborted", e.name, (e.cause as Error).message, ee.listenerCount("ready")); + } + + const ac4 = new AbortController(); + setTimeout(() => ee.emit("error", new Error("boom")), 1); + try { + await once(ee, "ready", { signal: ac4.signal }); + } catch (err) { + console.log("error event", (err as Error).message, ee.listenerCount("ready")); + } + ac4.abort(); + console.log("late abort changes nothing"); + + const errs = once(ee, "error", { signal: ac.signal }); + ee.emit("error", new Error("as value")); + const [first] = await errs; + console.log("error as value", (first as Error).message); + + // An undefined signal is the plain form. + const maybe: AbortSignal | undefined = undefined; + setTimeout(() => ee.emit("ready", 3, "four"), 1); + console.log("undefined signal", JSON.stringify(await once(ee, "ready", { signal: maybe }))); + + // Listeners registered before and after the once's abort listener. + const ac5 = new AbortController(); + const ee5 = new EventEmitter(); + ac5.signal.addEventListener("abort", () => { + console.log("first abort listener", ee5.listenerCount("z")); + }); + const p5 = once(ee5, "z", { signal: ac5.signal }); + ac5.signal.addEventListener("abort", () => { + console.log("second abort listener", ee5.listenerCount("z")); + }); + ac5.abort("why"); + await p5.catch((err: Error) => console.log("p5", err.name, String(err.cause))); + + // The abort's removals emit 'removeListener'. + const ee6 = new EventEmitter(); + ee6.on("removeListener", (name: string) => console.log("removed", name)); + const ac6 = new AbortController(); + const p6 = once(ee6, "w", { signal: ac6.signal }); + ac6.abort(); + await p6.catch(() => console.log("p6 rejected")); + + // A 'newListener' listener that throws rejects the promise. + const ee7 = new EventEmitter(); + ee7.on("newListener", (name: string) => { + if (name === "go") throw new Error("no " + name); + }); + await once(ee7, "go", { signal: new AbortController().signal }).then( + () => console.log("fulfilled"), + (err: Error) => console.log("newListener rejected", err.message, ee7.listenerCount("go")), + ); + + // Settlement turns against plain promise chains. + const ee8 = new EventEmitter(); + const ac8 = new AbortController(); + void once(ee8, "x").then(() => console.log("plain")); + void once(ee8, "x", { signal: ac8.signal }).then(() => console.log("signal")); + void Promise.resolve() + .then(() => console.log("t1")) + .then(() => console.log("t2")) + .then(() => console.log("t3")) + .then(() => console.log("t4")); + ee8.emit("x"); + const ac9 = new AbortController(); + void once(ee8, "y", { signal: ac9.signal }).then( + () => console.log("fulfilled"), + () => console.log("aborted"), + ); + ac9.abort(); + const ac10 = new AbortController(); + ac10.abort(); + void once(ee8, "y", { signal: ac10.signal }).then( + () => console.log("fulfilled"), + () => console.log("pre-aborted turn"), + ); + await new Promise((resolve) => setTimeout(resolve, 5)); + + // Pending onces whose signal never aborts keep their listeners. + const idle = new AbortController(); + void once(ee, "never", { signal: idle.signal }); + void once(new EventEmitter(), "never", { signal: idle.signal }); + console.log("pending", ee.listenerCount("never"), ee.listenerCount("error")); +} + +void main(); diff --git a/tests/corpus/6490-js-named-promise-executor/main.ts b/tests/corpus/6490-js-named-promise-executor/main.ts new file mode 100644 index 000000000..79e2a17d7 --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/main.ts @@ -0,0 +1,22 @@ +// A JavaScript library that builds its promise from a named executor +// function (`function run(resolve, reject) {...}; return new Promise(run)`, +// the classic retry helper shape): the executor's parameters are untyped, +// so it receives the native resolve and reject as checked-dynamic function +// values and calls them like any JavaScript function. +import { attempt } from "later"; +async function main(): Promise { + const v = await attempt(async (_bail: (e: Error) => void, n: number): Promise => { + if (n < 3) throw new Error("try " + n); + return "done after " + n; + }, 5); + console.log(v); + try { + await attempt((bail: (e: Error) => void): string => { + bail(new Error("stop")); + return "ignored"; + }, 5); + } catch (e) { + console.log("caught", (e as Error).message); + } +} +main(); diff --git a/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts new file mode 100644 index 000000000..55956f01d --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts @@ -0,0 +1 @@ +export declare function attempt(fn: (bail: (e: Error) => void, n: number) => A | Promise, retries: number): Promise; diff --git a/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js new file mode 100644 index 000000000..deefa6aee --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js @@ -0,0 +1,31 @@ +"use strict"; +function attempt(fn, retries) { + function run(resolve, reject) { + let n = 0; + function bail(err) { + reject(err || new Error("Aborted")); + } + function onError(err) { + if (err.bail) { + bail(err); + return; + } + if (n > retries) reject(err); + else runAttempt(); + } + function runAttempt() { + n++; + let val; + try { + val = fn(bail, n); + } catch (err) { + onError(err); + return; + } + Promise.resolve(val).then(resolve).catch(onError); + } + runAttempt(); + } + return new Promise(run); +} +module.exports = { attempt }; diff --git a/tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json b/tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json new file mode 100644 index 000000000..ce1f29bac --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json @@ -0,0 +1,6 @@ +{ + "name": "later", + "version": "1.0.0", + "main": "./index.js", + "types": "./index.d.ts" +} diff --git a/tests/corpus/6490-js-named-promise-executor/tsconfig.json b/tests/corpus/6490-js-named-promise-executor/tsconfig.json new file mode 100644 index 000000000..27d492dd2 --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/tsconfig.json @@ -0,0 +1,3 @@ +{ + "compilerOptions": { "strict": true, "types": ["node"] } +} diff --git a/tests/corpus/6491-catch-any-bindings.ts b/tests/corpus/6491-catch-any-bindings.ts new file mode 100644 index 000000000..9979637b4 --- /dev/null +++ b/tests/corpus/6491-catch-any-bindings.ts @@ -0,0 +1,34 @@ +// A catch binding annotated `any` reads the caught value through its +// checked-dynamic face: member reads, expando writes, `delete` and `in` +// act on the thrown object itself, and a rethrow keeps its identity. +const thrown: Error[] = []; + +function fail(n: number): never { + const e = new Error(`failure ${n}`); + thrown.push(e); + throw e; +} + +for (const n of [1, 2]) { + try { + try { + fail(n); + } catch (error: any) { + error.bail = n === 1; + console.log("marked:", error.bail, "bail" in error, error.message); + if (error.bail) { + delete error.bail; + console.log("deleted:", error.bail, "bail" in error); + } + throw error; + } + } catch (outer) { + console.log("outer:", outer === thrown[n - 1], (outer as Error).message, "bail" in (outer as object)); + } +} + +try { + JSON.parse("{"); +} catch (error: any) { + console.log(error.name, typeof error.message, error instanceof SyntaxError); +} diff --git a/tests/corpus/6491-events-once-streams.ts b/tests/corpus/6491-events-once-streams.ts new file mode 100644 index 000000000..efe60c737 --- /dev/null +++ b/tests/corpus/6491-events-once-streams.ts @@ -0,0 +1,87 @@ +// events.once over streams: file streams, Readable.from, a PassThrough +// destroyed with an error, a Writable, and a NodeJS.ReadableStream slot +// raced against its destination's 'close' with an AbortSignal that the +// finally block aborts. +import { once } from "node:events"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Readable, Writable } from "node:stream"; + +function copyPart(src: NodeJS.ReadableStream, out: PassThrough): Promise { + const ac = new AbortController(); + src.pipe(out, { end: false }); + return Promise.race([ + once(src, "end", { signal: ac.signal }), + once(src, "error", { signal: ac.signal }).then(([error]) => { + throw error; + }), + once(out, "close", { signal: ac.signal }).then(() => { + throw new Error("output closed before the part ended"); + }), + ]).finally(() => { + ac.abort(); + src.unpipe(out); + console.log("part done", out.listenerCount("close"), out.listenerCount("error")); + }); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-once-streams-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "part one\n"); + + const rs = fs.createReadStream(file); + rs.resume(); + console.log("file end", JSON.stringify(await once(rs, "end"))); + const ws = fs.createWriteStream(path.join(dir, "b.txt")); + ws.end("x"); + await once(ws, "finish"); + console.log("write stream finish"); + await once(ws, "close"); + console.log("write stream close"); + + const pt = new PassThrough(); + setTimeout(() => pt.destroy(new Error("pt broke")), 1); + try { + await once(pt, "end"); + } catch (err) { + console.log("passthrough error", (err as Error).message, pt.listenerCount("end")); + } + const r = Readable.from(["a", "b"]); + r.resume(); + await once(r, "close"); + console.log("readable close"); + const w = new Writable({ + write(_chunk, _encoding, callback) { + callback(); + }, + }); + w.end("z"); + await once(w, "finish"); + console.log("writable finish"); + + const out = new PassThrough(); + let text = ""; + out.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + await copyPart(fs.createReadStream(file), out); + await copyPart(fs.createReadStream(file), out); + console.log(JSON.stringify(text)); + try { + await copyPart(fs.createReadStream(path.join(dir, "missing.txt")), out); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + const closing = new PassThrough(); + setTimeout(() => closing.destroy(), 1); + try { + await copyPart(new Readable({ read() {} }), closing); + } catch (err) { + console.log("closed", (err as Error).message); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6492-promise-race-combined.ts b/tests/corpus/6492-promise-race-combined.ts new file mode 100644 index 000000000..70f5399af --- /dev/null +++ b/tests/corpus/6492-promise-race-combined.ts @@ -0,0 +1,72 @@ +// Promise.race over events.once results: entries that only reject (a +// handler that throws) fit any result, and entries of different value +// types combine into 'unknown'. +import { EventEmitter, once } from "node:events"; + +function waitFor(a: EventEmitter, b: EventEmitter) { + let failed = false; + return Promise.race([ + once(a, "done"), + once(a, "error").then(([error]) => { + failed = true; + throw error; + }), + once(b, "close").then(() => { + failed = true; + throw new Error("closed early"); + }), + ]).finally(() => { + console.log("settled", failed); + }); +} + +function waitVoid(a: EventEmitter): Promise { + return Promise.race([ + once(a, "done").then((): void => undefined), + once(a, "error").then(([error]) => { + throw error; + }), + ]); +} + +function waitMixed(a: EventEmitter): Promise { + return Promise.race([once(a, "done").then((): void => undefined), once(a, "other")]); +} + +async function main(): Promise { + const a = new EventEmitter(); + const b = new EventEmitter(); + setTimeout(() => a.emit("done", 7), 1); + console.log("value", JSON.stringify(await waitFor(a, b))); + setTimeout(() => b.emit("close"), 1); + try { + await waitFor(a, b); + } catch (err) { + console.log("caught", (err as Error).message); + } + setTimeout(() => a.emit("error", new Error("bad")), 1); + try { + await waitFor(a, b); + } catch (err) { + console.log("caught", (err as Error).message); + } + setTimeout(() => a.emit("done", 8), 1); + await waitVoid(a); + console.log("void done"); + setTimeout(() => a.emit("other", 1, "x"), 1); + console.log("mixed", JSON.stringify(await waitMixed(a))); + setTimeout(() => a.emit("done", 9), 1); + console.log("mixed undefined", await waitMixed(a)); + // Turn order: the race settles one turn after its first entry. + const c = new EventEmitter(); + void waitMixed(c).then(() => console.log("race settled")); + void Promise.resolve() + .then(() => console.log("u1")) + .then(() => console.log("u2")) + .then(() => console.log("u3")) + .then(() => console.log("u4")) + .then(() => console.log("u5")); + c.emit("other", 2, "y"); +} + +void main(); diff --git a/tests/corpus/6493-stream-function-callbacks.ts b/tests/corpus/6493-stream-function-callbacks.ts new file mode 100644 index 000000000..7d5ee45e1 --- /dev/null +++ b/tests/corpus/6493-stream-function-callbacks.ts @@ -0,0 +1,66 @@ +// Stream subclasses that declare their completion callbacks with the broad +// `Function` type: the runtime's callable arrives and calls through it +// complete the chunk, pass data, or fail the stream. +import { Transform, Writable } from "node:stream"; + +class Prefixer extends Transform { + _s: string; + constructor() { + super(); + this._s = ""; + } + _transform(c: Buffer, _: string, cb: Function) { + const text = c.toString(); + const lines = text.split("\n").map((line, index, all) => { + if (index === 0) return this._s === "" || this._s.endsWith("\n") ? "> " + line : line; + if (index === all.length - 1) return line !== "" ? "> " + line : line; + return "> " + line; + }); + this._s += text; + this.push(lines.join("\n")); + cb(); + } +} + +class Upper extends Transform { + _transform(chunk: Buffer, _encoding: string, cb: Function) { + if (chunk.toString() === "bad") { + cb(new Error("bad chunk")); + return; + } + cb(null, chunk.toString().toUpperCase()); + } + _flush(cb: Function) { + this.push("!"); + cb(); + } +} + +class Sink extends Writable { + seen: string[] = []; + _write(chunk: Buffer, _encoding: string, cb: Function) { + this.seen.push(chunk.toString()); + cb(); + } + _final(cb: Function) { + this.seen.push(""); + cb(); + } +} + +const prefixer = new Prefixer(); +prefixer.pipe(process.stdout); +prefixer.write("a\nb"); +prefixer.write("c\n"); +prefixer.end("d\n"); + +const sink = new Sink(); +const upper = new Upper(); +upper.pipe(sink); +sink.on("finish", () => console.log("sink", JSON.stringify(sink.seen))); +upper.write("a"); +upper.end("b"); + +const bad = new Upper(); +bad.on("error", (err: Error) => console.log("error", err.message)); +bad.write("bad"); diff --git a/tests/corpus/6494-events-once-dyn-stream-slot/main.ts b/tests/corpus/6494-events-once-dyn-stream-slot/main.ts new file mode 100644 index 000000000..501944ae3 --- /dev/null +++ b/tests/corpus/6494-events-once-dyn-stream-slot/main.ts @@ -0,0 +1,59 @@ +// events.once over a checked-dynamic NodeJS.ReadableStream slot: the +// program also moves a JavaScript stream object into such a slot, so the +// slots hold native streams through their typed capsules. +import { once } from "node:events"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough } from "node:stream"; +import { part } from "./sources.js"; + +function collect(src: NodeJS.ReadableStream): Promise { + return new Promise((resolve) => { + let text = ""; + src.on("data", (chunk: Buffer | string) => { + text += chunk.toString(); + }); + src.on("end", () => resolve(text)); + }); +} + +function copyPart(src: NodeJS.ReadableStream, out: PassThrough) { + const ac = new AbortController(); + src.pipe(out, { end: false }); + return Promise.race([ + once(src, "end", { signal: ac.signal }), + once(src, "error", { signal: ac.signal }).then(([error]) => { + throw error; + }), + once(out, "close", { signal: ac.signal }).then(() => { + throw new Error("output closed before the part ended"); + }), + ]).finally(() => { + ac.abort(); + src.unpipe(out); + }); +} + +async function main(): Promise { + console.log("js part", await collect(part(["a", "b"]))); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-once-dyn-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "native part\n"); + const out = new PassThrough(); + let text = ""; + out.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + await copyPart(fs.createReadStream(file), out); + await copyPart(fs.createReadStream(file), out); + console.log(JSON.stringify(text)); + try { + await copyPart(fs.createReadStream(path.join(dir, "missing.txt")), out); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6494-events-once-dyn-stream-slot/sources.js b/tests/corpus/6494-events-once-dyn-stream-slot/sources.js new file mode 100644 index 000000000..e76e88785 --- /dev/null +++ b/tests/corpus/6494-events-once-dyn-stream-slot/sources.js @@ -0,0 +1,21 @@ +// A constructor-function stream with its own listener table. +function Part(chunks) { + this.chunks = chunks; + this.listeners = {}; + setImmediate(() => { + for (const chunk of this.chunks) this.emit("data", chunk); + this.emit("end"); + }); +} +Part.prototype.on = function (event, listener) { + (this.listeners[event] = this.listeners[event] || []).push(listener); + return this; +}; +Part.prototype.emit = function (event, value) { + for (const listener of this.listeners[event] || []) listener(value); + return true; +}; + +export function part(chunks) { + return new Part(chunks); +} diff --git a/tests/corpus/6560-constructor-function-readable.cjs b/tests/corpus/6560-constructor-function-readable.cjs new file mode 100644 index 000000000..5319aada1 --- /dev/null +++ b/tests/corpus/6560-constructor-function-readable.cjs @@ -0,0 +1,121 @@ +// Readable streams built by constructor functions, the from2 pattern: +// util.inherits(Ctor, Readable), Readable.call(this, opts) and _read on +// the prototype. +const { Readable } = require("stream"); +const util = require("util"); + +function Source(list, opts) { + if (!(this instanceof Source)) return new Source(list, opts); + this._list = list.slice(); + // Before Readable.call there is no state: Node's setter ignores this. + this.destroyed = false; + Readable.call(this, opts || {}); + this._reads = 0; + console.log("hwm", this._readableState.highWaterMark, "objectMode", this._readableState.objectMode); +} +util.inherits(Source, Readable); +Source.prototype._read = function (size) { + this._reads++; + const item = this._list.shift(); + this.push(item === undefined ? null : Buffer.from(item)); +}; + +const s = Source(["alpha", "beta", "gamma"]); +console.log("instanceof", s instanceof Source, s instanceof Readable, Source.super_ === Readable); +console.log("readable", s.readable, "ended", s.readableEnded, "destroyed", s.destroyed); +console.log("flowing", s.readableFlowing, s._readableState.flowing); +s.on("data", function (chunk) { + console.log("data", this === s, Buffer.isBuffer(chunk), chunk.toString()); +}); +s.on("end", function () { + console.log("end", this === s, s.readableEnded, s._readableState.ended, s._reads); +}); +s.on("close", () => { + console.log("close", s.destroyed, s.closed); + paused(); +}); +console.log("sync after on('data')", s.readableFlowing); + +// from2's callback-driven _read, asynchronous pushes and a small +// highWaterMark. +function From(read, opts) { + if (!(this instanceof From)) return new From(read, opts); + this._reading = false; + this._callback = check; + this.destroyed = false; + Readable.call(this, opts); + const self = this; + const hwm = this._readableState.highWaterMark; + function check(err, data) { + if (self.destroyed) return; + if (err) return self.destroy(err); + if (data === null) return self.push(null); + self._reading = false; + if (self.push(data)) self._read(hwm); + } + this._from = read; +} +util.inherits(From, Readable); +From.prototype._read = function (size) { + if (this._reading || this.destroyed) return; + this._reading = true; + this._from(size, this._callback); +}; +From.prototype.destroy = function (err) { + if (this.destroyed) return; + this.destroyed = true; + const self = this; + process.nextTick(function () { + if (err) self.emit("error", err); + self.emit("close"); + }); +}; + +function paused() { + const parts = ["one ", "two ", "three"]; + const r = new From(function (size, next) { + setImmediate(() => next(null, parts.length ? parts.shift() : null)); + }, { highWaterMark: 4 }); + console.log("from hwm", r._readableState.highWaterMark, r.readableHighWaterMark); + r.setEncoding("utf8"); + let text = ""; + r.on("readable", () => { + let chunk; + while ((chunk = r.read()) !== null) text += chunk; + }); + r.on("end", () => { + console.log("paused text", JSON.stringify(text), typeof text); + // From() without new runs outside the listener, where `this` is unset. + setImmediate(failing); + }); +} + +function failing() { + let calls = 0; + const r = From(function (size, next) { + calls++; + if (calls === 2) return next(new Error("source failed")); + next(null, "chunk" + calls); + }); + r.on("data", (d) => console.log("failing data", String(d))); + r.on("error", (e) => console.log("failing error", e.message, r.destroyed)); + r.on("close", () => { + console.log("failing close", calls); + unshifted(); + }); +} + +function unshifted() { + const r = new Source(["xyz"]); + r.once("readable", () => { + const first = r.read(1); + console.log("read(1)", String(first), r.readableLength); + r.unshift(first); + console.log("after unshift", r.readableLength); + r.pause(); + console.log("isPaused", r.isPaused()); + r.on("data", (d) => console.log("rest", String(d))); + r.resume(); + }); + r.on("end", () => console.log("unshifted end")); +} diff --git a/tests/corpus/6561-constructor-function-writable.cjs b/tests/corpus/6561-constructor-function-writable.cjs new file mode 100644 index 000000000..ee25b7595 --- /dev/null +++ b/tests/corpus/6561-constructor-function-writable.cjs @@ -0,0 +1,141 @@ +// Writable and Duplex streams built by constructor functions, the bl and +// tar-stream patterns: Writable.call(this, opts), Duplex.call(this), +// _write/_final/_read on the prototype, listeners added before the base +// constructor runs, and an end() override that calls the base's end. +const stream = require("stream"); +const util = require("util"); + +function Sink(opts) { + if (!(this instanceof Sink)) return new Sink(opts); + stream.Writable.call(this, opts); + this.chunks = []; + this.finalized = false; +} +util.inherits(Sink, stream.Writable); +Sink.prototype._write = function (chunk, enc, cb) { + console.log("_write", this instanceof Sink, enc, Buffer.isBuffer(chunk), chunk.toString()); + this.chunks.push(chunk); + if (this.chunks.length === 2) setImmediate(cb); + else cb(); +}; +Sink.prototype._final = function (cb) { + console.log("_final", this.chunks.length); + this.finalized = true; + cb(); +}; + +const sink = new Sink({ highWaterMark: 8 }); +console.log("writable", sink.writable, sink.writableHighWaterMark, sink._writableState.highWaterMark); +console.log("write a", sink.write("first")); +console.log("write b", sink.write(Buffer.from("second"), () => console.log("write b callback"))); +console.log("needDrain", sink.writableNeedDrain, sink.writableLength); +sink.on("drain", () => console.log("drain", sink.writableLength)); +sink.on("finish", function () { + console.log("finish", this === sink, sink.finalized, sink.writableFinished, Buffer.concat(sink.chunks).toString()); +}); +sink.on("close", () => { + console.log("close", sink.destroyed, sink.closed); + duplex(); +}); +sink.end("third", (err) => console.log("end callback", err, sink.writableEnded, sink._writableState.ended)); +console.log("after end", sink.writableEnded, sink.writable); + +// A bl-style list: a Duplex that collects writes and reads them back. +function BufferListStream(callback) { + if (!(this instanceof BufferListStream)) return new BufferListStream(callback); + if (typeof callback === "function") { + this._callback = callback; + const piper = function piper(err) { + if (this._callback) { + this._callback(err); + this._callback = null; + } + }.bind(this); + this.on("pipe", function onPipe(src) { + console.log("pipe event", src instanceof stream.Readable); + src.on("error", piper); + }); + this.on("unpipe", function onUnpipe(src) { + console.log("unpipe event"); + src.removeListener("error", piper); + }); + callback = null; + } + this._bufs = []; + this.length = 0; + stream.Duplex.call(this); +} +util.inherits(BufferListStream, stream.Duplex); +BufferListStream.prototype._write = function (buf, encoding, callback) { + this._bufs.push(buf); + this.length += buf.length; + callback(); +}; +BufferListStream.prototype._read = function (size) { + if (!this.length) return this.push(null); + const all = Buffer.concat(this._bufs); + size = Math.min(size, this.length, 4); + this.push(all.subarray(0, size)); + this._bufs = [all.subarray(size)]; + this.length -= size; +}; +BufferListStream.prototype.end = function end(chunk) { + stream.Duplex.prototype.end.call(this, chunk); + if (this._callback) { + this._callback(null, Buffer.concat(this._bufs)); + this._callback = null; + } +}; +BufferListStream.prototype.slice = function () { + return Buffer.concat(this._bufs); +}; + +function Letters(text) { + stream.Readable.call(this, { highWaterMark: 2 }); + this._text = text; +} +util.inherits(Letters, stream.Readable); +Letters.prototype._read = function () { + const next = this._text.slice(0, 3); + this._text = this._text.slice(3); + this.push(next.length ? next : null); +}; + +function duplex() { + const list = new BufferListStream((err, data) => { + console.log("bl callback", err, data.toString(), data.length); + }); + console.log("duplex", list.readable, list.writable, list.allowHalfOpen, list instanceof stream.Duplex); + console.log("pre-constructor listeners", list.listenerCount("pipe"), list.listenerCount("unpipe")); + list.write("hello "); + list.write("constructor "); + list.end("functions"); + list.on("finish", () => { + const out = []; + list.on("data", (d) => out.push(d.toString())); + list.on("end", () => { + console.log("read back", JSON.stringify(out)); + piping(); + }); + }); +} + +function piping() { + const src = new Letters("abcdefghij"); + const dst = new Sink({ highWaterMark: 1 }); + dst.on("pipe", (from) => console.log("dst pipe", from === src)); + const returned = src.pipe(dst); + console.log("pipe returns dest", returned === dst); + dst.on("finish", () => { + console.log("piped", Buffer.concat(dst.chunks).toString(), src.readableEnded); + const keep = new Sink(); + const from = new Letters("xyz"); + from.pipe(keep, { end: false }); + from.on("end", () => { + setImmediate(() => { + console.log("end:false keeps the destination open", keep.writable, keep.writableEnded); + keep.end(); + }); + }); + }); +} diff --git a/tests/corpus/6562-constructor-function-transform.cjs b/tests/corpus/6562-constructor-function-transform.cjs new file mode 100644 index 000000000..144a746bc --- /dev/null +++ b/tests/corpus/6562-constructor-function-transform.cjs @@ -0,0 +1,143 @@ +// Transform and PassThrough streams built by constructor functions (the +// yazl and tar-stream patterns), piped together with runtime streams: +// zlib and fs. +const { Readable, Writable, Transform, PassThrough } = require("stream"); +const util = require("util"); +const zlib = require("zlib"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +// yazl's ByteCounter +function ByteCounter(options) { + Transform.call(this, options); + this.byteCount = 0; +} +util.inherits(ByteCounter, Transform); +ByteCounter.prototype._transform = function (chunk, encoding, cb) { + this.byteCount += chunk.length; + cb(null, chunk); +}; + +// A transform with a flush that appends a trailer. +function Upper() { + if (!(this instanceof Upper)) return new Upper(); + Transform.call(this); + this.seen = 0; +} +util.inherits(Upper, Transform); +Upper.prototype._transform = function (chunk, encoding, cb) { + this.seen++; + this.push(chunk.toString().toUpperCase()); + cb(); +}; +Upper.prototype._flush = function (cb) { + cb(null, "[" + this.seen + " chunks]"); +}; + +// tar-stream's entry stream: a PassThrough with its own destroy. +function Source(parent, offset) { + this._parent = parent; + this.offset = offset; + PassThrough.call(this, { autoDestroy: false }); +} +util.inherits(Source, PassThrough); +Source.prototype.destroy = function (err) { + console.log("Source.destroy forwarded", err && err.message); + this._parent.destroyed = true; +}; + +// A PassThrough without its own _transform passes chunks through. +function Plain() { + PassThrough.call(this); +} +util.inherits(Plain, PassThrough); + +function Words(words) { + Readable.call(this); + this._words = words.slice(); +} +util.inherits(Words, Readable); +Words.prototype._read = function () { + this.push(this._words.length ? this._words.shift() : null); +}; + +function Collect() { + Writable.call(this); + this.parts = []; +} +util.inherits(Collect, Writable); +Collect.prototype._write = function (chunk, enc, cb) { + this.parts.push(chunk); + cb(); +}; +Collect.prototype.text = function () { + return Buffer.concat(this.parts).toString(); +}; + +const upper = Upper(); +const words = []; +upper.on("data", (d) => words.push(d.toString())); +upper.on("end", () => { + console.log("upper", JSON.stringify(words), upper.seen, upper._readableState.ended, upper._writableState.finished); + counter(); +}); +upper.write("one "); +upper.write(Buffer.from("two ")); +upper.end("three"); + +function counter() { + const counterStream = new ByteCounter(); + const sink = new Collect(); + new Words(["hello ", "byte ", "counter"]).pipe(counterStream).pipe(sink); + sink.on("finish", () => { + console.log("counted", counterStream.byteCount, JSON.stringify(sink.text())); + entry(); + }); +} + +function entry() { + const parent = { destroyed: false }; + const source = new Source(parent, 512); + const plain = new Plain(); + console.log("source", source.offset, source instanceof PassThrough, source instanceof Transform); + let body = ""; + source.pipe(plain); + plain.on("data", (d) => (body += d)); + plain.on("end", () => { + console.log("entry body", JSON.stringify(body), source.destroyed); + source.destroy(new Error("stop")); + console.log("parent destroyed", parent.destroyed); + gzip(); + }); + source.write("file "); + source.end("contents"); +} + +function gzip() { + const counted = new ByteCounter(); + const out = new Collect(); + new Words(["compressed ", "through ", "zlib"]).pipe(counted).pipe(zlib.createGzip()).pipe(out); + out.on("finish", () => { + const back = zlib.gunzipSync(Buffer.concat(out.parts)).toString(); + console.log("gzip roundtrip", JSON.stringify(back), counted.byteCount); + toFile(); + }); +} + +function toFile() { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ctor-stream-")); + const file = path.join(dir, "out.txt"); + const ws = fs.createWriteStream(file); + new Words(["written ", "by ", "a ", "constructor"]).pipe(Upper()).pipe(ws); + ws.on("finish", () => { + console.log("file", JSON.stringify(fs.readFileSync(file, "utf8"))); + // A runtime stream piped into a constructor-function stream. + const back = new Collect(); + fs.createReadStream(file).pipe(new ByteCounter()).pipe(back); + back.on("finish", () => { + console.log("read back", JSON.stringify(back.text())); + fs.rmSync(dir, { recursive: true }); + }); + }); +} diff --git a/tests/corpus/6563-constructor-function-stream-errors.cjs b/tests/corpus/6563-constructor-function-stream-errors.cjs new file mode 100644 index 000000000..cf2fb3e7c --- /dev/null +++ b/tests/corpus/6563-constructor-function-stream-errors.cjs @@ -0,0 +1,137 @@ +// Errors, destroy and custom events on constructor-function streams. +const { Readable, Writable, Duplex } = require("stream"); +const util = require("util"); + +function Failing() { + Writable.call(this); +} +util.inherits(Failing, Writable); +Failing.prototype._write = function (chunk, enc, cb) { + const err = new Error("cannot store " + chunk); + err.code = "E_STORE"; + this.lastError = err; + cb(err); +}; + +const failing = new Failing(); +failing.on("error", function (err) { + console.log("write error", this === failing, err === failing.lastError, err.message, err.code); + console.log("errored", failing.errored === err, failing.destroyed, failing.writable); +}); +failing.on("close", () => { + console.log("write close"); + destroying(); +}); +failing.write("x", (err) => console.log("write callback", err && err.code)); + +function Ticker() { + Readable.call(this); + this.n = 0; +} +util.inherits(Ticker, Readable); +Ticker.prototype._read = function () { + this.push(String(this.n++)); +}; +Ticker.prototype._destroy = function (err, cb) { + console.log("_destroy", err && err.message, this.n > 0); + setImmediate(() => cb(err && new Error("wrapped: " + err.message))); +}; + +function destroying() { + const t = new Ticker(); + const order = []; + t.on("data", (d) => { + if (d.toString() === "3") t.destroy(new Error("enough"), (e) => order.push("destroy callback " + (e && e.message))); + }); + t.on("error", (e) => order.push("error " + e.message)); + t.on("close", () => { + order.push("close"); + console.log(order.join(" | "), t.destroyed, t.readable); + t.destroy(null, () => console.log("destroy after close calls back")); + notImplemented(); + }); +} + +function Bare() { + Readable.call(this); +} +util.inherits(Bare, Readable); + +function Throws() { + Readable.call(this); +} +util.inherits(Throws, Readable); +Throws.prototype._read = function () { + throw new TypeError("read threw"); +}; + +function notImplemented() { + const bare = new Bare(); + bare.on("error", (e) => { + console.log("bare", e.code, e.message); + const thrower = new Throws(); + thrower.on("error", (err) => { + console.log("throws", err instanceof TypeError, err.message, thrower.destroyed); + afterEnd(); + }); + thrower.resume(); + }); + bare.resume(); +} + +function Quiet() { + Writable.call(this); +} +util.inherits(Quiet, Writable); +Quiet.prototype._write = function (chunk, enc, cb) { + cb(); +}; + +function afterEnd() { + const q = new Quiet(); + q.end("done"); + q.on("error", (e) => { + console.log("after end", e.code, e.message); + customEvents(); + }); + q.write("late"); +} + +// tar-stream's Extract emits 'entry' with three arguments. +function Extract() { + if (!(this instanceof Extract)) return new Extract(); + Writable.call(this); +} +util.inherits(Extract, Writable); +Extract.prototype._write = function (data, enc, cb) { + const header = { name: data.toString(), size: data.length }; + this.emit("entry", header, "stream-for-" + header.name, cb); +}; + +function customEvents() { + const ex = Extract(); + const seen = []; + function onEntry(header, s, next) { + seen.push(header.name + ":" + header.size + ":" + s + ":" + (this === ex)); + next(); + } + ex.on("entry", onEntry); + ex.once("entry", (header) => seen.push("once " + header.name)); + ex.prependListener("entry", () => seen.push("first")); + console.log("listenerCount", ex.listenerCount("entry"), ex.listenerCount("entry", onEntry)); + ex.write("a.txt"); + ex.removeListener("entry", onEntry); + ex.write("b.txt"); + console.log(seen.join(" | ")); + console.log("emit returns", ex.emit("nothing"), ex.emit("entry", { name: "c", size: 0 }, "", () => {})); + try { + ex.emit("error", new RangeError("nobody listens")); + } catch (e) { + console.log("unhandled error rethrown", e instanceof RangeError, e.message); + } + ex.setMaxListeners(1); + console.log("max listeners", ex.getMaxListeners()); + ex.removeAllListeners("entry"); + console.log("after removeAll", ex.listenerCount("entry")); + console.log("Duplex prototype", typeof Duplex.prototype.write, typeof Duplex.prototype.push); +} diff --git a/tests/corpus/6564-constructor-function-stream-late-hooks.cjs b/tests/corpus/6564-constructor-function-stream-late-hooks.cjs new file mode 100644 index 000000000..22a8ec2f4 --- /dev/null +++ b/tests/corpus/6564-constructor-function-stream-late-hooks.cjs @@ -0,0 +1,117 @@ +// Stream hooks a constructor function assigns after the base constructor +// runs, and unpipe from the destination's 'pipe' listener. +const { Writable, Transform, PassThrough } = require("stream"); +const util = require("util"); + +function Sink() { + Writable.call(this); + this.chunks = []; + this._final = function (cb) { + console.log("_final", this instanceof Sink, this.chunks.join(",")); + setImmediate(cb); + }; +} +util.inherits(Sink, Writable); +Sink.prototype._write = function (chunk, enc, cb) { + this.chunks.push(String(chunk)); + cb(); +}; + +const sink = new Sink(); +sink.on("prefinish", () => console.log("sink prefinish")); +sink.on("finish", () => { + console.log("sink finish"); + flushing(); +}); +sink.write("a"); +sink.end("b"); + +function Summer() { + Transform.call(this); + this.total = 0; + this._flush = function (cb) { + console.log("_flush", this.total); + cb(null, "total " + this.total); + }; +} +util.inherits(Summer, Transform); +Summer.prototype._transform = function (chunk, enc, cb) { + this.total += Number(String(chunk)); + cb(null, "seen " + chunk + "\n"); +}; + +function flushing() { + const summer = new Summer(); + const out = []; + summer.on("data", (chunk) => out.push(String(chunk))); + summer.on("end", () => { + console.log("summer end", JSON.stringify(out)); + passing(); + }); + summer.write("2"); + summer.write("3"); + summer.end(); +} + +function Upper() { + PassThrough.call(this); + this._transform = function (chunk, enc, cb) { + cb(null, String(chunk).toUpperCase()); + }; +} +util.inherits(Upper, PassThrough); + +function passing() { + const upper = new Upper(); + const out = []; + upper.on("data", (chunk) => out.push(String(chunk))); + upper.on("end", () => { + console.log("upper end", JSON.stringify(out)); + plain(); + }); + upper.write("x"); + upper.end("y"); +} + +// Without late hooks the defaults still apply. +function Plain() { + PassThrough.call(this); +} +util.inherits(Plain, PassThrough); + +function plain() { + const p = new Plain(); + const out = []; + p.on("data", (chunk) => out.push(String(chunk))); + p.on("finish", () => console.log("plain finish")); + p.on("end", () => { + console.log("plain end", JSON.stringify(out)); + unpiping(); + }); + p.end("same"); +} + +function Collector() { + Writable.call(this); + this.chunks = []; +} +util.inherits(Collector, Writable); +Collector.prototype._write = function (chunk, enc, cb) { + this.chunks.push(String(chunk)); + cb(); +}; + +function unpiping() { + const source = new PassThrough(); + const collector = new Collector(); + collector.on("pipe", function (src) { + console.log("pipe", typeof src.unpipe, this === collector); + src.unpipe(this); + }); + collector.on("unpipe", (src) => { + console.log("unpipe", typeof src.pipe); + source.write("after"); + setImmediate(() => console.log("collected", JSON.stringify(collector.chunks), source.readableFlowing)); + }); + source.pipe(collector); +} diff --git a/tests/diagnostics/retired-engine-761-any-objects.ts b/tests/corpus/761-any-objects.ts similarity index 67% rename from tests/diagnostics/retired-engine-761-any-objects.ts rename to tests/corpus/761-any-objects.ts index 59796020b..45e6e6f82 100644 --- a/tests/diagnostics/retired-engine-761-any-objects.ts +++ b/tests/corpus/761-any-objects.ts @@ -1,6 +1,6 @@ -// Object/array literals in `any` slots build natively in the island; -// property reads/writes, computed keys, and method calls go through the -// engine (this-binding and coercions JS-exact). +// Object/array literals in `any` slots: property reads/writes, computed +// keys, and method calls go through the checked-dynamic value, including a +// member read on a fresh typed call result (`s.split(" ").length`). const o: any = { a: 1, b: "x", nested: { deep: true } }; o.a = o.a + 10; o["c"] = 2.5; diff --git a/tests/diagnostics/events-once-fences.ts b/tests/diagnostics/events-once-fences.ts index 2e5613f70..97bc76254 100644 --- a/tests/diagnostics/events-once-fences.ts +++ b/tests/diagnostics/events-once-fences.ts @@ -1,11 +1,14 @@ -// events.once lowers for EventEmitter instances and literal event names; -// these forms stay refused. +// events.once lowers for EventEmitter and stream instances, literal event +// names and a literal { signal } options object; these forms stay refused. import { EventEmitter, once } from "node:events"; +import { PassThrough } from "node:stream"; const ee = new EventEmitter(); const name: string = process.argv[2] ?? "ready"; +const options = {}; async function main(): Promise { - await once(ee, "ready", { signal: AbortSignal.timeout(5) }); // options + await once(ee, "ready", options); // options that are not an object literal await once(ee, name); // computed event name + await once(new PassThrough(), "data"); // a stream's 'data' event } void main(); diff --git a/tests/fixtures/node-types/events-once-signal-race.ts b/tests/fixtures/node-types/events-once-signal-race.ts new file mode 100644 index 000000000..4bc1d2d36 --- /dev/null +++ b/tests/fixtures/node-types/events-once-signal-race.ts @@ -0,0 +1,86 @@ +// events.once under @types/node, which types the result Promise: +// a NodeJS.ReadableStream slot and a PassThrough raced with an AbortSignal +// that the finally block aborts (the entries that throw are +// Promise, so the race result is the any[] of the 'end' entry), an +// EventEmitter race, and a Transform whose completion callback is declared +// with the broad `Function` type. +import { EventEmitter, once } from "node:events"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Transform } from "node:stream"; + +function copyPart(src: NodeJS.ReadableStream, out: PassThrough) { + const ac = new AbortController(); + let failed = false; + src.pipe(out, { end: false }); + return Promise.race([ + once(src, "end", { signal: ac.signal }), + once(src, "error", { signal: ac.signal }).then(([error]) => { + failed = true; + throw error; + }), + once(out, "error", { signal: ac.signal }).then(([error]) => { + failed = true; + throw error; + }), + once(out, "close", { signal: ac.signal }).then(() => { + failed = true; + throw new Error("output closed before the part ended"); + }), + ]).finally(() => { + ac.abort(); + src.unpipe(out); + console.log("part done", failed); + }); +} + +function waitFor(a: EventEmitter) { + const ac = new AbortController(); + return Promise.race([ + once(a, "done", { signal: ac.signal }), + once(a, "error", { signal: ac.signal }).then(([error]) => { + throw error; + }), + ]).finally(() => ac.abort()); +} + +class Prefixer extends Transform { + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + _transform(chunk: Buffer, _encoding: string, cb: Function) { + cb(null, "> " + chunk.toString()); + } +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-once-race-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "part\n"); + const out = new PassThrough(); + let text = ""; + out.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + const ended = await copyPart(fs.createReadStream(file), out); + console.log("ended", ended.length); + await copyPart(fs.createReadStream(file).pipe(new Prefixer()), out); + console.log(JSON.stringify(text)); + try { + await copyPart(fs.createReadStream(path.join(dir, "missing.txt")), out); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + const a = new EventEmitter(); + setTimeout(() => a.emit("done", 1, "two"), 1); + const args = (await waitFor(a)) as unknown[]; + console.log("done", JSON.stringify(args), a.listenerCount("done"), a.listenerCount("error")); + setTimeout(() => a.emit("error", new Error("bad")), 1); + try { + await waitFor(a); + } catch (err) { + console.log("caught", (err as Error).message, a.listenerCount("done")); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/fixtures/self-hosting/lowering-context-cases.ts b/tests/fixtures/self-hosting/lowering-context-cases.ts index efa27183d..a39063796 100644 --- a/tests/fixtures/self-hosting/lowering-context-cases.ts +++ b/tests/fixtures/self-hosting/lowering-context-cases.ts @@ -35,7 +35,7 @@ function runContextCase(services: FrontendServices, entry: string): string[] { const shadowSymbol = symbols[1]!; const unusedSymbol = symbols[2]!; requireValue(outerSymbol !== shadowSymbol, "shadowed bindings share identity"); - requireValue(outerSymbol.name === shadowSymbol.name, "fixture did not shadow a name"); + requireValue(ts.symbolName(outerSymbol) === ts.symbolName(shadowSymbol), "fixture did not shadow a name"); const source = bindingSource(declarations[0]!); const root = newFnCtx(false, null, null, VOID); const outer = declareContextLocal(root, "value", F64, true, outerSymbol, source); diff --git a/tests/fixtures/self-hosting/ts7-checker-cases.ts b/tests/fixtures/self-hosting/ts7-checker-cases.ts index da0a3dc52..664dccd50 100644 --- a/tests/fixtures/self-hosting/ts7-checker-cases.ts +++ b/tests/fixtures/self-hosting/ts7-checker-cases.ts @@ -2,7 +2,7 @@ import { AstNode } from "../../../packages/compiler/src/frontend/ts7/ast-node.js import type { SourceFile } from "../../../packages/compiler/src/frontend/ts7/ast-types.js"; import { CheckerFacade, constituentTypes } from "../../../packages/compiler/src/frontend/ts7/checker.js"; import { SemanticChecker } from "../../../packages/compiler/src/frontend/ts7/semantic-checker.js"; -import { SemanticType } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; +import { SemanticType, symbolName, symbolNameOf } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; import { SyntaxKind, TypeFlags } from "../../../packages/compiler/src/frontend/ts7/enums.js"; export function checkerSource(): string { @@ -67,8 +67,8 @@ export function checkCheckerFacade(raw: SemanticChecker, root: SourceFile, reque const box = declaration(root, "Box"); const boxSymbol = facade.getSymbolAtLocation(box.name!)!; const boxType = facade.getDeclaredTypeOfSymbol(boxSymbol); - check(facade.getPropertyOfType(boxType, "value")!.name === "value", "property lookup"); - check(facade.getPropertiesOfType(boxType).some((property) => property.name === "method"), "property list"); + check(symbolName(facade.getPropertyOfType(boxType, "value")!) === "value", "property lookup"); + check(facade.getPropertiesOfType(boxType).some((property) => symbolName(property) === "method"), "property list"); check(facade.getBaseTypes(boxType).length === 0, "empty class bases"); const child = facade.getDeclaredTypeOfSymbol(facade.getSymbolAtLocation(declaration(root, "Child").name!)!); check(facade.getBaseTypes(child)[0] === boxType, "class base identity"); @@ -123,7 +123,7 @@ export function checkCheckerFacade(raw: SemanticChecker, root: SourceFile, reque check(facade.getTypeArguments(number).length === 0, "nonreference arguments"); const dict = facade.getDeclaredTypeOfSymbol(facade.getSymbolAtLocation(declaration(root, "Dict").name!)!); const index = facade.getIndexInfosOfType(dict)[0]!; - check(index.keyType === string && index.valueType === number && index.isReadonly, "index signature"); + check(index.keyType === string && index.type === number && index.isReadonly, "index signature"); check(facade.getAwaitedType(aliasType(facade, root, "Promised")) === number, "await promise"); check(facade.getAwaitedType(aliasType(facade, root, "MaybePromised")) === number, "await collapsed union"); check(facade.getAwaitedType(aliasType(facade, root, "DifferentPromises")) === undefined, "await distinct union refusal"); diff --git a/tests/fixtures/self-hosting/ts7-client-cases.ts b/tests/fixtures/self-hosting/ts7-client-cases.ts index d97f90892..0feaee6c9 100644 --- a/tests/fixtures/self-hosting/ts7-client-cases.ts +++ b/tests/fixtures/self-hosting/ts7-client-cases.ts @@ -12,6 +12,7 @@ import { SyntaxKind, ScriptTarget, ModuleKind, ModuleResolutionKind, ModuleDetec import { Ts7Session } from "../../../packages/compiler/src/frontend/ts7/session.js"; import { Ts7Paths, ts7DocumentFile } from "../../../packages/compiler/src/frontend/ts7/session-path.js"; import { parseSemanticJson } from "../../../packages/compiler/src/frontend/ts7/semantic-json.js"; +import { symbolName, symbolNameOf } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; import { checkSemanticModel, semanticSource } from "./ts7-semantic-cases.js"; import { checkAstViews } from "./ts7-ast-views.js"; @@ -313,7 +314,7 @@ export function runTs7Client(client: Ts7RpcClient, directory: string, report: st const timing = client.timing(); check(timing.requests > 20 && timing.callbacks > 0 && reads > 0, "requests and filesystem callbacks executed"); writeFileSync(report, JSON.stringify({ - typeText, symbol: ownedSymbol.name, diagnostics: diagnostics.map((diagnostic) => diagnostic.code), + typeText, symbol: symbolName(ownedSymbol), diagnostics: diagnostics.map((diagnostic) => diagnostic.code), surrogateBoundary: checkSurrogateBoundary(), semanticSurrogateBoundary: checkSemanticSurrogateBoundary(), semanticModel: true, sessionLifecycle: true, echo: true, binaryAst: true, astIdentity: true, astViews: true, checkerFacade: true, checkerSnapshots: true, virtualFiles: true, retainedSnapshot: true, serverErrorRecovery: true, protocolFailures: true, diff --git a/tests/fixtures/self-hosting/ts7-semantic-cases.ts b/tests/fixtures/self-hosting/ts7-semantic-cases.ts index 41b61b824..07f2d7e14 100644 --- a/tests/fixtures/self-hosting/ts7-semantic-cases.ts +++ b/tests/fixtures/self-hosting/ts7-semantic-cases.ts @@ -1,7 +1,8 @@ import { AstFile, AstNode } from "../../../packages/compiler/src/frontend/ts7/ast-node.js"; import { AstKind } from "../../../packages/compiler/src/frontend/ts7/ast-schema.generated.js"; import { SemanticChecker } from "../../../packages/compiler/src/frontend/ts7/semantic-checker.js"; -import { SemanticSignature, SemanticSnapshot, SemanticSymbol, SemanticType } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; +import { SemanticSignature, SemanticSnapshot, SemanticSymbol, SemanticType, symbolName, symbolNameOf } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; +import type { __String } from "../../../packages/compiler/src/frontend/ts7/ast-types.js"; import { SemanticSignatureKind, SemanticSymbolFlags, SemanticTypeFlags } from "../../../packages/compiler/src/frontend/ts7/semantic-schema.generated.js"; export function semanticSource(): string { @@ -113,7 +114,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const pairTarget = pair.getTarget()!; check(pairTarget.isTupleType() && pairTarget.fixedLength === 2 && pairTarget.readonly === true && pairTarget.elementFlags!.length === 2, "tuple target metadata"); check(checker.getTypeArguments(pair)[0] === intrinsicNumber && checker.getTypeArguments(pair)[1] === intrinsicString, "tuple argument identities"); - check(pair.getAliasSymbol()!.name === "Pair" && pair.getAliasTypeArguments()[0] === intrinsicNumber, "alias metadata"); + check(symbolName(pair.getAliasSymbol()!) === "Pair" && pair.getAliasTypeArguments()[0] === intrinsicNumber, "alias metadata"); const box = declaration(root, "Box"); const boxSymbol = symbolAt(checker, box.name!); @@ -121,21 +122,21 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic check(boxType.isClassOrInterface() && boxType.getSymbol() === boxSymbol, "declared class type"); // The checker instantiates a transient property symbol for a class type; // the binder's member symbol is distinct but refers to the same AST node. - const boundMember = boxSymbol.getMembers().get("value")!; + const boundMember = boxSymbol.getMembers().get("value" as __String)!; const checkedMember = checker.getPropertyOfType(boxType, "value")!; check(boundMember !== checkedMember && boundMember.declarationHandles[0]!.resolve() === checkedMember.declarationHandles[0]!.resolve(), "bound and instantiated member declarations"); check(boxSymbol.getMembers() === boxSymbol.getMembers() && boxSymbol.getExports() === boxSymbol.getExports(), "symbol table cache"); check(boxType.getBaseTypes()!.length === 0 && checker.getBaseTypes(boxType).length === 0, "empty base types"); const child = checker.getDeclaredTypeOfSymbol(symbolAt(checker, declaration(root, "Child").name!)); check(child.getBaseTypes()![0] === boxType && checker.getBaseTypes(child)[0] === boxType, "base type request identity"); - check(checker.getPropertiesOfType(boxType).some((symbol) => symbol.name === "method"), "property query"); + check(checker.getPropertiesOfType(boxType).some((symbol) => symbolName(symbol) === "method"), "property query"); check(checker.getPropertyOfType(boxType, "missing") === undefined, "absent property"); const nullable = typeAt(checker, declaration(root, "Nullable").type!); check(checker.getNonNullableType(nullable) === boxType, "non-nullable type"); const dict = checker.getDeclaredTypeOfSymbol(symbolAt(checker, declaration(root, "Dict").name!)); const index = checker.getIndexInfosOfType(dict)[0]!; - check(index.keyType === intrinsicString && index.valueType === intrinsicNumber && index.isReadonly, "index signature types"); - check(index.declaration!.resolve() === declaration(root, "Dict").members?.nodes[0], "index signature declaration"); + check(index.keyType === intrinsicString && index.type === intrinsicNumber && index.isReadonly, "index signature types"); + check(index.declaration === declaration(root, "Dict").members?.nodes[0], "index signature declaration"); const generic = checker.getDeclaredTypeOfSymbol(symbolAt(checker, declaration(root, "Generic").name!)); const genericParameter = generic.getTypeParameters()[0]!; check(genericParameter.isTypeParameter() && generic.getLocalTypeParameters()[0] === genericParameter && generic.getOuterTypeParameters().length === 0, "interface type parameters"); @@ -147,7 +148,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const signature = signatureAt(checker, identity); const signatureTypes = signature.getTypeParameters(); check(signatureTypes.length === 1 && signatureTypes[0]!.isTypeParameter(), "signature type parameters"); - check(signature.getParameters()[0]!.name === "value" && signature.getParameters()[0]!.declarationHandles[0]!.resolve() === identity.parameters?.nodes[0], "signature parameters"); + check(symbolName(signature.getParameters()[0]!) === "value" && signature.getParameters()[0]!.declarationHandles[0]!.resolve() === identity.parameters?.nodes[0], "signature parameters"); check(signature.declarationHandle!.resolve() === identity, "signature declaration"); check(checker.getReturnTypeOfSignature(signature) === signatureTypes[0], "generic return identity"); check(checker.getParameterType(signature, 0) === signatureTypes[0], "parameter type query"); @@ -159,7 +160,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const ctor = checker.getSignaturesOfType(checker.getTypeOfSymbol(boxSymbol)!, SemanticSignatureKind.Construct)[0]!; check(ctor.isConstruct && !ctor.isAbstract && checker.getReturnTypeOfSignature(ctor) === boxType, "construct signature"); const thisSignature = signatureAt(checker, declaration(root, "withThis")); - check(thisSignature.hasRestParameter && !thisSignature.isConstruct && thisSignature.getThisParameter()!.name === "this", "signature this/rest flags"); + check(thisSignature.hasRestParameter && !thisSignature.isConstruct && symbolName(thisSignature.getThisParameter()!) === "this", "signature this/rest flags"); check(checker.getRestTypeOfSignature(thisSignature) === intrinsicNumber && checker.isArrayLikeType(pair), "array predicates and rest element type"); const predicate = checker.getTypePredicateOfSignature(signatureAt(checker, declaration(root, "isBox")))!; check(predicate.type === boxType && predicate.parameterIndex === 0 && predicate.parameterName === "value", "type predicate"); @@ -169,7 +170,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const moduleSymbol = symbolAt(checker, root); const exports = moduleSymbol.getExports(); - const renamed = exports.get("renamed")!; + const renamed = exports.get("renamed" as __String)!; check(checker.getAliasedSymbol(renamed) === answerSymbol && checker.getImmediateAliasedSymbol(renamed) === answerSymbol, "symbol alias targets"); check(checker.getMemberInModuleExports(moduleSymbol, "answer") === answerSymbol, "module export lookup"); check(checker.getExportsOfModule(moduleSymbol).some((symbol) => symbol === answerSymbol), "module exports identity"); diff --git a/tests/harness/__snapshots__/events-once-fences.ts.txt b/tests/harness/__snapshots__/events-once-fences.ts.txt index ffab538b0..60c499cfe 100644 --- a/tests/harness/__snapshots__/events-once-fences.ts.txt +++ b/tests/harness/__snapshots__/events-once-fences.ts.txt @@ -1,17 +1,26 @@ -events-once-fences.ts:8:27 - error SC2020: 'events.once with options' is part of the standard library types but has no scriptc lowering yet +events-once-fences.ts:10:27 - error SC2020: 'events.once options that are not an object literal' is part of the standard library types but has no scriptc lowering yet - 7 | async function main(): Promise { - 8 | await once(ee, "ready", { signal: AbortSignal.timeout(5) }); // options - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 9 | await once(ee, name); // computed event name + 9 | async function main(): Promise { + 10 | await once(ee, "ready", options); // options that are not an object literal + | ^~~~~~~ + 11 | await once(ee, name); // computed event name - hint: the lowered form is events.once(emitter, name); AbortSignal options are not supported yet + hint: pass the options as an object literal: { signal } -events-once-fences.ts:9:18 - error SC2020: 'events.once with a computed or meta event name' is part of the standard library types but has no scriptc lowering yet +events-once-fences.ts:11:18 - error SC2020: 'events.once with a computed or meta event name' is part of the standard library types but has no scriptc lowering yet - 8 | await once(ee, "ready", { signal: AbortSignal.timeout(5) }); // options - 9 | await once(ee, name); // computed event name + 10 | await once(ee, "ready", options); // options that are not an object literal + 11 | await once(ee, name); // computed event name | ^~~~ - 10 | } + 12 | await once(new PassThrough(), "data"); // a stream's 'data' event - hint: name the awaited event with a string literal \ No newline at end of file + hint: name the awaited event with a string literal + +events-once-fences.ts:12:33 - error SC2020: 'events.once of a stream's 'data' event' is part of the standard library types but has no scriptc lowering yet + + 11 | await once(ee, name); // computed event name + 12 | await once(new PassThrough(), "data"); // a stream's 'data' event + | ^~~~~~ + 13 | } + + hint: the chunk has no checked-dynamic boxing here; register stream.once('data', listener) instead \ No newline at end of file diff --git a/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt b/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt index 531002a4b..4bbff5a33 100644 --- a/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt +++ b/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt @@ -1,66 +1,3 @@ -retired-engine-2083-destructuring-island-decl.ts:9:5 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 8 | const a: any = [1, "two", true, 4, 5]; - 9 | var [x, , y, ...rest] = a; - | ^~~~~~~~~~~~~~~~~~~~~ - 10 | console.log(`${x} ${y} ${rest.length} ${rest[0]} ${rest[1]}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:12:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 11 | const o: any = { p: 1, q: { r: "deep" } }; - 12 | const { p, q: { r } } = o; - | ^~~~~~~~~~~~~~~~~~~ - 13 | console.log(`${p} ${r}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:14:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 13 | console.log(`${p} ${r}`); - 14 | const { missing = 42 } = o; - | ^~~~~~~~~~~~~~~~~~~~ - 15 | console.log(`${missing}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:16:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 15 | console.log(`${missing}`); - 16 | const { p: p2 = 9, ...others } = o; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 17 | console.log(`${p2} ${others.q.r}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:17:22 - error SC2004: uses of 'others' inherit the blocker on its declaration - - 16 | const { p: p2 = 9, ...others } = o; - 17 | console.log(`${p2} ${others.q.r}`); - | ^~~~~~~~~~ - 18 | var [] = [1, 2] as any; - - hint: the declaration of 'others' did not compile — fix the diagnostic reported there and these sites clear with it - -retired-engine-2083-destructuring-island-decl.ts:19:5 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 18 | var [] = [1, 2] as any; - 19 | var {} = o; - | ^~~~~~ - 20 | console.log("empties ok"); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:22:11 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 21 | const u: any = undefined; - 22 | try { var {} = u; } catch (e) { console.log("obj-undef throws"); } - | ^~~~~~ - 23 | try { var [] = 0 as any; } catch (e) { console.log("arr-num throws"); } - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2083-destructuring-island-decl.ts:23:11 - error SC2011: destructuring on 'any'-typed values cannot compile statically 22 | try { var {} = u; } catch (e) { console.log("obj-undef throws"); } diff --git a/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt b/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt index b41cd4d10..415eb5895 100644 --- a/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt +++ b/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt @@ -1,12 +1,10 @@ -retired-engine-2104-computed-key-destructuring.ts:12:9 - error SC2011: destructuring on 'any'-typed values cannot compile statically +retired-engine-2104-computed-key-destructuring.ts:12:11 - error SC1031: destructuring with computed keys that do not fold to one property name is not supported yet 11 | function pick(key: string) { 12 | const { [key]: got } = src; - | ^~~~~~~~~~~~~~~~~~~~ + | ^~~~~~~~~~ 13 | return got; - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2104-computed-key-destructuring.ts:13:10 - error SC2004: uses of 'got' inherit the blocker on its declaration 12 | const { [key]: got } = src; @@ -16,15 +14,13 @@ retired-engine-2104-computed-key-destructuring.ts:13:10 - error SC2004: uses of hint: the declaration of 'got' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2104-computed-key-destructuring.ts:17:9 - error SC2011: destructuring on 'any'-typed values cannot compile statically +retired-engine-2104-computed-key-destructuring.ts:17:11 - error SC1031: destructuring with computed keys that do not fold to one property name is not supported yet 16 | function pick2(key: string) { 17 | const { [key]: got = "dflt" } = src; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ^~~~~~~~~~~~~~~~~~~ 18 | return got; - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2104-computed-key-destructuring.ts:18:10 - error SC2004: uses of 'got' inherit the blocker on its declaration 17 | const { [key]: got = "dflt" } = src; @@ -34,15 +30,6 @@ retired-engine-2104-computed-key-destructuring.ts:18:10 - error SC2004: uses of hint: the declaration of 'got' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2104-computed-key-destructuring.ts:23:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 22 | const key2 = "y"; - 23 | const { x: sx, [key2]: sy } = src; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ - 24 | console.log(`${sx} ${sy}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2104-computed-key-destructuring.ts:30:38 - error SC2009: values of type '[any?]' cannot be compiled: the tuple shape is supported, but its element type 'any' does not compile 29 | let order = (n: any): any => { traceS += `${n},`; return n; }; diff --git a/tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt b/tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt deleted file mode 100644 index 008901f2d..000000000 --- a/tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt +++ /dev/null @@ -1,6 +0,0 @@ -retired-engine-761-any-objects.ts:13:38 - error SC1090: reading 'length' from a value of type 'any' is not supported yet - - 12 | const s: any = "hello world"; - 13 | console.log(`${s.toUpperCase()}`, `${s.split(" ").length}`, `${s.charAt(4)}`); - | ^~~~~~~~~~~~~~~~~~~ - 14 | const num: any = 3.14159; \ No newline at end of file diff --git a/tests/harness/ci-lanes.test.ts b/tests/harness/ci-lanes.test.ts index c0e74a934..faca8a7d0 100644 --- a/tests/harness/ci-lanes.test.ts +++ b/tests/harness/ci-lanes.test.ts @@ -1,7 +1,22 @@ -import { readFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { load } from "js-yaml"; import { expect, test } from "vitest"; import { select } from "../../scripts/ci-lanes.mjs"; +import { darwinCorpusFiles, darwinFiles } from "../../scripts/ci-test-plan.mjs"; + +type Job = { + if?: string; + "runs-on"?: string; + strategy?: { matrix?: { include?: { runner?: string }[] } }; + steps: { id?: string; run?: string }[]; +}; +const workflow = load( + readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), +) as { on: { workflow_dispatch: { inputs: Record } }; jobs: Record }; +const lanesScript = workflow.jobs["lanes"]!.steps.find((step) => step.id === "lanes")!.run!; type Selection = { on: Record; reasons: string[] }; const lanesOf = (files: string[], lines: { file: string; text: string }[] = []) => @@ -43,6 +58,16 @@ test.each([ ["tests/harness/ffi-frameworks.test.ts"], { linux: true, macos: true, effects: false, bootstrap: false }, ], + [ + "the kqueue event loop", + ["packages/runtime/src/scr_loop_kqueue.c"], + { linux: true, macos: true, effects: false, bootstrap: false }, + ], + [ + "the runtime's platform header", + ["packages/runtime/src/scr_platform.h"], + { linux: true, macos: true, effects: false, bootstrap: false }, + ], [ "the Effect harness", ["tests/harness/effect.test.ts"], @@ -91,15 +116,61 @@ test("a changed line that mentions a darwin-specific API runs the macOS lane", ( expect(lanesOf([file], [{ file, text: " struct kevent change;" }]).macos).toBe(true); }); +test("every file of the darwin suite runs the macOS lane", () => { + for (const file of [...darwinCorpusFiles, ...darwinFiles]) + expect(lanesOf([file]).macos, file).toBe(true); +}); + test("CI runs the base commit's lane selection against the pull request's diff", () => { - const workflow = load( - readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), - ) as { jobs: Record }; - const script = workflow.jobs["lanes"]!.steps.find((step) => step.id === "lanes")!.run!; - expect(script).toContain('git show "$BASE:scripts/ci-lanes.mjs"'); - expect(script).toContain( + expect(lanesScript).toContain('git show "$BASE:scripts/ci-lanes.mjs"'); + expect(lanesScript).toContain( 'node "$RUNNER_TEMP/ci-lanes.mjs" --base "$BASE" --head "$HEAD" --github-output', ); - // Pushes and the nightly sweep, and a base without the script, run every lane. - expect(script.match(/every$/gm)).toHaveLength(2); + // The nightly sweep and manual runs, and a base without the script, run every lane. + expect(lanesScript.match(/every$/gm)).toHaveLength(2); +}); + +function runLanes(event: string) { + const directory = mkdtempSync(join(tmpdir(), "ci-lanes-")); + try { + const output = join(directory, "output"); + writeFileSync(output, ""); + const result = spawnSync("bash", ["-e", "-o", "pipefail", "-c", lanesScript], { + encoding: "utf8", + env: { ...process.env, EVENT: event, GITHUB_OUTPUT: output }, + }); + expect(result.status, result.stderr).toBe(0); + return readFileSync(output, "utf8"); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +} + +test("pushes to main run every lane except macos; the nightly sweep and manual runs run every lane", () => { + expect(runLanes("push")).toBe("linux=true\nmacos=false\neffects=true\nbootstrap=true\n"); + for (const event of ["schedule", "workflow_dispatch"]) + expect(runLanes(event), event).toBe("linux=true\nmacos=true\neffects=true\nbootstrap=true\n"); + expect(workflow.on.workflow_dispatch.inputs["macos_suite"]).toEqual({ + description: "The test suite to run on macOS", + type: "choice", + options: ["full", "darwin"], + default: "full", + }); +}); + +test("every job on a macOS runner is in the macos lane", () => { + const macosJobs = Object.entries(workflow.jobs).filter(([, job]) => + [job["runs-on"], ...(job.strategy?.matrix?.include ?? []).map((entry) => entry.runner)].some( + (runner) => runner?.startsWith("macos"), + ), + ); + expect(macosJobs.map(([name]) => name).sort()).toEqual([ + "llvm_artifacts_macos_arm64", + "llvm_artifacts_macos_x64", + "mobile_runtime_packs_ios", + "native_macos", + "tests_macos", + ]); + for (const [name, job] of macosJobs) + expect(job.if, name).toContain("needs.lanes.outputs.macos == 'true'"); }); diff --git a/tests/harness/ci-test-plan.test.ts b/tests/harness/ci-test-plan.test.ts index 2343407df..34d61346f 100644 --- a/tests/harness/ci-test-plan.test.ts +++ b/tests/harness/ci-test-plan.test.ts @@ -201,12 +201,12 @@ test.for([1, 2, 3])("the darwin suite retains every darwin file with %i shards", /** Expands the ${{ }} expressions of a workflow value for one event: string * literals, ==, !=, !, &&, || and fromJSON, with matrix.shard left as * "{shard}". A value that is a single expression returns its result. */ -function expand(value: string, context: { event: string; macosFull: boolean | null }): unknown { +function expand(value: string, context: { event: string; macosSuite: string | null }): unknown { const evaluate = (expression: string): unknown => new Function( `return (${expression .replaceAll("github.event_name", JSON.stringify(context.event)) - .replaceAll("inputs.macos_full", JSON.stringify(context.macosFull)) + .replaceAll("inputs.macos_suite", JSON.stringify(context.macosSuite)) .replaceAll("fromJSON", "JSON.parse")});`, )(); const single = /^\$\{\{(.*)\}\}$/s.exec(value); @@ -240,18 +240,17 @@ test("pull requests run the full suite on Linux and the darwin suite on macOS", ).toBe("native-linux-x64-gnu"); expect(macos["runs-on"]).toBe("macos-15"); expect(macos.needs).toContain("native_macos"); - // Pull requests, pushes to main and plain manual runs run the darwin suite in - // three slices; the nightly sweep and manual runs with macos_full run the + // Pull requests and manual runs with macos_suite set to darwin run the darwin + // suite in three slices; the nightly sweep and other manual runs run the // complete suite in ten. - for (const [event, macosFull, full] of [ + for (const [event, macosSuite, full] of [ ["pull_request", null, false], - ["push", null, false], - ["workflow_dispatch", false, false], - ["workflow_dispatch", true, true], + ["workflow_dispatch", "darwin", false], + ["workflow_dispatch", "full", true], ["schedule", null, true], ] as const) { - const context = { event, macosFull }; - const label = `${event} ${macosFull}`; + const context = { event, macosSuite }; + const label = `${event} ${macosSuite}`; expect(expand(String(macos.strategy.matrix.shard), context), label).toEqual( full ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] : [1, 2, 3], ); diff --git a/tests/harness/corpus-costs/6470-any-destructuring.ts.json b/tests/harness/corpus-costs/6470-any-destructuring.ts.json new file mode 100644 index 000000000..101a112d4 --- /dev/null +++ b/tests/harness/corpus-costs/6470-any-destructuring.ts.json @@ -0,0 +1 @@ +{ "plain": 2678, "san": 3175 } diff --git a/tests/harness/corpus-costs/6471-any-method-chains.ts.json b/tests/harness/corpus-costs/6471-any-method-chains.ts.json new file mode 100644 index 000000000..13db76f84 --- /dev/null +++ b/tests/harness/corpus-costs/6471-any-method-chains.ts.json @@ -0,0 +1 @@ +{ "plain": 1526, "san": 1821 } diff --git a/tests/harness/corpus-costs/6472-namespace-destructuring/main.ts.json b/tests/harness/corpus-costs/6472-namespace-destructuring/main.ts.json new file mode 100644 index 000000000..a296c0da0 --- /dev/null +++ b/tests/harness/corpus-costs/6472-namespace-destructuring/main.ts.json @@ -0,0 +1 @@ +{ "plain": 1728, "san": 1731 } diff --git a/tests/harness/corpus-costs/6473-any-parameter-values.ts.json b/tests/harness/corpus-costs/6473-any-parameter-values.ts.json new file mode 100644 index 000000000..cb6a6fb42 --- /dev/null +++ b/tests/harness/corpus-costs/6473-any-parameter-values.ts.json @@ -0,0 +1 @@ +{ "plain": 1588, "san": 1779 } diff --git a/tests/harness/corpus-costs/6474-async-any-callbacks/main.ts.json b/tests/harness/corpus-costs/6474-async-any-callbacks/main.ts.json new file mode 100644 index 000000000..7c0aa7365 --- /dev/null +++ b/tests/harness/corpus-costs/6474-async-any-callbacks/main.ts.json @@ -0,0 +1 @@ +{ "plain": 1691, "san": 1787 } diff --git a/tests/harness/corpus-costs/6475-js-promise-resolve-adoption/main.ts.json b/tests/harness/corpus-costs/6475-js-promise-resolve-adoption/main.ts.json new file mode 100644 index 000000000..e1cfd6357 --- /dev/null +++ b/tests/harness/corpus-costs/6475-js-promise-resolve-adoption/main.ts.json @@ -0,0 +1 @@ +{ "plain": 1592, "san": 1653 } diff --git a/tests/harness/corpus-costs/6490-js-named-promise-executor/main.ts.json b/tests/harness/corpus-costs/6490-js-named-promise-executor/main.ts.json new file mode 100644 index 000000000..f5d08389a --- /dev/null +++ b/tests/harness/corpus-costs/6490-js-named-promise-executor/main.ts.json @@ -0,0 +1 @@ +{ "plain": 1658, "san": 1832 } diff --git a/tests/harness/corpus-costs/6491-catch-any-bindings.ts.json b/tests/harness/corpus-costs/6491-catch-any-bindings.ts.json new file mode 100644 index 000000000..644275755 --- /dev/null +++ b/tests/harness/corpus-costs/6491-catch-any-bindings.ts.json @@ -0,0 +1 @@ +{ "plain": 1509, "san": 1704 } diff --git a/tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json b/tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json new file mode 100644 index 000000000..e873baf78 --- /dev/null +++ b/tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json @@ -0,0 +1 @@ +{ "plain": 2750, "san": 3200 } diff --git a/tests/harness/corpus-costs/761-any-objects.ts.json b/tests/harness/corpus-costs/761-any-objects.ts.json new file mode 100644 index 000000000..50113af31 --- /dev/null +++ b/tests/harness/corpus-costs/761-any-objects.ts.json @@ -0,0 +1 @@ +{ "plain": 1524, "san": 1822 } diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index 168fdb6bd..f5f3fdeeb 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -91,13 +91,13 @@ const KNOWN: Record = { "node24-web-stream-encoding": { run: "SC1090" }, "node24-web-stream-errors": { run: "SC1090" }, "node24-web-stream-lifecycle": { run: "SC1090" }, - "node24-web-stream-pipe-abort": { run: "SC1090" }, + "node24-web-stream-pipe-abort": { run: "SC2020" }, "node24-web-stream-pipe-errors": { run: "SC1090" }, "node24-web-stream-pipes": { run: "SC1090" }, "node24-web-stream-queues": { run: "SC1090" }, - "node24-web-stream-readable-lifecycle": { run: "SC1090" }, + "node24-web-stream-readable-lifecycle": { run: "SC2020" }, "node24-web-stream-readers": { run: "SC1090" }, - "node24-web-stream-tee": { run: "SC1090" }, + "node24-web-stream-tee": { run: "SC2020" }, "node24-web-stream-transforms": { run: "SC1090" }, "package-imports": { build: "SC2013" }, "promise-bridge": { build: "SC1090" }, diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index 1e1905368..b7c30a910 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -205,6 +205,24 @@ test("node-types: events.once over typed EventEmitter listeners under @types/nod expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: events.once with a signal over streams, raced, under @types/node", async () => { + const outDir = outDirFor("events-once-signal-race"); + const entry = join(nodeTypesDir, "events-once-signal-race.ts"); + const result = await compile(entry, { + outPath: join(outDir, "events-once-signal-race"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: socket.address and resetAndDestroy under @types/node", async () => { const outDir = outDirFor("net-reset-address"); const entry = join(nodeTypesDir, "net-reset-address.ts"); diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index f924845f1..83cc0d2fd 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -31,6 +31,7 @@ const features: NativeLinkFeatures = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/tests/harness/self-hosting-runtime-features.test.ts b/tests/harness/self-hosting-runtime-features.test.ts index 8d65d72cd..b2f7e3062 100644 --- a/tests/harness/self-hosting-runtime-features.test.ts +++ b/tests/harness/self-hosting-runtime-features.test.ts @@ -51,6 +51,7 @@ const featureCalls: readonly [string, IrLibFn][] = [ ["searchParams", "sp.new"], ["qs", "qs.parse"], ["dynBuiltinClass", "dyn.builtinClass"], + ["dynBuiltinStream", "dyn.builtinStreamClass"], ["parseArgs", "util.parseArgs"], ["parseArgs", "util.isDeepStrictEqual"], ["parseArgs", "util.styleText"], diff --git a/tests/harness/self-hosting-runtime-pack.test.ts b/tests/harness/self-hosting-runtime-pack.test.ts index f8a280ef4..2b9e51969 100644 --- a/tests/harness/self-hosting-runtime-pack.test.ts +++ b/tests/harness/self-hosting-runtime-pack.test.ts @@ -32,6 +32,7 @@ const base: NativeLinkFeatures = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false,