mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-10 01:56:15 +08:00
Keep forwarded-only tuple bindings per function
The lowerer kept the tuple bindings that are only forwarded in one map for the whole program, keyed by local id. Local ids are unique within one function only: two wrappers that both bind `arguments` (or a `const args = arguments as any` alias) declare the same id, so a wrapper whose tuple exists found the other wrapper's entry, and reading an element of its `arguments` failed with "forwarded-only 'arguments' read as a value". Each function's context now keeps its own map. The corpus program wraps functions with a forwarding-only wrapper and with one that also reads an element of `arguments`, directly and through an alias, in both orders.
This commit is contained in:
@@ -48,6 +48,12 @@ export interface FnCtx {
|
||||
argumentsLocal?: IrLocal | null;
|
||||
/** Arrows inherit the nearest ordinary function's arguments binding. */
|
||||
inheritsArguments?: boolean;
|
||||
/** Tuple bindings of this function that are only forwarded
|
||||
* (typed-arguments.ts onlyForwarded): binding local id → the native
|
||||
* slots forwarding reads. Such a binding is never materialized. Local ids
|
||||
* are unique within one function only, and nested functions never read
|
||||
* these bindings (a read there needs the tuple). */
|
||||
pureTuples: Map<string, IrLocal[]>;
|
||||
/** Declared return type — lets `return` detect record-shape mismatches
|
||||
* (SC2002) before the validator would ICE on them. */
|
||||
returnType: IrType;
|
||||
@@ -81,6 +87,7 @@ export function newFnCtx(
|
||||
captures: lifted ? [] : null,
|
||||
captureSources: [],
|
||||
captureBySymbol: new Map(),
|
||||
pureTuples: new Map(),
|
||||
selfSymbol,
|
||||
selfType,
|
||||
returnType,
|
||||
|
||||
@@ -908,7 +908,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
// type and lowerLambda declared the local.
|
||||
if (expr.text === "arguments") {
|
||||
const binding = captureContextArguments(lowerer.fnStack);
|
||||
if (binding && lowerer.pureTuples.has(binding.id)) {
|
||||
if (binding && lowerer.ctx.pureTuples.has(binding.id)) {
|
||||
throw new InternalCompilerError("lowerer bug: forwarded-only 'arguments' read as a value");
|
||||
}
|
||||
if (binding) return { kind: "varRef", localId: binding.id, type: binding.type, loc };
|
||||
|
||||
@@ -1917,10 +1917,6 @@ export class Lowerer {
|
||||
/** Const bindings holding a forwarded fixed tuple (`const args =
|
||||
* arguments as any` in a typed-arguments function) → the tuple type. */
|
||||
readonly tupleAliasSymbols = new Map<ts.Symbol, IrType>();
|
||||
/** Tuple bindings that are only forwarded (typed-arguments.ts
|
||||
* onlyForwarded): binding local id → the native slots forwarding reads.
|
||||
* Such a binding is never materialized. */
|
||||
readonly pureTuples = new Map<string, IrLocal[]>();
|
||||
/** The static names of the current generic instance's name-keyed
|
||||
* function parameters (static-function-names.ts). */
|
||||
staticFnNames: Map<ts.Symbol, string | null> | null = null;
|
||||
@@ -10725,7 +10721,7 @@ export class Lowerer {
|
||||
(id) => id !== nameNode && this.checker.getSymbolAtLocation(id) === symbol,
|
||||
)
|
||||
) {
|
||||
this.pureTuples.set(body.id, slots);
|
||||
this.ctx.pureTuples.set(body.id, slots);
|
||||
return;
|
||||
}
|
||||
const init = this.tupleOfSlots(slots, tupleType, nameNode, loc);
|
||||
@@ -11122,7 +11118,7 @@ export class Lowerer {
|
||||
}
|
||||
if (!symbol) return null;
|
||||
const direct = this.resolveKey(symbol, ident);
|
||||
if (direct && this.pureTuples.has(direct.id)) {
|
||||
if (direct && this.ctx.pureTuples.has(direct.id)) {
|
||||
// onlyForwarded proved every read forwards; a plain read here means
|
||||
// the analysis and the lowering disagree.
|
||||
throw new InternalCompilerError(
|
||||
|
||||
@@ -176,7 +176,7 @@ export function declareTypedArguments(
|
||||
if (
|
||||
onlyForwarded(lowerer, node, (id) => id.text === "arguments" && argumentsOwner(id) === node)
|
||||
) {
|
||||
lowerer.pureTuples.set(local.id, slots);
|
||||
lowerer.ctx.pureTuples.set(local.id, slots);
|
||||
return;
|
||||
}
|
||||
prologue.push({
|
||||
@@ -273,7 +273,7 @@ function pureSlotsOf(lowerer: Lowerer, node: ts.Expression): IrLocal[] | null {
|
||||
if (!ts.isIdentifier(operand)) return null;
|
||||
const local =
|
||||
operand.text === "arguments" ? lowerer.ctx.argumentsLocal : lowerer.peekLocal(operand);
|
||||
return local ? (lowerer.pureTuples.get(local.id) ?? null) : null;
|
||||
return local ? (lowerer.ctx.pureTuples.get(local.id) ?? null) : null;
|
||||
}
|
||||
|
||||
/** The positional values `node` forwards when it is a pure-forwarded tuple
|
||||
@@ -466,7 +466,7 @@ export function lowerTupleAliasDecl(lowerer: Lowerer, decl: ts.VariableDeclarati
|
||||
// An alias of a pure-forwarded tuple is itself only forwarded
|
||||
// (onlyForwarded followed it): it names the same slots.
|
||||
const local = lowerer.declareLocal(decl.name, decl.name.text, tupleType, false);
|
||||
lowerer.pureTuples.set(local.id, slots);
|
||||
lowerer.ctx.pureTuples.set(local.id, slots);
|
||||
return { kind: "block", body: [], loc: locOf(decl) };
|
||||
}
|
||||
const init = lowerer.lowerExpr(stripTypeWrappers(decl.initializer));
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6385-variadic-wrapper-shared-local-names.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Variadic wrappers whose bodies declare the same local names: one only
|
||||
// forwards its `arguments` (or its alias of them), the other also reads an
|
||||
// element, so its tuple exists. Each wrapper forwards its own arguments.
|
||||
type AnyFn = (...args: any) => any;
|
||||
|
||||
function forward<F extends AnyFn>(fn: F): F {
|
||||
return function (this: unknown) {
|
||||
return fn.apply(this, arguments as any);
|
||||
} as F;
|
||||
}
|
||||
|
||||
function logFirst<F extends AnyFn>(fn: F): F {
|
||||
return function (this: unknown) {
|
||||
console.log("first:", arguments[0], "of", arguments.length);
|
||||
return fn.apply(this, arguments as any);
|
||||
} as F;
|
||||
}
|
||||
|
||||
function forwardAlias<F extends AnyFn>(fn: F): F {
|
||||
return function (this: unknown) {
|
||||
const args = arguments as any;
|
||||
return fn.apply(this, args);
|
||||
} as F;
|
||||
}
|
||||
|
||||
function logFirstAlias<F extends AnyFn>(fn: F): F {
|
||||
return function (this: unknown) {
|
||||
const args = arguments as any;
|
||||
console.log("alias first:", args[0]);
|
||||
return fn.apply(this, args);
|
||||
} as F;
|
||||
}
|
||||
|
||||
function add3(a: number, b: number, c: number): number {
|
||||
return a + b + c;
|
||||
}
|
||||
|
||||
function greet(name: string): string {
|
||||
return "hi " + name;
|
||||
}
|
||||
|
||||
const sum = forward(add3);
|
||||
console.log(sum(1, 2, 3));
|
||||
const hello = logFirst(greet);
|
||||
console.log(hello("ada"));
|
||||
|
||||
const sumAlias = forwardAlias(add3);
|
||||
console.log(sumAlias(4, 5, 6));
|
||||
const helloAlias = logFirstAlias(greet);
|
||||
console.log(helloAlias("grace"));
|
||||
|
||||
// The other order: the wrapper that reads an element first.
|
||||
const hello2 = logFirst(greet);
|
||||
console.log(hello2("linus"));
|
||||
const sum2 = forward(add3);
|
||||
console.log(sum2(7, 8, 9));
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2200, "san": 3300 }
|
||||
Reference in New Issue
Block a user