Skip untaken global-detection arms, and fix the shim test checks

- An if statement whose condition is a global-detection test
  (static-typeof.ts) lowers only its taken arm, unless the untaken arm
  declares a var or function binding. Code Node never runs no longer
  counts as a deferred site, so global-detection-typeof.cjs is fully
  static.
- The resolver parity sweep skips specifiers the package-shim registry
  serves: they resolve to the built-in module by design.
- undici-shim-types.test.ts joins the TypeScript 5 import allowlist.
- node-types-timers uses a 200 ms timer, so the immediate runs first on
  a loaded machine as well.
This commit is contained in:
Chris Tate
2026-10-20 08:52:39 -05:00
parent 10e39b8083
commit 5632decbb9
5 changed files with 49 additions and 2 deletions
@@ -3,6 +3,7 @@ import {
storedMatchAllRowsReadByElements,
} from "./containers/match-row-uses.js";
import { dynUndefinedExpr, nodeThrowExpr, numLit, varRef } from "../../ir/build.js";
import { declaresHoistedBinding, staticCondition } from "./static-typeof.js";
import { lowerCollectionSpread } from "./containers/collection-methods.js";
import { lowerEventObjectPropertyWrite } from "./lower-event-target.js";
import { lowerDynObjectLiteral } from "./expressions/object-literals.js";
@@ -1501,6 +1502,20 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt
}
if (ts.isIfStatement(stmt)) {
const cond = lowerer.lowerCondition(stmt.expression);
// A global-detection condition (static-typeof.ts) folds; its untaken
// arm is code Node never runs and does not lower, unless it declares a
// hoisted binding.
const decided = staticCondition(lowerer, stmt.expression);
if (decided !== null) {
const taken = decided ? stmt.thenStatement : stmt.elseStatement;
const untaken = decided ? stmt.elseStatement : stmt.thenStatement;
if (untaken === undefined || !declaresHoistedBinding(untaken)) {
const arm = taken === undefined ? [] : lowerer.lowerScopedBlock(taken);
return decided
? { kind: "if", cond, then: arm, else_: null, loc: locOf(stmt) }
: { kind: "if", cond, then: [], else_: arm, loc: locOf(stmt) };
}
}
// Aliased-typeof narrows (ms's `var type = typeof val` guard): the
// then-branch lowers under what a TRUE condition proves, the
// else-branch under a FALSE one — the var/let alias narrowing the
@@ -98,3 +98,28 @@ export function staticallySelected(lowerer: Lowerer, expr: ts.Expression): ts.Ex
e = taken ? e.whenTrue : e.whenFalse;
}
}
/** True when `stmt` declares a binding that is visible outside it: a `var`
* or a function declaration, outside nested functions. Such an arm still
* lowers when its condition is decided, for the hoisted binding. */
export function declaresHoistedBinding(stmt: ts.Statement): boolean {
let found = false;
const visit = (n: ts.Node): void => {
if (found) return;
if (ts.isFunctionDeclaration(n)) {
found = true;
return;
}
if (ts.isFunctionLike(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n)) return;
if (
ts.isVariableDeclarationList(n) &&
(n.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) === 0
) {
found = true;
return;
}
n.forEachChild(visit);
};
visit(stmt);
return found;
}
@@ -22,6 +22,7 @@ import {
resolveTypeDirective,
setProjectPathMappings,
} from "../../src/frontend/resolve.js";
import { packageShimFor } from "../../src/frontend/package-shims.js";
import { isRelativeSpecifier } from "../../src/frontend/workspace-registry.js";
import { options5 } from "./harness.js";
@@ -138,6 +139,10 @@ test("relative and bare specifiers across the whole test tree resolve identicall
// the resolver follows Node — corpus 2124 pins the behavior
// differentially, so parity has nothing to check here.
if (spec.startsWith("#/")) continue;
// A package scriptc implements itself (package-shims.ts) resolves
// to its built-in module by design; 5.9.3 answers the installed
// package.
if (packageShimFor(file, spec) !== null) continue;
bare++;
// PROJECT imports (#alias / self-name): ts5's NON-external answers
// for bare specifiers are exactly the package.json-mediated
+1
View File
@@ -30,6 +30,7 @@ const ALLOWED_TYPESCRIPT5_IMPORTS = new Set([
"packages/compiler/src/library/semantic-source.ts",
"packages/compiler/test/ts7/harness.ts",
"packages/compiler/test/ts7/resolver-parity.test.ts",
"packages/compiler/test/undici-shim-types.test.ts",
]);
function sourceFilesUnder(dir) {
+3 -2
View File
@@ -52,8 +52,9 @@ const program = `class Holder {
timer: NodeJS.Timeout | null = null;
}
const holder = new Holder();
holder.timer = setTimeout(() => console.log("never"), 50);
const kept = setTimeout(() => console.log("fired", holder.timer === null), 10);
holder.timer = setTimeout(() => console.log("never"), 1000);
// Long enough that the immediate runs first even on a loaded machine.
const kept = setTimeout(() => console.log("fired", holder.timer === null), 200);
kept.unref().ref();
kept.refresh();
console.log("hasRef", kept.hasRef());