Skip split require calls by identity and climb parentheses in early-read checks

This commit is contained in:
Chris Tate
2026-10-23 19:05:58 -05:00
parent 02597fa3e8
commit bf68b3860c
6 changed files with 39 additions and 11 deletions
+13 -8
View File
@@ -1563,6 +1563,14 @@ function splitRequireOfStatement7(
return null;
}
/** The top-level statement of a split require binding's assignment, from
* its require call (`m = require("./x")` or `m = (require("./x"))`). */
function splitRequireStatementOf7(call: ts.CallExpression): ts.Node | undefined {
let node: ts.Node = call;
while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent;
return node.parent?.parent;
}
/** The require call of a split require binding's declaration, or null. */
function splitRequireCallOf7(
program: ts.Program,
@@ -1877,7 +1885,7 @@ function requireEarlyReference7(
const sf = ts.getSourceFileOfNode(decl);
// A split require binding is bound at its assignment statement.
const splitCall = splitRequireCallOf7(program, decl);
const stmt = splitCall !== null ? splitCall.parent?.parent : decl.parent?.parent;
const stmt = splitCall !== null ? splitRequireStatementOf7(splitCall) : decl.parent?.parent;
if (
stmt !== undefined &&
(ts.isVariableStatement(stmt) || splitCall !== null) &&
@@ -6184,15 +6192,12 @@ function preflight7(load: LoadResult): {
// counts, and targets outside the compiled graph add nothing here:
// their use sites keep their existing lowering fences.
if (jsRequireFile && !isNodeEsmFile7(sf, program)) {
// A split require binding's assignment is a statement-level require
// edge (requiresOf7 above), parenthesized or not.
const splitCalls = new Set(splitRequireBindings7(program, sf).values());
for (const call of inlineRequiresOf7(sf)) {
if (requireCallBindingKind7(program, call) !== "ambient") continue;
// A split require binding's assignment is a statement-level
// require edge (requiresOf7 above).
if (
call.parent?.parent !== undefined &&
splitRequireOfStatement7(program, call.parent.parent as ts.Statement) !== null
)
continue;
if (splitCalls.has(call)) continue;
const spec = requireSpecOf7(call)!;
if (load.externalTypes.has(spec) || canonicalBuiltinModule(spec) !== null) continue;
const lazy = inDeferredPosition7(call);
@@ -12,6 +12,16 @@
"end": 415
},
"milestone": "later"
},
{
"code": "SC1013",
"message": "require() binding 'wrapped' assigned below code that can reach it (Node reads undefined before the assignment runs, which is not modeled — move the assignment above the code that can run first) is not supported yet",
"loc": {
"file": "<repo>/tests/diagnostics/split-cjs-require-early/main.js",
"start": 530,
"end": 549
},
"milestone": "later"
}
]
}
@@ -1,7 +1,8 @@
// Split require bindings (`var m; m = require("./x.cjs")`) that close a
// CommonJS cycle: each module publishes its exports before its require, and
// reads the other module's binding only in functions that run after both
// modules finish, the cycle shape the const form compiles too.
// modules finish, the cycle shape the const form compiles too. One
// assignment parenthesizes its require.
console.log("main: start");
var even, odd;
even = require("./even.cjs");
+1 -1
View File
@@ -8,5 +8,5 @@ exports.isOdd = function (n) {
exports.calls = function () {
return calls;
};
even = require("./even.cjs");
even = (require("./even.cjs"));
console.log("odd: loaded");
@@ -9,6 +9,11 @@ let lib;
console.log('boot', helper());
lib = require('./lib.js');
// The same with the require parenthesized.
let wrapped;
console.log('peek', wrapped === undefined);
wrapped = (require('./lib.js'));
function helper() {
return lib === undefined ? 'not loaded' : lib.greet('late');
}
@@ -3,4 +3,11 @@ split-cjs-require-early/main.js:10:7 - error SC1013: require() binding 'lib' ass
9 | console.log('boot', helper());
10 | lib = require('./lib.js');
| ^~~~~~~~~~~~~~~~~~~
11 |
11 |
split-cjs-require-early/main.js:15:12 - error SC1013: require() binding 'wrapped' assigned below code that can reach it (Node reads undefined before the assignment runs, which is not modeled — move the assignment above the code that can run first) is not supported yet
14 | console.log('peek', wrapped === undefined);
15 | wrapped = (require('./lib.js'));
| ^~~~~~~~~~~~~~~~~~~
16 |