mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Compile the remaining corpus programs with the native compiler (#1418)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6022-width-lift-discriminant.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -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 }
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user