mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Merge main
This commit is contained in:
+46
-28
@@ -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
@@ -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[]>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]!)
|
||||
) {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user