Merge main

This commit is contained in:
Chris Tate
2026-10-20 14:27:36 -05:00
313 changed files with 7963 additions and 2207 deletions
+46 -28
View File
@@ -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."
@@ -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.
@@ -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.
+1 -1
View File
@@ -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<unknown[]>;
+1 -1
View File
@@ -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"
},
@@ -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,
@@ -43,6 +43,7 @@ export async function compilePackedLibrary(
searchParams: options.searchParams ?? false,
qs: false,
dynBuiltinClass: false,
dynBuiltinStream: false,
parseArgs: false,
stream: false,
net: false,
+33
View File
@@ -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<string, number> = {
"%Readable": 2,
"%Writable": 3,
"%Duplex": 4,
"%Transform": 5,
"%PassThrough": 6,
};
return kinds[className] ?? null;
}
export class LlDyn {
private readonly dynMatchers = new Map<string, string>();
private readonly dynBuilders = new Map<string, string>();
@@ -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();
+16 -3
View File
@@ -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<string, LlStreamTypedRefAdapter>();
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<string, string>();
readonly dynPromiseAdapters = new Map<string, string>();
@@ -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 {
@@ -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`,
`}`,
``,
@@ -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) {
@@ -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":
@@ -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();
@@ -1464,6 +1464,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"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<string>([
"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.
@@ -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;
@@ -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;
@@ -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.
@@ -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,
@@ -108,6 +108,7 @@ const features = {
searchParams: false,
qs: false,
dynBuiltinClass: false,
dynBuiltinStream: false,
parseArgs: false,
stream: false,
net: false,
@@ -45,6 +45,7 @@ const BASE: NativeLinkFeatures = {
searchParams: false,
qs: false,
dynBuiltinClass: false,
dynBuiltinStream: false,
parseArgs: false,
stream: false,
net: false,
@@ -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);
});
@@ -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<string> = 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<IrType["kind"]> = 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<string, { caller: IrFunction; args: IrExpr[] }[]>();
private readonly named: Set<string>;
private readonly inits = new Map<IrFunction, Map<string, IrExpr | null>>();
/** 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<string, boolean>();
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<string>();
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<string>): 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<string>): 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 `%<class>.<member>` convention of class
* methods, accessors and constructors for one of `classNames`. */
function isClassMember(name: string, classNames: ReadonlySet<string>): 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<string>();
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);
@@ -51,6 +51,7 @@ export async function warmNativeCaches(
searchParams: false,
qs: false,
dynBuiltinClass: false,
dynBuiltinStream: false,
parseArgs: false,
stream: false,
net: false,
@@ -46,6 +46,7 @@ const native: EarlyExecutableNativeFeatures = {
searchParams: false,
qs: false,
dynBuiltinClass: false,
dynBuiltinStream: false,
parseArgs: false,
stream: false,
net: false,
@@ -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",
+21 -22
View File
@@ -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)) {
@@ -130,7 +130,7 @@ function markerNamed(
function nameText(node: ts.Node | undefined): string {
if (node === undefined) return "<anonymous>";
if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node)) return node.text;
return node.getText();
return ts.getTextOfNode(node);
}
function contractDiag(
@@ -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;
@@ -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",
@@ -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) {
@@ -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]!)
) {
+12 -8
View File
@@ -115,7 +115,7 @@ const CONTRACT_GLOBAL_TYPES = new Set(["Array", "ReadonlyArray", "Uint8Array"]);
type TypeBindings = Map<ts.SourceFile, Set<string>>;
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
@@ -35,7 +35,7 @@ export function entryFunctionExports(entry: ts.SourceFile): Map<string, EntryExp
if (out.has(name) && stmt.body === undefined) continue; // overload signature
out.set(name, {
name,
loc: { file: entry.fileName, start: stmt.getStart(), end: stmt.end },
loc: { file: entry.fileName, start: ts.tokenStartOfNode(stmt), end: stmt.end },
generic: (stmt.typeParameters?.nodes.length ?? 0) > 0,
async: mods?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true,
generator: stmt.asteriskToken !== undefined,
@@ -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;
}
@@ -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)),
@@ -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;
@@ -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: [],
@@ -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",
@@ -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) {
@@ -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,
@@ -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,
@@ -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) => {
@@ -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;
@@ -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)) {
@@ -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 };
}
@@ -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,
};
@@ -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;
}
@@ -27,9 +27,10 @@ const ASSERTION_ERROR_PROPERTIES: ReadonlySet<string> = 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)),
)
);
}
@@ -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);
@@ -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
@@ -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)))
);
}
@@ -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),
@@ -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;
}
@@ -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);
}
@@ -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;
}
@@ -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
);
}
@@ -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,
@@ -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. */
@@ -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,
@@ -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<never> 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<never> entry (the deadline idiom: `new Promise<never>((_,
// 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<never> entry (the deadline idiom: `new Promise<never>((_,
// 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
@@ -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";
});
@@ -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)),
);
}
@@ -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);
@@ -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
@@ -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 {
@@ -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 };
@@ -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)
);
}
@@ -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))
@@ -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));
}
@@ -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 ||
@@ -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;
@@ -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 &&
@@ -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<string, ts.Statement>();
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;
@@ -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);
});
@@ -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;
@@ -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;
@@ -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) => {
@@ -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;
@@ -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<ts.Symbol>();
const writesNames = (written: Set<string> | "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;
@@ -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)
)
@@ -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 (
@@ -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;
@@ -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;
@@ -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 &&
@@ -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;
@@ -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({
@@ -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,
@@ -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)
@@ -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,
@@ -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") {
@@ -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];
@@ -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);
@@ -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<union-of-signatures>, 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);
@@ -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,
@@ -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<number>();
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;
@@ -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)
);
}
@@ -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,
);
}
@@ -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) ||
@@ -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;
}
@@ -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,

Some files were not shown because too many files have changed in this diff Show More