mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Check an early read of a cycle class's name
- `C.name` on a class of an import cycle reads the name from the class object, so the reference gets the class's initialization check: an early read throws Node's ReferenceError instead of returning the name. Other classes keep the compile-time name. - The static field storage of a flag without a class is a shared empty set, so the self-hosted frontend lowers cycle-initialization.ts.
This commit is contained in:
@@ -70,6 +70,9 @@ interface ClassReference {
|
||||
name: string;
|
||||
}
|
||||
|
||||
/** The static field storage of a flag without a class. */
|
||||
const NO_STATIC_GLOBALS: ReadonlySet<string> = new Set();
|
||||
|
||||
/** Registers the initialization flags after global collection. Bindings
|
||||
* whose early access has no exact representation are refused: a `var`
|
||||
* (Node reads `undefined` there, but module storage only starts when the
|
||||
@@ -492,7 +495,8 @@ function rewriteFunction(
|
||||
for (const [className, flag] of lowerer.classInitFlags) {
|
||||
if (!runsEarly(flag) || fn.name.startsWith(`%${className}.`)) continue;
|
||||
classes.set(className, flag);
|
||||
for (const id of flag.staticGlobals ?? []) statics.set(id, [...(statics.get(id) ?? []), flag]);
|
||||
for (const id of flag.staticGlobals ?? NO_STATIC_GLOBALS)
|
||||
statics.set(id, [...(statics.get(id) ?? []), flag]);
|
||||
}
|
||||
if (active.size === 0 && classes.size === 0) return;
|
||||
const flagOf = (localId: string, loc: SrcLoc): CycleInitFlag | undefined =>
|
||||
@@ -660,7 +664,7 @@ function rewriteFunction(
|
||||
for (const [className, flag] of [...classes]) {
|
||||
if (flag.module !== initOf || !setsFlag(s, flag)) continue;
|
||||
classes.delete(className);
|
||||
for (const id of flag.staticGlobals ?? []) {
|
||||
for (const id of flag.staticGlobals ?? NO_STATIC_GLOBALS) {
|
||||
const rest = statics.get(id)!.filter((other) => other !== flag);
|
||||
if (rest.length > 0) statics.set(id, rest);
|
||||
else statics.delete(id);
|
||||
|
||||
@@ -5408,8 +5408,18 @@ export function lowerStaticFieldRead(
|
||||
}
|
||||
// `C.name` — the JS-observable class name, a compile-time constant on
|
||||
// the direct spelling (tsc rejects user statics named `name`, so the
|
||||
// chain above can never shadow it in TypeScript sources).
|
||||
// chain above can never shadow it in TypeScript sources). A class of an
|
||||
// import cycle reads it from its class object instead, whose reference
|
||||
// checks the class's initialization (checkCycleBindingAccesses).
|
||||
if (expr.name.text === "name" && info.def.jsName !== undefined) {
|
||||
if (lowerer.classInitFlags.get(info.def.name)?.cycle === true)
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "class.name",
|
||||
args: [classValueRef(lowerer, info, expr.expression)],
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
return { kind: "strLit", value: info.def.jsName, type: STRING, loc };
|
||||
}
|
||||
if (info.callableBase)
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
earlyMethod,
|
||||
earlyInherited,
|
||||
earlyInheritedField,
|
||||
earlyName,
|
||||
earlyRenamedName,
|
||||
earlyRenamed,
|
||||
earlyCap,
|
||||
} from "./stats.ts";
|
||||
@@ -15,7 +17,9 @@ console.log("early:", early);
|
||||
console.log("early method:", earlyMethod);
|
||||
console.log("early inherited:", earlyInherited);
|
||||
console.log("early inherited field:", earlyInheritedField);
|
||||
console.log("early name:", earlyName);
|
||||
console.log("early renamed name:", earlyRenamedName);
|
||||
console.log("early renamed:", earlyRenamed);
|
||||
console.log("early renamed constant:", earlyCap);
|
||||
console.log("probed:", probed);
|
||||
console.log("late:", new Sink("late").tag, Sink.describe(), Sink.hello(), Sink.count);
|
||||
console.log("late:", new Sink("late").tag, Sink.describe(), Sink.hello(), Sink.count, Sink.name);
|
||||
|
||||
@@ -10,8 +10,8 @@ function attempt(run: () => string): string {
|
||||
|
||||
// Each early access throws Node's ReferenceError, naming the binding the
|
||||
// access spells: construction, a static method read as a value, static
|
||||
// members inherited from a base class, the class and a constant through
|
||||
// renamed imports.
|
||||
// members inherited from a base class, the class's name, the class and a
|
||||
// constant through renamed imports.
|
||||
export const early = attempt(() => new Sink("early").tag);
|
||||
export const earlyMethod = attempt(() => {
|
||||
const describe = Sink.describe;
|
||||
@@ -19,9 +19,11 @@ export const earlyMethod = attempt(() => {
|
||||
});
|
||||
export const earlyInherited = attempt(() => Sink.hello());
|
||||
export const earlyInheritedField = attempt(() => String(Renamed.count));
|
||||
export const earlyName = attempt(() => Sink.name);
|
||||
export const earlyRenamedName = attempt(() => Renamed.name);
|
||||
export const earlyRenamed = attempt(() => new Renamed("renamed").tag);
|
||||
export const earlyCap = attempt(() => String(cap));
|
||||
|
||||
export function probe(): string {
|
||||
return `${new Sink("probe").tag} ${Sink.describe()} ${Sink.hello()} ${Sink.count} ${new Renamed("probe").tag} ${cap}`;
|
||||
return `${new Sink("probe").tag} ${Sink.describe()} ${Sink.hello()} ${Sink.count} ${Sink.name} ${new Renamed("probe").tag} ${cap}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user