mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Merge main
This commit is contained in:
@@ -5,15 +5,20 @@ import * as ts from "../ts7/adapter.js";
|
||||
import { bodyReadsArguments } from "../arguments-usage.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
type IrExpr,
|
||||
type IrFunction,
|
||||
type IrParam,
|
||||
type IrStmt,
|
||||
type IrType,
|
||||
type SrcLoc,
|
||||
STRING,
|
||||
VOID,
|
||||
isUnitType,
|
||||
typeEquals,
|
||||
} from "../../ir/ir.js";
|
||||
import { helperLoc, nodeThrowExpr } from "../../ir/build.js";
|
||||
import { isJsSourceFile, locOf } from "../program.js";
|
||||
import { typeKey } from "../type-mapper.js";
|
||||
import { functionCanReturnUndefined } from "../function-completion.js";
|
||||
@@ -24,7 +29,7 @@ import { nsPathPrefix } from "./lower-namespaces.js";
|
||||
import { declSymbolOf } from "./lower-modules.js";
|
||||
import { npmStaticPackageOfPath } from "../npm-static.js";
|
||||
import { rejectStaticThis } from "./static-this.js";
|
||||
import { jsBindingHasOpenWrites } from "./lower-stmts.js";
|
||||
import { isVarDeclared, jsBindingHasOpenWrites } from "./lower-stmts.js";
|
||||
import {
|
||||
type ParamShape,
|
||||
paramShapes,
|
||||
@@ -93,6 +98,17 @@ export interface GenericFnInfo {
|
||||
* declaration makes a new function object in JavaScript, while the
|
||||
* compiled instances are shared module functions, so only calls lower. */
|
||||
functionScoped?: true;
|
||||
/** The declaration of a generic arrow/function-expression binding. */
|
||||
bindingDecl?: ts.VariableDeclaration;
|
||||
/** A module-scope binding that code can use before its declaration
|
||||
* runs: the BOOL global its declaration sets, and whether it is a
|
||||
* `let`/`const` (a temporal dead zone) or a `var` (undefined). */
|
||||
presence?: { flagId: string; lexical: boolean };
|
||||
/** genericValueForwarder's lifted functions, by instance name. */
|
||||
valueForwarders?: Map<string, string>;
|
||||
/** runsAfterDeclaration's answers for the function declarations whose
|
||||
* calls it placed. */
|
||||
ordering?: Map<ts.Node, boolean>;
|
||||
/** IMPLICIT-ANY monomorphization (npm-static JS): parallel to
|
||||
* decl.parameters — the param's own symbol when the slot is a BINDABLE
|
||||
* implicit-any parameter (untyped, identifier-named, never written in
|
||||
@@ -266,7 +282,14 @@ export function lowerGenericCall(
|
||||
? implicitCallInstance(lowerer, expr, info)
|
||||
: genericCallInstance(lowerer, expr, info);
|
||||
const args = lowerer.completeArgs(expr.arguments.nodes, instance.params, loc, expr);
|
||||
return { kind: "call", callee: instance.name, args, type: instance.returnType, loc };
|
||||
const call: IrExpr & { kind: "call" } = {
|
||||
kind: "call",
|
||||
callee: instance.name,
|
||||
args,
|
||||
type: instance.returnType,
|
||||
loc,
|
||||
};
|
||||
return guardEarlyBindingCall(lowerer, info, expr, call);
|
||||
}
|
||||
|
||||
/** The instance a CALL of a generic function-like names: resolved
|
||||
@@ -1361,6 +1384,110 @@ export function lowerGenericFnValue(
|
||||
}
|
||||
}
|
||||
if (!target) fenceUnpinned();
|
||||
return guardEarlyBindingValue(
|
||||
lowerer,
|
||||
info,
|
||||
ref,
|
||||
pinnedGenericClosure(lowerer, ref, info, target, fenceUnpinned),
|
||||
);
|
||||
}
|
||||
|
||||
/** The value a generic binding declared inside a function stores when a
|
||||
* nested function read it before the declaration lowered (`const read =
|
||||
* () => id; const id: (x: number) => number = <T>(x: T): T => x`): such
|
||||
* a read resolves to an ordinary TDZ local, so the declaration assigns it
|
||||
* the instance its concrete declared signature pins. Each evaluation of
|
||||
* the declaration makes a new function object, as in JavaScript: the
|
||||
* value is a closure over a forwarder to the instance, not the instance's
|
||||
* interned closure. */
|
||||
export function genericBindingStoredValue(
|
||||
lowerer: Lowerer,
|
||||
decl: ts.VariableDeclaration,
|
||||
info: GenericFnInfo,
|
||||
): IrExpr {
|
||||
const name = decl.name as ts.Identifier;
|
||||
const fenceUnpinned = (): never =>
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
name,
|
||||
`generic function bindings read before their declaration without a concrete declared signature (annotate '${info.baseName}' with one, or declare it before its uses)`,
|
||||
);
|
||||
const sigs = lowerer.checker.getCallSignatures(lowerer.typeOf(name));
|
||||
if (sigs.length !== 1) fenceUnpinned();
|
||||
const pinned = pinnedGenericClosure(lowerer, name, info, sigs[0]!, fenceUnpinned);
|
||||
const async = ts.getModifiers(info.decl)?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword);
|
||||
if (pinned.kind !== "closure" || pinned.type.kind !== "func" || pinned.type.rest || async)
|
||||
return lowerer.unsupported(
|
||||
"SC1090",
|
||||
name,
|
||||
`async or rest-parameter generic function bindings read before their declaration (each evaluation of '${info.baseName}' makes a new function object)`,
|
||||
);
|
||||
return {
|
||||
...pinned,
|
||||
fnName: genericValueForwarder(lowerer, info, pinned.fnName, pinned.type, pinned.loc),
|
||||
};
|
||||
}
|
||||
|
||||
/** A lifted function that calls the instance `instance` (of type `type`)
|
||||
* with its own arguments. Lifted functions get a new closure object per
|
||||
* evaluation; module functions, which instances are, get one interned
|
||||
* closure. */
|
||||
function genericValueForwarder(
|
||||
lowerer: Lowerer,
|
||||
info: GenericFnInfo,
|
||||
instance: string,
|
||||
type: IrType & { kind: "func" },
|
||||
at: SrcLoc,
|
||||
): string {
|
||||
info.valueForwarders ??= new Map();
|
||||
const existing = info.valueForwarders.get(instance);
|
||||
if (existing !== undefined) return existing;
|
||||
const name = `${instance}%value`;
|
||||
const loc = helperLoc(at);
|
||||
const params: IrParam[] = type.params.map((paramType, i) => ({
|
||||
localId: `%arg${i}.0`,
|
||||
name: `%arg${i}`,
|
||||
type: paramType,
|
||||
}));
|
||||
const call: IrExpr = {
|
||||
kind: "call",
|
||||
callee: instance,
|
||||
args: params.map((p): IrExpr => ({ kind: "varRef", localId: p.localId, type: p.type, loc })),
|
||||
type: type.ret,
|
||||
loc,
|
||||
};
|
||||
lowerer.liftedFns.push({
|
||||
name,
|
||||
params,
|
||||
returnType: type.ret,
|
||||
locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
|
||||
captures: [],
|
||||
...(ts.isFunctionExpression(info.decl) ? { ownsPrototype: true } : {}),
|
||||
body:
|
||||
type.ret.kind === "void"
|
||||
? [
|
||||
{ kind: "exprStmt", expr: call, loc },
|
||||
{ kind: "return", value: null, loc },
|
||||
]
|
||||
: [{ kind: "return", value: call, loc }],
|
||||
loc,
|
||||
});
|
||||
lowerer.noteEdge(name);
|
||||
info.valueForwarders.set(instance, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
/** The closure of the instance of `info` that `target` pins, for a value
|
||||
* read at `ref`. `fenceUnpinned` reports a signature that leaves a type
|
||||
* parameter unbound. */
|
||||
function pinnedGenericClosure(
|
||||
lowerer: Lowerer,
|
||||
ref: ts.Expression,
|
||||
info: GenericFnInfo,
|
||||
target: ts.Signature,
|
||||
fenceUnpinned: () => never,
|
||||
): IrExpr {
|
||||
const loc = locOf(ref);
|
||||
const bindings = new Map<ts.Symbol, IrType>();
|
||||
unifySignatureBindings(lowerer, info, target, bindings);
|
||||
const hofBinding = runtimeOptionalHofGenericBinding(lowerer, ref, info);
|
||||
@@ -2693,9 +2820,18 @@ export function bindingGenericFnInfoOf(
|
||||
typeParams,
|
||||
instances: new Map(),
|
||||
...(functionScoped ? { functionScoped: true as const } : {}),
|
||||
bindingDecl: decl,
|
||||
};
|
||||
lowerer.bindingGenericFns.set(fnNode, info);
|
||||
lowerer.genericFnsBySymbol.set(sym, info);
|
||||
// Module-scope uses resolve to the instances from the start, so those
|
||||
// that can run first need the flag. Inside a function the declaration
|
||||
// registers before any later use lowers, and earlier uses read a TDZ
|
||||
// box (genericBindingStoredValue).
|
||||
if (!functionScoped) {
|
||||
const presence = bindingPresenceOf(lowerer, info, decl, sym);
|
||||
if (presence !== undefined) info.presence = presence;
|
||||
}
|
||||
if (ts.isFunctionExpression(fnNode) && fnNode.name !== undefined) {
|
||||
const inner = lowerer.checker.getSymbolAtLocation(fnNode.name);
|
||||
if (inner) lowerer.genericFnsBySymbol.set(inner, info);
|
||||
@@ -2703,6 +2839,261 @@ export function bindingGenericFnInfoOf(
|
||||
return info;
|
||||
}
|
||||
|
||||
/** Whether `use`, a reference to the binding `info` declares, can only
|
||||
* run after the declaration has run: it follows the declaration in its
|
||||
* statement list (closures count where they are created), sits inside the
|
||||
* initializer itself, or is inside a function declaration whose every
|
||||
* reference does. References from other modules run after this module's
|
||||
* body, and export specifiers only export. A use the check cannot place
|
||||
* answers false. */
|
||||
export function runsAfterDeclaration(lowerer: Lowerer, info: GenericFnInfo, use: ts.Node): boolean {
|
||||
const decl = info.bindingDecl;
|
||||
if (decl === undefined) return true;
|
||||
info.ordering ??= new Map();
|
||||
return useRunsAfter(lowerer, decl, use, info.ordering, new Set());
|
||||
}
|
||||
|
||||
function useRunsAfter(
|
||||
lowerer: Lowerer,
|
||||
decl: ts.VariableDeclaration,
|
||||
use: ts.Node,
|
||||
memo: Map<ts.Node, boolean>,
|
||||
inProgress: Set<ts.Node>,
|
||||
): boolean {
|
||||
if (ts.getSourceFileOfNode(use) !== ts.getSourceFileOfNode(decl)) return true;
|
||||
const init = decl.initializer;
|
||||
if (init !== undefined && use.pos >= init.pos && use.end <= init.end) return true;
|
||||
const stmt = decl.parent?.parent;
|
||||
const list = stmt?.parent;
|
||||
if (stmt === undefined || list === undefined) return false;
|
||||
for (let n: ts.Node = use; n.parent !== undefined; n = n.parent) {
|
||||
if (n.parent === list) return n === stmt ? use.pos >= decl.end : n.pos >= stmt.end;
|
||||
if (ts.isFunctionDeclaration(n)) return functionRunsAfter(lowerer, decl, n, memo, inProgress);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Whether every call of the function declaration `fn` runs after `decl`
|
||||
* has run. A function in progress counts as running after: its calls of
|
||||
* itself, directly or through other functions, add no earlier caller.
|
||||
* Only answers that depend on no function in progress are memoized. */
|
||||
function functionRunsAfter(
|
||||
lowerer: Lowerer,
|
||||
decl: ts.VariableDeclaration,
|
||||
fn: ts.FunctionDeclaration,
|
||||
memo: Map<ts.Node, boolean>,
|
||||
inProgress: Set<ts.Node>,
|
||||
): boolean {
|
||||
const known = memo.get(fn);
|
||||
if (known !== undefined) return known;
|
||||
if (inProgress.has(fn)) return true;
|
||||
// `export default function () {}`: only importers call it, after this
|
||||
// module's body has run.
|
||||
if (fn.name === undefined) return true;
|
||||
inProgress.add(fn);
|
||||
let result = true;
|
||||
try {
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(fn.name);
|
||||
if (symbol === undefined) result = false;
|
||||
else {
|
||||
for (const ref of bindingReferences(lowerer, ts.getSourceFileOfNode(fn), symbol, fn.name)) {
|
||||
if (ref.pos >= fn.pos && ref.end <= fn.end) continue;
|
||||
if (!useRunsAfter(lowerer, decl, ref, memo, inProgress)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
inProgress.delete(fn);
|
||||
}
|
||||
if (inProgress.size === 0) memo.set(fn, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** The value references of `symbol` in `file` other than `name` (its
|
||||
* declaration name): identifiers spelled like it that resolve to it,
|
||||
* outside type positions and export specifiers. */
|
||||
function bindingReferences(
|
||||
lowerer: Lowerer,
|
||||
file: ts.SourceFile,
|
||||
symbol: ts.Symbol,
|
||||
name: ts.Identifier,
|
||||
): ts.Identifier[] {
|
||||
let byName = lowerer.bindingIdentifiers.get(file);
|
||||
if (byName === undefined) {
|
||||
byName = new Map();
|
||||
lowerer.bindingIdentifiers.set(file, byName);
|
||||
}
|
||||
let idents = byName.get(name.text);
|
||||
if (idents === undefined) {
|
||||
const found: ts.Identifier[] = [];
|
||||
ts.walkPreorder(file, (node) => {
|
||||
if (ts.isIdentifier(node) && node.text === name.text) found.push(node);
|
||||
return undefined;
|
||||
});
|
||||
lowerer.checker.prefetchSymbolNodesExact(found);
|
||||
byName.set(name.text, found);
|
||||
idents = found;
|
||||
}
|
||||
return idents.filter((ident) => {
|
||||
if (ident === name) return false;
|
||||
const parent = ident.parent;
|
||||
if (parent === undefined || ts.isExportSpecifier(parent) || ts.isExportAssignment(parent))
|
||||
return false;
|
||||
let resolved = lowerer.checker.getSymbolAtLocation(ident);
|
||||
if (ts.isShorthandPropertyAssignment(parent) && parent.name === ident)
|
||||
resolved = lowerer.checker.getShorthandAssignmentValueSymbol(parent) ?? resolved;
|
||||
if (resolved !== symbol) return false;
|
||||
for (let p: ts.Node | undefined = parent; p !== undefined; p = p.parent) {
|
||||
if (ts.isTypeNode(p) && p.kind !== ts.SyntaxKind.ExpressionWithTypeArguments) return false;
|
||||
if (ts.isStatement(p) || ts.isFunctionLike(p)) break;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/** The presence flag of a module-scope generic binding that code can use
|
||||
* before its declaration runs (a hoisted function or an earlier closure
|
||||
* called during module evaluation), or undefined when every use runs
|
||||
* after it. The declaration statement sets the flag. */
|
||||
function bindingPresenceOf(
|
||||
lowerer: Lowerer,
|
||||
info: GenericFnInfo,
|
||||
decl: ts.VariableDeclaration,
|
||||
symbol: ts.Symbol,
|
||||
): GenericFnInfo["presence"] {
|
||||
const name = decl.name as ts.Identifier;
|
||||
const early = bindingReferences(lowerer, ts.getSourceFileOfNode(decl), symbol, name).some(
|
||||
(ref) => !runsAfterDeclaration(lowerer, info, ref),
|
||||
);
|
||||
if (!early) return undefined;
|
||||
const flagId = `%g.${info.qualifiedName}%initialized`;
|
||||
lowerer.globalsList.push({ id: flagId, name: "%initialized", type: BOOL, mutable: true });
|
||||
return { flagId, lexical: !isVarDeclared(decl) };
|
||||
}
|
||||
|
||||
/** The statement that records a generic binding's declaration as run,
|
||||
* for a binding with a presence flag. */
|
||||
export function genericBindingPresenceStore(
|
||||
info: GenericFnInfo,
|
||||
decl: ts.VariableDeclaration,
|
||||
): IrStmt | null {
|
||||
if (info.presence === undefined) return null;
|
||||
const loc = locOf(decl);
|
||||
return {
|
||||
kind: "assign",
|
||||
localId: info.presence.flagId,
|
||||
value: { kind: "boolLit", value: true, type: BOOL, loc },
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** A call of a generic binding where the binding may not be initialized
|
||||
* yet. A `let`/`const` binding in its temporal dead zone throws Node's
|
||||
* ReferenceError before the arguments run; an unassigned `var` is
|
||||
* undefined, so the arguments run and the call throws "is not a
|
||||
* function". Calls that run after the declaration stay direct. */
|
||||
function guardEarlyBindingCall(
|
||||
lowerer: Lowerer,
|
||||
info: GenericFnInfo,
|
||||
expr: ts.CallExpression,
|
||||
call: IrExpr & { kind: "call" },
|
||||
): IrExpr {
|
||||
if (info.bindingDecl === undefined || runsAfterDeclaration(lowerer, info, expr)) return call;
|
||||
const loc = call.loc;
|
||||
const presence = requirePresence(lowerer, info, expr.expression);
|
||||
const flag: IrExpr = { kind: "varRef", localId: presence.flagId, type: BOOL, loc };
|
||||
if (presence.lexical)
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [tdzCheck(flag, info.baseName, loc)],
|
||||
result: call,
|
||||
type: call.type,
|
||||
loc,
|
||||
};
|
||||
// Arguments first, in order, then the check: Node evaluates them
|
||||
// before it finds the callee is not callable.
|
||||
const stmts: IrStmt[] = [];
|
||||
const args = call.args.map((arg): IrExpr => {
|
||||
const id = `%earlyCallArg.${lowerer.ctx.locals.length}`;
|
||||
lowerer.ctx.locals.push({ id, name: "%earlyCallArg", type: arg.type, mutable: false });
|
||||
stmts.push({ kind: "varDecl", localId: id, init: arg, loc });
|
||||
return { kind: "varRef", localId: id, type: arg.type, loc };
|
||||
});
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts,
|
||||
result: {
|
||||
kind: "ternary",
|
||||
cond: flag,
|
||||
then: { ...call, args },
|
||||
else_: nodeThrowExpr(1, "", `${info.baseName} is not a function`, call.type, loc),
|
||||
type: call.type,
|
||||
loc,
|
||||
},
|
||||
type: call.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** A generic binding read as a value where it may not be initialized
|
||||
* yet: a `let`/`const` binding checks its temporal dead zone first. */
|
||||
function guardEarlyBindingValue(
|
||||
lowerer: Lowerer,
|
||||
info: GenericFnInfo,
|
||||
ref: ts.Expression,
|
||||
value: IrExpr,
|
||||
): IrExpr {
|
||||
if (info.bindingDecl === undefined || runsAfterDeclaration(lowerer, info, ref)) return value;
|
||||
const presence = requirePresence(lowerer, info, ref);
|
||||
if (!presence.lexical)
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
ref,
|
||||
`generic 'var' bindings read as values before their declaration runs (Node reads undefined there; declare '${info.baseName}' with const)`,
|
||||
);
|
||||
const loc = value.loc;
|
||||
const flag: IrExpr = { kind: "varRef", localId: presence.flagId, type: BOOL, loc };
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [tdzCheck(flag, info.baseName, loc)],
|
||||
result: value,
|
||||
type: value.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** The presence flag a use that can run before the declaration checks.
|
||||
* Only module-scope bindings have one: a binding inside a function would
|
||||
* need a per-call flag that its closures share. */
|
||||
function requirePresence(
|
||||
lowerer: Lowerer,
|
||||
info: GenericFnInfo,
|
||||
blame: ts.Node,
|
||||
): NonNullable<GenericFnInfo["presence"]> {
|
||||
if (info.presence !== undefined) return info.presence;
|
||||
return lowerer.unsupported(
|
||||
"SC1090",
|
||||
blame,
|
||||
`generic arrow/function-expression bindings declared inside functions that code can call before their declaration runs (the binding '${info.baseName}' is a 'var' in a branch or block, or is called from a place that can run first — declare it with const before its uses)`,
|
||||
);
|
||||
}
|
||||
|
||||
function tdzCheck(flag: IrExpr, name: string, loc: SrcLoc): IrStmt {
|
||||
return {
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "intrinsic",
|
||||
name: "module.tdzCheck",
|
||||
args: [flag, { kind: "strLit", value: name, type: STRING, loc }],
|
||||
type: VOID,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `const h = id` — a binding ALIASING a generic function (a top-level
|
||||
* declaration, a registered generic binding, or another alias — resolved
|
||||
* left to right in declaration order). The alias registers the SAME info
|
||||
|
||||
@@ -139,6 +139,8 @@ import {
|
||||
bindingGenericFnAliasInfoOf,
|
||||
bindingGenericFnInfoOf,
|
||||
bindingGenericFnNodeOf,
|
||||
genericBindingPresenceStore,
|
||||
genericBindingStoredValue,
|
||||
implicitLocalFnInfoOf,
|
||||
implicitLocalFnNodeOf,
|
||||
implicitMethodCallInfersReturn,
|
||||
@@ -5116,8 +5118,19 @@ export function lowerVarDecl(
|
||||
{
|
||||
const gfnNode = bindingGenericFnNodeOf(decl) ?? bindingContextualGenericFnNodeOf(lowerer, decl);
|
||||
if (gfnNode) {
|
||||
bindingGenericFnInfoOf(lowerer, decl, gfnNode);
|
||||
return null;
|
||||
const info = bindingGenericFnInfoOf(lowerer, decl, gfnNode);
|
||||
// A nested function that lowered before this declaration read the
|
||||
// binding as an ordinary TDZ box (predeclareForwardCapture); the
|
||||
// declaration initializes it, so those reads see the function once
|
||||
// it runs and a ReferenceError before.
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
|
||||
const pre = symbol ? lowerer.ctx.tdzPredeclared.get(symbol) : undefined;
|
||||
if (pre) {
|
||||
lowerer.ctx.tdzPredeclared.delete(symbol!);
|
||||
const value = genericBindingStoredValue(lowerer, decl, info);
|
||||
return { kind: "assign", localId: pre.id, value, initializes: true, loc: locOf(decl) };
|
||||
}
|
||||
return genericBindingPresenceStore(info, decl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1914,6 +1914,9 @@ export class Lowerer {
|
||||
* pinned values resolve through genericFnOf exactly like top-level
|
||||
* generic function declarations (bindingGenericFnInfoOf). */
|
||||
readonly bindingGenericFns = new Map<ts.Node, GenericFnInfo>();
|
||||
/** Identifiers by spelling, per file, for the generic bindings' use
|
||||
* ordering (runsAfterDeclaration). */
|
||||
readonly bindingIdentifiers = new Map<ts.SourceFile, Map<string, ts.Identifier[]>>();
|
||||
/** Per-symbol result of the never-reassigned file scan
|
||||
* (bindingNeverReassigned — object-literal generic-method receivers). */
|
||||
readonly neverReassignedCache = new Map<ts.Symbol, boolean>();
|
||||
|
||||
@@ -226,10 +226,9 @@ export class SemanticChecker {
|
||||
}
|
||||
/** getResolvedSignature for each node. */
|
||||
getResolvedSignatures(nodes: readonly AstNode[]): (SemanticSignature | undefined)[] {
|
||||
const data = this.project.request<(SignatureResponse | null)[]>("getResolvedSignatures", {
|
||||
return this.project.requestSignatures("getResolvedSignatures", {
|
||||
locations: nodes.map((node) => node.id),
|
||||
});
|
||||
return data.map((item) => (item === null ? undefined : this.project.signature(item)));
|
||||
}
|
||||
getSignatureFromDeclaration(node: AstNode): SemanticSignature | undefined {
|
||||
return this.signature("getSignatureFromDeclaration", { location: node.id });
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7180-generic-local-binding-early-uses.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7181-generic-module-binding-tdz.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/7182-generic-binding-direct-calls.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Generic arrow bindings declared inside functions, used by code that runs
|
||||
// before or after the declaration: closures created above the declaration
|
||||
// and hoisted function declarations read the binding like any const, so a
|
||||
// use before the declaration throws a ReferenceError and a later one sees
|
||||
// the function.
|
||||
function describe(error: unknown): string {
|
||||
return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
||||
}
|
||||
|
||||
function make(): (x: number) => number {
|
||||
const read = (): ((x: number) => number) => id;
|
||||
const id: (x: number) => number = <T>(x: T): T => x;
|
||||
return read();
|
||||
}
|
||||
console.log(make()(7));
|
||||
|
||||
function outer(flag: boolean): number {
|
||||
if (flag) return helper();
|
||||
const id: (x: number) => number = <T>(x: T): T => x;
|
||||
return helper();
|
||||
|
||||
function helper(): number {
|
||||
return id(1);
|
||||
}
|
||||
}
|
||||
console.log(outer(false));
|
||||
try {
|
||||
console.log(outer(true));
|
||||
} catch (error) {
|
||||
console.log(describe(error));
|
||||
}
|
||||
|
||||
function run(): void {
|
||||
const early = (): number => id(2);
|
||||
try {
|
||||
early();
|
||||
} catch (error) {
|
||||
console.log(describe(error));
|
||||
}
|
||||
const id: (x: number) => number = <T>(x: T): T => x;
|
||||
console.log(early());
|
||||
}
|
||||
run();
|
||||
|
||||
function counter(): () => number {
|
||||
let total = 0;
|
||||
const step = (): number => (total = add(total, 1));
|
||||
const add: (a: number, b: number) => number = <T extends number>(a: T, b: T): number => a + b;
|
||||
step();
|
||||
step();
|
||||
return step;
|
||||
}
|
||||
console.log(counter()());
|
||||
|
||||
// Each evaluation of the declaration makes a new function object.
|
||||
const first = make();
|
||||
console.log(first === make(), first === first, first(8));
|
||||
function looped(): string {
|
||||
const seen: ((x: string) => string)[] = [];
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const read = (): ((x: string) => string) => tag;
|
||||
const tag: (x: string) => string = function <T>(x: T): T {
|
||||
return x;
|
||||
};
|
||||
seen.push(read());
|
||||
}
|
||||
return `${seen[0] === seen[1]} ${seen[1]!("p")}`;
|
||||
}
|
||||
console.log(looped());
|
||||
@@ -0,0 +1,49 @@
|
||||
// A module-scope generic arrow binding is in its temporal dead zone until
|
||||
// its declaration runs: a hoisted function or an earlier closure that calls
|
||||
// it during module evaluation throws a ReferenceError, and calls made after
|
||||
// the declaration reach the function. A `var` binding is undefined until
|
||||
// then: the call's arguments run, then it throws "is not a function".
|
||||
function describe(error: unknown): string {
|
||||
return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
||||
}
|
||||
|
||||
function early(): number {
|
||||
return id(1);
|
||||
}
|
||||
const before = (): string => wrap("b");
|
||||
|
||||
try {
|
||||
console.log(early());
|
||||
} catch (error) {
|
||||
console.log(describe(error));
|
||||
}
|
||||
try {
|
||||
console.log(before());
|
||||
} catch (error) {
|
||||
console.log(describe(error));
|
||||
}
|
||||
|
||||
const id = <T>(x: T): T => x;
|
||||
let wrap = <T>(x: T): string => `[${String(x)}]`;
|
||||
|
||||
console.log(early(), before(), id("direct"), wrap(2));
|
||||
|
||||
function later(): string {
|
||||
return wrap(id(3));
|
||||
}
|
||||
console.log(later());
|
||||
|
||||
function callMissing(): number {
|
||||
return missing(log("argument"));
|
||||
}
|
||||
function log(text: string): number {
|
||||
console.log(text);
|
||||
return 1;
|
||||
}
|
||||
try {
|
||||
console.log(callMissing());
|
||||
} catch (error) {
|
||||
console.log(describe(error));
|
||||
}
|
||||
var missing = <T>(x: T): T => x;
|
||||
console.log(callMissing(), missing(5));
|
||||
@@ -0,0 +1,42 @@
|
||||
// Generic arrow bindings whose uses all run after the declaration: later
|
||||
// statements, closures created after it, later declarators, recursion and
|
||||
// function declarations that only later code calls. These calls go
|
||||
// straight to the compiled instances.
|
||||
const pair = <T>(x: T): [T, T] => [x, x];
|
||||
const doubled = pair(2), labels = pair("a");
|
||||
console.log(doubled.join(","), labels.join(","));
|
||||
|
||||
function useLater(): string {
|
||||
return pair(true).join(",");
|
||||
}
|
||||
console.log(useLater());
|
||||
|
||||
export function exported(n: number): number {
|
||||
return pair(n)[0] + 1;
|
||||
}
|
||||
console.log(exported(4));
|
||||
|
||||
function local(): string {
|
||||
var twice = <T extends number>(x: T): number => x * 2;
|
||||
const call = (): number => twice(4);
|
||||
const depth = <T>(xs: T[], n: number): number => (n <= 0 ? xs.length : depth(xs, n - 1) + 1);
|
||||
return `${twice(1)} ${call()} ${depth([1, 2], 3)} ${helper()}`;
|
||||
|
||||
function helper(): string {
|
||||
return pair("h").join("");
|
||||
}
|
||||
}
|
||||
console.log(local());
|
||||
|
||||
function loop(): string[] {
|
||||
const out: string[] = [];
|
||||
const later: (() => string)[] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const tag = <T>(x: T): string => `#${String(x)}`;
|
||||
later.push(() => tag("late"));
|
||||
out.push(tag(i * 10));
|
||||
}
|
||||
for (const f of later) out.push(f());
|
||||
return out;
|
||||
}
|
||||
console.log(loop().join(" "));
|
||||
@@ -60,3 +60,15 @@ function throughParam(a: Amb): void {
|
||||
console.log(a.m(4));
|
||||
}
|
||||
throughParam(amb);
|
||||
|
||||
// A `var` binding inside a function, declared in a branch and called from
|
||||
// a closure: when the branch does not run, Node finds the binding undefined.
|
||||
// The compiled instances have no per-call flag for that, so the call fences.
|
||||
function branchVar(flag: boolean): number {
|
||||
if (flag) {
|
||||
var picked = <T>(x: T): T => x;
|
||||
}
|
||||
const invoke = (): number => picked(1);
|
||||
return invoke();
|
||||
}
|
||||
console.log(branchVar(true));
|
||||
|
||||
@@ -92,4 +92,11 @@ generic-value-bindings.ts:60:15 - error SC1090: calls of the generic method 'm'
|
||||
59 | function throughParam(a: Amb): void {
|
||||
60 | console.log(a.m(4));
|
||||
| ^~~~~~
|
||||
61 | }
|
||||
61 | }
|
||||
|
||||
generic-value-bindings.ts:71:32 - error SC1090: generic arrow/function-expression bindings declared inside functions that code can call before their declaration runs (the binding 'picked' is a 'var' in a branch or block, or is called from a place that can run first — declare it with const before its uses) are not supported yet
|
||||
|
||||
70 | }
|
||||
71 | const invoke = (): number => picked(1);
|
||||
| ^~~~~~
|
||||
72 | return invoke();
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2542, "san": 2911 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2500, "san": 2870 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2686, "san": 3069 }
|
||||
Reference in New Issue
Block a user