mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-10 01:56:15 +08:00
Keep the CommonJS caches on their owners and recount 6110's fences
The exports-assignment cache moves from a module-level WeakMap onto the Lowerer (cjsExportsAssignments), and the inline require() calls that close a require cycle move from a module-level WeakSet into preflight's result: checkPreflight stores them on the LoadResult, and lowering receives them with the other preflight facts (LowerOptions.inlineRequireCycleCalls). 6110's call and apply wrappers now compile `root === module.exports` and `typeof module.exports`, which print what Node prints when the wrappers' untaken branches run. The class base's `console.log(module.exports)` is the one statement left as a runtime fence, and its body is lowered once, so the program's deferred-fence count is 1.
This commit is contained in:
@@ -148,17 +148,16 @@ function truthinessOnly(node: ts.Expression): boolean {
|
||||
}
|
||||
|
||||
/** Which of the module's exports bindings the file ever assigns (any
|
||||
* nesting, compound operators included). */
|
||||
const assignmentsCache = new WeakMap<ts.SourceFile, { exports: boolean; moduleExports: boolean }>();
|
||||
|
||||
* nesting, compound operators included), cached per file on the lowerer
|
||||
* (Lowerer.cjsExportsAssignments). */
|
||||
function exportsAssignments(
|
||||
lowerer: Lowerer,
|
||||
sf: ts.SourceFile,
|
||||
): { exports: boolean; moduleExports: boolean } {
|
||||
const cached = assignmentsCache.get(sf);
|
||||
const cached = lowerer.cjsExportsAssignments.get(sf);
|
||||
if (cached !== undefined) return cached;
|
||||
const out = { exports: false, moduleExports: false };
|
||||
assignmentsCache.set(sf, out);
|
||||
lowerer.cjsExportsAssignments.set(sf, out);
|
||||
ts.walkPreorder(sf, (node) => {
|
||||
// A fresh `exports = module.exports = {}` reset leaves both bindings
|
||||
// naming one (new, empty) exports object for the rest of the module.
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
cjsExportTargetLiteral,
|
||||
isCjsJsFile,
|
||||
isJsSourceFile,
|
||||
isInlineRequireCycleCall,
|
||||
isModuleExportsAccess,
|
||||
locOf,
|
||||
npmStaticDepSf7,
|
||||
@@ -109,7 +108,7 @@ export function inlineNamespaceRequireOf(
|
||||
/** `value` evaluated after the required module's run-once %init (and its
|
||||
* module-registry link) at the require's own position. */
|
||||
export function withRequireInit(lowerer: Lowerer, req: InlineRequire, value: IrExpr): IrExpr {
|
||||
if (isInlineRequireCycleCall(req.call)) {
|
||||
if (lowerer.inlineRequireCycleCalls.has(req.call)) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
req.call,
|
||||
|
||||
@@ -753,6 +753,8 @@ export interface LowerOptions {
|
||||
* resolution walk and named-import link checks): the program
|
||||
* compiles to that startup crash. */
|
||||
startupCrash?: StartupCrash | null;
|
||||
/** LoadResult.inlineRequireCycleCalls. */
|
||||
inlineRequireCycleCalls?: ReadonlySet<ts.Node> | undefined;
|
||||
/** LIBRARY mode's reachability roots: the profile-mapped exports of the
|
||||
* entry module. Executable builds root at the entry's top level alone
|
||||
* (an unreferenced export dead-strips); a library's exports are called from
|
||||
@@ -845,6 +847,8 @@ export interface LowererMode {
|
||||
* module init — Node refuses the whole graph before anything evaluates,
|
||||
* so nothing runs. */
|
||||
startupCrash?: StartupCrash | null;
|
||||
/** LoadResult.inlineRequireCycleCalls. */
|
||||
inlineRequireCycleCalls?: ReadonlySet<ts.Node> | undefined;
|
||||
/** The build's outbound native FFI declarations. */
|
||||
ffiImports?: readonly IrFfiImport[];
|
||||
/** LowerOptions.libraryCallbacks (see there). */
|
||||
@@ -958,6 +962,7 @@ export function lowerToIr(
|
||||
projectCheckJs: options.projectCheckJs ?? false,
|
||||
targetPlatform,
|
||||
startupCrash,
|
||||
inlineRequireCycleCalls: options.inlineRequireCycleCalls,
|
||||
ffiImports,
|
||||
libraryCallbacks,
|
||||
forkTargets,
|
||||
@@ -981,6 +986,7 @@ export function lowerToIr(
|
||||
projectCheckJs: options.projectCheckJs ?? false,
|
||||
targetPlatform,
|
||||
startupCrash,
|
||||
inlineRequireCycleCalls: options.inlineRequireCycleCalls,
|
||||
ffiImports,
|
||||
libraryCallbacks,
|
||||
forkTargets,
|
||||
@@ -1011,6 +1017,7 @@ export function lowerToIr(
|
||||
reachable,
|
||||
targetPlatform,
|
||||
startupCrash,
|
||||
inlineRequireCycleCalls: options.inlineRequireCycleCalls,
|
||||
ffiImports,
|
||||
libraryCallbacks,
|
||||
forkTargets,
|
||||
@@ -1069,6 +1076,7 @@ export function lowerToIr(
|
||||
reachable,
|
||||
remainder: true,
|
||||
alreadyFlushed: resultLowerer.flushedSymbols,
|
||||
inlineRequireCycleCalls: options.inlineRequireCycleCalls,
|
||||
targetPlatform,
|
||||
ffiImports,
|
||||
libraryCallbacks,
|
||||
@@ -1628,6 +1636,15 @@ export class Lowerer {
|
||||
readonly checker: ts.TypeChecker;
|
||||
readonly diags: ScrDiagnostic[] = [];
|
||||
readonly fnSigsBySymbol = new Map<ts.Symbol, FnSig>();
|
||||
/** Per file: whether it assigns `exports` and `module.exports`
|
||||
* (cjs-exports-object.ts). */
|
||||
readonly cjsExportsAssignments = new Map<
|
||||
ts.SourceFile,
|
||||
{ exports: boolean; moduleExports: boolean }
|
||||
>();
|
||||
/** Inline require() calls whose module edge closes a require cycle
|
||||
* (LoadResult.inlineRequireCycleCalls). */
|
||||
readonly inlineRequireCycleCalls: ReadonlySet<ts.Node>;
|
||||
/** Immutable callable aliases and compiler-generated adapters. The map
|
||||
* is symbol-keyed, so shadowing and cross-module imports keep the same
|
||||
* exact binding discipline as globals/functions. */
|
||||
@@ -2859,6 +2876,7 @@ export class Lowerer {
|
||||
this.alreadyFlushed = mode.alreadyFlushed ?? new Set();
|
||||
this.targetPlatform = mode.targetPlatform ?? process.platform;
|
||||
this.startupCrash = mode.startupCrash ?? null;
|
||||
this.inlineRequireCycleCalls = mode.inlineRequireCycleCalls ?? new Set();
|
||||
this.projectCheckJs = mode.projectCheckJs ?? false;
|
||||
this.ffiImports = mode.ffiImports ?? [];
|
||||
this.libraryCallbacks = mode.libraryCallbacks ?? false;
|
||||
|
||||
@@ -593,6 +593,7 @@ function loadFrontend(
|
||||
...opts,
|
||||
frontendServices: finalLoad.services,
|
||||
startupCrash: finalLoad.startupCrash ?? null,
|
||||
inlineRequireCycleCalls: finalLoad.inlineRequireCycleCalls,
|
||||
projectCheckJs: finalLoad.typeWorld?.projectCheckJs ?? false,
|
||||
externalTypes: finalLoad.externalTypes,
|
||||
externalTypeSpecifiersByFile: finalLoad.externalTypeSpecifiersByFile,
|
||||
|
||||
@@ -338,6 +338,12 @@ export interface LoadResult {
|
||||
* last. Empty until checkPreflight runs (and empty when preflight fails
|
||||
* structurally). */
|
||||
moduleOrder: ts.SourceFile[];
|
||||
/** Inline require() calls whose edge closes a require cycle. Node answers
|
||||
* them with the still-evaluating module's partial exports, which the
|
||||
* static export globals do not model; their lowering refuses them by name
|
||||
* at run time instead of refusing the whole program. Filled by
|
||||
* checkPreflight. */
|
||||
inlineRequireCycleCalls?: ReadonlySet<ts.Node>;
|
||||
/** Node's startup refusal, when the graph carries an import edge Node
|
||||
* rejects before ANY module evaluates — a RESOLUTION refusal (a bare
|
||||
* specifier nothing installed resolves, an exports/imports map that
|
||||
@@ -975,9 +981,10 @@ export function loadProgram(
|
||||
* preflight diagnostics. Preserve the caller's complete load shape when
|
||||
* mutating it, including its owned services and disposal callback. */
|
||||
export function checkPreflight<T extends LoadResult>(load: T): ScrDiagnostic[] {
|
||||
const { diags, moduleOrder, startupCrash } = preflight7(load);
|
||||
const { diags, moduleOrder, startupCrash, inlineRequireCycleCalls } = preflight7(load);
|
||||
load.moduleOrder = moduleOrder;
|
||||
load.startupCrash = startupCrash;
|
||||
load.inlineRequireCycleCalls = inlineRequireCycleCalls;
|
||||
return diags;
|
||||
}
|
||||
|
||||
@@ -2447,17 +2454,6 @@ export interface CycleEdge {
|
||||
call?: ts.CallExpression;
|
||||
}
|
||||
|
||||
/** Inline require() calls whose edge closes a require cycle. Node answers
|
||||
* them with the still-evaluating module's partial exports, which the static
|
||||
* export globals do not model; their lowering refuses them by name at run
|
||||
* time instead of refusing the whole program (the inline call kept its
|
||||
* runtime fence before it became a module edge). */
|
||||
const inlineRequireCycleCalls = new WeakSet<ts.Node>();
|
||||
|
||||
export function isInlineRequireCycleCall(call: ts.Node): boolean {
|
||||
return inlineRequireCycleCalls.has(call);
|
||||
}
|
||||
|
||||
/** True when the reference at `node` can only evaluate AFTER the module
|
||||
* graph finished initializing: inside a function-like BODY, inside a
|
||||
* parameter (default initializers evaluate at call time), or inside an
|
||||
@@ -4133,6 +4129,7 @@ function preflight7(load: LoadResult): {
|
||||
diags: ScrDiagnostic[];
|
||||
moduleOrder: ts.SourceFile[];
|
||||
startupCrash: StartupCrash | null;
|
||||
inlineRequireCycleCalls: ReadonlySet<ts.Node>;
|
||||
} {
|
||||
const { program, entry } = load;
|
||||
const diags: ScrDiagnostic[] = [...load.configDiags];
|
||||
@@ -5118,6 +5115,7 @@ function preflight7(load: LoadResult): {
|
||||
return all.some((e) => e.call !== undefined) ? all.filter((e) => e.call === undefined) : all;
|
||||
});
|
||||
const order: ts.SourceFile[] = [];
|
||||
const inlineRequireCycleCalls = new Set<ts.Node>();
|
||||
const state = new Map<ts.SourceFile, "visiting" | "done">();
|
||||
const stack: string[] = [];
|
||||
const visit = (sf: ts.SourceFile): void => {
|
||||
@@ -5214,7 +5212,12 @@ function preflight7(load: LoadResult): {
|
||||
esmNamedImportLinkCheck(program, entry, order, diags),
|
||||
);
|
||||
|
||||
return { diags, moduleOrder: order, startupCrash: resolveCrash ?? linkCrash };
|
||||
return {
|
||||
diags,
|
||||
moduleOrder: order,
|
||||
startupCrash: resolveCrash ?? linkCrash,
|
||||
inlineRequireCycleCalls,
|
||||
};
|
||||
}
|
||||
|
||||
/* ── the CJS named-import link check ─────────────────────────────────────
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// @deferred-fences: 9
|
||||
// @deferred-fences: 1
|
||||
// Function expressions lowered speculatively — the `.call(this)` and
|
||||
// `.apply` receivers of CommonJS wrappers, and a computed class base — keep
|
||||
// every statement of their bodies. A statement with no static lowering
|
||||
// (here `module.exports` read as a value) compiles to its runtime fence
|
||||
// instead of vanishing; these never run, so the binary matches Node.
|
||||
// Coverage counts the three fenced statements nine times: each lowering of
|
||||
// a wrapper body counts its statements, and the `.call` and `.apply`
|
||||
// wrapper bodies are each lowered four times.
|
||||
// every statement of their bodies. The wrappers' `root === module.exports`
|
||||
// and `typeof module.exports` compile; a statement with no static lowering
|
||||
// (here `module.exports` passed as a value in the class base) compiles to
|
||||
// its runtime fence instead of vanishing. It never runs, so the binary
|
||||
// matches Node. Coverage counts that one fenced statement once: the class
|
||||
// base's body is lowered once.
|
||||
const verbose = process.argv.length > 99;
|
||||
(function () {
|
||||
var root = this;
|
||||
|
||||
Reference in New Issue
Block a user