Compile the remaining corpus programs with the native compiler (#1418)

This commit is contained in:
Chris Tate
2026-10-20 20:29:41 -05:00
committed by GitHub
13 changed files with 294 additions and 20 deletions
+1 -4
View File
@@ -889,10 +889,7 @@ class Int32Contracts {
const value = s.kind === "varDecl" ? s.init : s.value;
if (note && value && !int32(value))
fail(note, value, value.loc, "the value stored into");
} else if (
s.kind === "forOf" ||
(s.kind === "tryCatch" && s.catchLocalId !== undefined)
) {
} else if (s.kind === "forOf" || (s.kind === "tryCatch" && s.catchLocalId !== null)) {
const id = s.kind === "forOf" ? s.localId : s.catchLocalId!;
const note = localNote(id);
if (note)
@@ -1058,9 +1058,21 @@ export function lambdaSignature(
// Union-inferred returns adopt too (a mixed-return body inferring a
// SUB-union of the slot's union — adopting is a no-op when the two
// A synchronous object-literal method adopts its slot's return the same
// way: its slot is the property of that name on the literal's
// contextual type. A literal under `satisfies` keeps its own type (the
// constraint is only checked), so its methods keep their inferred
// returns: callers of the value see those, not the constraint's.
const literalMethod =
ts.isMethodDeclaration(node) &&
ts.isObjectLiteralExpression(node.parent) &&
!node.asteriskToken &&
node.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) !== true &&
!isSatisfiesOperand(node.parent);
if (
(ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&
(!isAsyncLike || ret.kind === "promise")
((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&
(!isAsyncLike || ret.kind === "promise")) ||
literalMethod
) {
// The slot's signature: the contextual type stripped of its nullish
// parts (an OPTIONAL callback field's context is the whole
@@ -1068,7 +1080,10 @@ export function lambdaSignature(
// declared in USER code: stdlib callback slots (flatMap's
// `U | readonly U[]`, sort comparators) have intrinsic lowerings
// that inspect the INFERRED type, so they must not widen.
const ctxType = lowerer.checker.getContextualType(node);
const ctxType =
literalMethod && ts.isMethodDeclaration(node) && node.parent
? objectLiteralMethodSlot(lowerer, node, node.parent)
: lowerer.checker.getContextualType(node);
const ctxSigs = ctxType
? lowerer.checker.getCallSignatures(lowerer.checker.getNonNullableType(ctxType))
: [];
@@ -1136,6 +1151,7 @@ export function lambdaSignature(
const inferredLiteral =
literalSlot !== undefined && (literalType === null || literalType.kind === "dyn");
if (
!literalMethod &&
!isAsyncLike &&
!node.type &&
!inferredLiteral &&
@@ -1151,3 +1167,35 @@ export function lambdaSignature(
const funcType = funcTypeFromParamShapes(shapes, ret);
return { shapes, funcType };
}
/** Whether the object literal `literal` is the operand of a `satisfies`
* expression, directly or as a property value of literals that are. */
function isSatisfiesOperand(literal: ts.Node): boolean {
let n: ts.Node | undefined = literal;
while (n !== undefined) {
const parent: ts.Node | undefined = n.parent;
if (parent === undefined) return false;
if (ts.isParenthesizedExpression(parent)) n = parent;
else if (ts.isPropertyAssignment(parent) && parent.initializer === n) n = parent.parent;
else return ts.isSatisfiesExpression(parent) && parent.expression === n;
}
return false;
}
/** The type of the property an object-literal method fills on the
* literal's contextual type; undefined when the literal has no contextual
* type or the method has a computed name. */
function objectLiteralMethodSlot(
lowerer: Lowerer,
node: ts.MethodDeclaration,
literal: ts.Node,
): ts.Type | undefined {
if (!ts.isIdentifier(node.name) && !ts.isStringLiteral(node.name)) return undefined;
const literalType = lowerer.checker.getContextualType(literal);
if (!literalType) return undefined;
const property = lowerer.checker.getPropertyOfType(
lowerer.checker.getNonNullableType(literalType),
node.name.text,
);
return property ? lowerer.checker.getTypeOfSymbol(property) : undefined;
}
@@ -243,7 +243,8 @@ export class StringIndexBounds {
/** Every write of a block-scoped local or parameter, or null for any
* other binding (module imports, var, class members, ...). */
private binding(symbol: ts.Symbol): Binding | null {
if (this.bindings.has(symbol)) return this.bindings.get(symbol)!;
const cached = this.bindings.get(symbol);
if (cached !== undefined) return cached;
const result = this.scanBinding(symbol);
this.bindings.set(symbol, result);
return result;
+2 -1
View File
@@ -2121,7 +2121,8 @@ function stableCreateRequireBindingReason7(
decl: ts.VariableDeclaration,
): string | null {
const cache = program.analysis.createRequireReasons;
if (cache.has(decl)) return cache.get(decl)!;
const cached = cache.get(decl);
if (cached !== undefined) return cached;
const check = (): string | null => {
const stmt = decl.parent?.parent;
if (
@@ -21,9 +21,13 @@ import { dirname, join, resolve } from "node:path";
import { Ts7Api } from "./api.js";
import type { Ts7TimingInfo } from "./session-timing.js";
import type { Ts7FileSystem } from "./rpc-filesystem.js";
import type { CompilerOptions, Diagnostic, SourceFileMetadata } from "./semantic-types.js";
import type { Diagnostic, SourceFileMetadata } from "./semantic-types.js";
import type { SourceFile } from "./ast-types.js";
import type { Ts7SessionProject as Project, Ts7SessionSnapshot as Snapshot } from "./session.js";
import type {
Ts7ProjectCompilerOptions,
Ts7SessionProject as Project,
Ts7SessionSnapshot as Snapshot,
} from "./session.js";
import { CheckerFacade } from "./checker.js";
import {
moduleDetectionKindName,
@@ -45,8 +49,9 @@ import { packageShimDeclarationText } from "../package-shims.js";
/** The compiler options our createProgram accepts: TS7's CompilerOptions
* shape (numeric enums for target/module/moduleResolution — the enums module
* generates them from the pinned SDK) with the 5.9.3 "lib.es2025.d.ts" lib
* spelling also accepted. */
export type Ts7CompilerOptions = CompilerOptions;
* spelling also accepted, plus the project options scriptc adopts that the
* TS7 schema no longer declares (Ts7ProjectCompilerOptions). */
export type Ts7CompilerOptions = Ts7ProjectCompilerOptions;
/* tsconfig JSON wants enum NAMES; the options object carries TS7's numeric
* enum values. Generated name lookups preserve the SDK's reverse mappings
@@ -288,7 +293,7 @@ export class Ts7Program {
private readonly configPath: string,
) {}
getCompilerOptions(): CompilerOptions {
getCompilerOptions(): Ts7CompilerOptions {
return this.project.program.getCompilerOptions();
}
+24 -6
View File
@@ -18,6 +18,24 @@ import type {
Ts7SourceMetadata,
} from "./session-schema.generated.js";
/** Project options tsgo still reports for a tsconfig but the TS 7 SDK
* schema no longer declares. scriptc adopts them (ADOPTED_OPTIONS), so the
* options a project reports are parsed with them declared: a native
* compiler keeps only declared fields when it parses the response. */
export interface Ts7ProjectCompilerOptions extends Ts7CompilerOptionsData {
alwaysStrict?: boolean;
esModuleInterop?: boolean;
allowSyntheticDefaultImports?: boolean;
}
interface Ts7ReportedProjectData extends Ts7ProjectData {
compilerOptions: Ts7ProjectCompilerOptions;
}
interface Ts7ReportedSnapshotData extends Ts7SnapshotData {
projects: Ts7ReportedProjectData[];
}
export interface Ts7FileChanges {
changed?: SemanticDocument[];
created?: SemanticDocument[];
@@ -104,7 +122,7 @@ export class Ts7Session {
updateSnapshot(params: Ts7Update = {}): Ts7SessionSnapshot {
const paths = this.initialize();
const data = parseSemanticJson<Ts7SnapshotData>(
const data = parseSemanticJson<Ts7ReportedSnapshotData>(
this.text("updateSnapshot", JSON.stringify(params)),
);
const snapshot = new Ts7SessionSnapshot(data, this, paths);
@@ -160,7 +178,7 @@ export class Ts7SessionSnapshot {
private disposed = false;
constructor(
data: Ts7SnapshotData,
data: Ts7ReportedSnapshotData,
private readonly session: Ts7Session,
readonly paths: Ts7Paths,
) {
@@ -189,7 +207,7 @@ export class Ts7SessionSnapshot {
}
getDefaultProjectForFile(file: SemanticDocument): Ts7SessionProject | undefined {
this.ensureActive();
const data = this.session.request<Ts7ProjectData | null>("getDefaultProjectForFile", {
const data = this.session.request<Ts7ReportedProjectData | null>("getDefaultProjectForFile", {
snapshot: this.id,
file,
});
@@ -215,12 +233,12 @@ export class Ts7SessionSnapshot {
export class Ts7SessionProject {
readonly id: string;
readonly configFileName: string;
readonly compilerOptions: Ts7CompilerOptionsData;
readonly compilerOptions: Ts7ProjectCompilerOptions;
readonly rootFiles: string[];
readonly program: Ts7SessionProgram;
readonly checker: SemanticChecker;
constructor(data: Ts7ProjectData, snapshot: Ts7SessionSnapshot, session: Ts7Session) {
constructor(data: Ts7ReportedProjectData, snapshot: Ts7SessionSnapshot, session: Ts7Session) {
this.id = data.id;
this.configFileName = data.configFileName;
this.compilerOptions = data.compilerOptions;
@@ -261,7 +279,7 @@ export class Ts7SessionProgram {
...(file === undefined ? {} : { file }),
};
}
getCompilerOptions(): Ts7CompilerOptionsData {
getCompilerOptions(): Ts7ProjectCompilerOptions {
this.ensureActive();
return this.project.compilerOptions;
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6022-width-lift-discriminant.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6024-object-literal-method-slot-return.ts"
],
"diags": []
}
@@ -0,0 +1,54 @@
// A record built in a map callback whose fields width-lift into several
// members of a discriminated union (a nested literal with a narrowed field,
// and a member that only needs `kind` and `loc`): its discriminant selects
// the member.
interface Loc {
start: number;
}
type Expr =
| { kind: "get"; obj: Expr; field: string; loc: Loc }
| { kind: "ref"; name: string; loc: Loc }
| { kind: "undef"; loc: Loc };
type Stmt =
| { kind: "set"; obj: Expr; field: string; value: Expr; loc: Loc }
| { kind: "break"; label?: string; loc: Loc }
| { kind: "return"; value: Expr | null; loc: Loc };
function showExpr(expr: Expr): string {
switch (expr.kind) {
case "get":
return `${showExpr(expr.obj)}.${expr.field}`;
case "ref":
return expr.name;
case "undef":
return "undefined";
}
}
function show(stmt: Stmt): string {
switch (stmt.kind) {
case "set":
return `${showExpr(stmt.obj)}.${stmt.field} = ${showExpr(stmt.value)}`;
case "break":
return `break${stmt.label === undefined ? "" : " " + stmt.label}`;
case "return":
return `return ${stmt.value === null ? "-" : showExpr(stmt.value)}`;
}
}
function copies(fields: string[], source: Expr & { kind: "ref" }, dst: Expr, loc: Loc): Stmt[] {
return fields.map((field) => ({
kind: "set",
obj: dst,
field,
value: { kind: "get", obj: source, field, loc },
loc,
}));
}
const loc: Loc = { start: 0 };
const out: Expr = { kind: "ref", name: "out", loc };
const span: Expr = { kind: "ref", name: "span", loc };
if (span.kind === "ref") console.log(copies(["lo", "hi"], span, out, loc).map(show).join("; "));
const stops: Stmt[] = ["a", "b"].map((label) => ({ kind: "break", label, loc }));
console.log(stops.map(show).join("; "));
@@ -0,0 +1,130 @@
// An object-literal method returns the type its slot declares. The literal
// statements below have their own inferred type, a union of records whose
// `init` fields are `Expr` or `null` and whose tryCatch member has fields
// the declared `Stmt` does not; the method still returns `Stmt[]`.
type Expr =
| { kind: "num"; value: number }
| { kind: "str"; text: string }
| { kind: "ref"; name: string };
type Stmt =
| { kind: "varDecl"; name: string; init: Expr | null }
| { kind: "exprStmt"; expr: Expr }
| { kind: "if"; cond: Expr; then: Stmt[]; else_: Stmt[] | null }
| { kind: "loop"; init: Stmt | null; body: Stmt[] }
| {
kind: "tryCatch";
tryBody: Stmt[];
catchBody: Stmt[] | null;
catchName: string | null;
finallyBody: Stmt[] | null;
};
interface Step {
label: string;
build(source: Expr, append: (value: Expr) => Stmt[]): Stmt[];
first(source: Expr): Stmt | Expr;
}
const ref = (name: string): Expr => ({ kind: "ref", name });
function arrayStep(label: string): Step {
return {
label,
build(source, append) {
const loop: Stmt = {
kind: "loop",
init: { kind: "varDecl", name: "cursor", init: { kind: "num", value: 0 } },
body: [...append(ref("value"))],
};
return [
{ kind: "varDecl", name: "array", init: source },
{ kind: "varDecl", name: "close", init: ref("flag") },
{ kind: "varDecl", name: "value", init: null },
{
kind: "tryCatch",
tryBody: [loop],
catchBody: null,
catchName: null,
finallyBody: [{ kind: "if", cond: ref("close"), then: [], else_: null }],
},
];
},
first(source) {
if (source.kind === "num") return { kind: "varDecl", name: "n", init: null };
return source;
},
};
}
function show(expr: Expr | null): string {
if (expr === null) return "null";
switch (expr.kind) {
case "num":
return String(expr.value);
case "str":
return JSON.stringify(expr.text);
case "ref":
return expr.name;
}
}
function print(stmts: Stmt[], depth: number): void {
const pad = " ".repeat(depth);
for (const s of stmts) {
switch (s.kind) {
case "varDecl":
console.log(`${pad}let ${s.name} = ${show(s.init)}`);
break;
case "exprStmt":
console.log(`${pad}${show(s.expr)}`);
break;
case "if":
console.log(`${pad}if ${show(s.cond)} (else: ${s.else_ === null ? "none" : s.else_.length})`);
print(s.then, depth + 1);
break;
case "loop":
console.log(`${pad}loop`);
if (s.init) print([s.init], depth + 1);
print(s.body, depth + 1);
break;
case "tryCatch":
console.log(`${pad}try (catch: ${s.catchName ?? "none"})`);
print(s.tryBody, depth + 1);
if (s.catchBody) print(s.catchBody, depth + 1);
if (s.finallyBody) {
console.log(`${pad}finally`);
print(s.finallyBody, depth + 1);
}
break;
}
}
}
const step = arrayStep("array");
console.log(step.label);
const stmts = step.build({ kind: "str", text: "items" }, (value) => [
{ kind: "exprStmt", expr: value },
{ kind: "varDecl", name: "done", init: null },
]);
print(stmts, 0);
console.log(stmts.length, JSON.stringify(stmts[2]));
for (const source of [{ kind: "num", value: 1 }, ref("x")] satisfies Expr[]) {
const head = step.first(source);
console.log(head.kind, "name" in head ? head.name : "", JSON.stringify(head));
}
// A literal under `satisfies` keeps its own type, so its methods keep their
// inferred returns: callers read the fields the constraint does not name.
interface Point {
x: number;
}
const located = {
point() {
return { x: 1, y: 2 };
},
rows() {
return [{ x: 3, label: "three" }];
},
} satisfies { point(): Point; rows(): Point[] };
const point = located.point();
console.log(point.x, point.y, located.rows()[0].label);
@@ -0,0 +1 @@
{ "plain": 3800, "san": 7400 }
@@ -0,0 +1 @@
{ "plain": 3000, "san": 4400 }
+6
View File
@@ -56,7 +56,13 @@ export const programs = [
"nullish-long-chain.ts",
"class-array-optional-return.ts",
"contextual-array-union-write.ts",
"2391-default-builtin-imports/main.ts",
"3055-default-builtin-nodenext/main.ts",
"5854-array-from-async.ts",
"6020-class-extends-function-constructor.js",
"5585-record-results-inlined.ts",
"2631-create-require/main.ts",
"4970-int32-call-abi.ts",
// Re-chosen key orders rebuild the inspect helpers after lowering.
"key-order-late-optional.ts",
"5888-inspect-sorted.ts",