mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-24 06:14:01 +08:00
Run module initializers that only ++ and -- read
This commit is contained in:
@@ -138,3 +138,33 @@ test("fills every empty copy with the exact file and span while preserving popul
|
||||
expect(populated.body).toEqual([value(1)]);
|
||||
for (const untouched of [otherFile, otherStart, otherEnd]) expect(untouched.body).toEqual([]);
|
||||
});
|
||||
|
||||
test("an update of the binding demands its initializer", () => {
|
||||
const { initializers, pending, lowerer } = fixture();
|
||||
const updated = block(loc),
|
||||
written = block({ ...loc, start: 2, end: 3 });
|
||||
const update = (id: string, prefix: boolean): IrStmt => ({
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "incDec", op: "+", prefix, localId: id, type: F64, loc },
|
||||
loc,
|
||||
});
|
||||
const functions = [
|
||||
fn("init", [updated, written]),
|
||||
fn("use", [
|
||||
update("updated", false),
|
||||
{
|
||||
kind: "assign",
|
||||
localId: "written",
|
||||
value: { kind: "numLit", value: 1, type: F64, loc },
|
||||
loc,
|
||||
},
|
||||
]),
|
||||
];
|
||||
pending.set("updated", [{ loc, lower: () => [value(2)] }]);
|
||||
pending.set("written", [{ loc: written.loc, lower: () => [value(3)] }]);
|
||||
expect(initializers.process(lowerer, functions)).toBe(true);
|
||||
expect(updated.body).toEqual([value(2)]);
|
||||
// A plain write never observes the initial value.
|
||||
expect(written.body).toEqual([]);
|
||||
expect(pending.has("written")).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import { everyStmtList } from "../../ir/traverse.js";
|
||||
import type { IrFunction, IrStmt, IrType } from "../../ir/ir.js";
|
||||
import type { IrExpr, IrFunction, IrStmt, IrType } from "../../ir/ir.js";
|
||||
import { locOf } from "../program.js";
|
||||
import { npmStaticPackageOfPath } from "../npm-static.js";
|
||||
import type { Lowerer } from "./lowerer.js";
|
||||
@@ -164,7 +164,7 @@ export class DeferredModuleInitializers {
|
||||
stmt: () => true,
|
||||
expr: (expr) => {
|
||||
demandType(expr.type);
|
||||
if (expr.kind === "varRef" && this.pending.has(expr.localId)) demanded.add(expr.localId);
|
||||
if (readsBinding(expr) && this.pending.has(expr.localId)) demanded.add(expr.localId);
|
||||
if (expr.kind === "call") {
|
||||
const owner = classHelpers.get(expr.callee);
|
||||
if (owner) {
|
||||
@@ -186,7 +186,7 @@ export class DeferredModuleInitializers {
|
||||
stmt: () => true,
|
||||
expr: (expr) => {
|
||||
demandType(expr.type);
|
||||
if (expr.kind === "varRef" && this.pending.has(expr.localId)) demanded.add(expr.localId);
|
||||
if (readsBinding(expr) && this.pending.has(expr.localId)) demanded.add(expr.localId);
|
||||
if (expr.kind === "call") {
|
||||
const owner = classHelpers.get(expr.callee);
|
||||
if (owner) {
|
||||
@@ -241,6 +241,12 @@ export class DeferredModuleInitializers {
|
||||
}
|
||||
}
|
||||
|
||||
/** A read of a binding's current value: a plain read, or an update
|
||||
* (`n++`, `--n`) that reads before it writes. */
|
||||
function readsBinding(expr: IrExpr): expr is IrExpr & { kind: "varRef" | "incDec" } {
|
||||
return expr.kind === "varRef" || expr.kind === "incDec";
|
||||
}
|
||||
|
||||
function registryInitializer(
|
||||
lowerer: Lowerer,
|
||||
expression: ts.Expression,
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5065-global-increment-initializers.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5066-global-compound-updates.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5067-global-updates-in-functions.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5068-global-var-let-const-updates.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5069-global-update-modules/counter.ts",
|
||||
"<repo>/tests/corpus/5069-global-update-modules/reexport.ts",
|
||||
"<repo>/tests/corpus/5069-global-update-modules/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Module-level bindings whose only later use is ++ or --: the initializer
|
||||
// must still run before the update reads the binding.
|
||||
function f(x: number): { done: boolean; v: number } {
|
||||
return { done: x > 3, v: x };
|
||||
}
|
||||
let tr = f(0);
|
||||
let n = 2;
|
||||
while (!tr.done) {
|
||||
console.log(tr.v);
|
||||
tr = f(n++);
|
||||
}
|
||||
|
||||
const id = (x: number): number => x;
|
||||
let a = 2;
|
||||
console.log("a++", id(a++));
|
||||
let b = 2;
|
||||
console.log("b--", id(b--));
|
||||
let c = 2;
|
||||
console.log("++c", id(++c));
|
||||
let d = 2;
|
||||
console.log("--d", id(--d));
|
||||
|
||||
// Updates in statement position and loop conditions.
|
||||
let ticks = 3;
|
||||
while (ticks-- > 0) console.log("tick");
|
||||
let up = -2;
|
||||
do console.log("up", up); while (++up < 1);
|
||||
let steps = 10;
|
||||
for (let i = 0; i < 3; i++) console.log("steps", steps--);
|
||||
|
||||
// Floating-point and negative starting values.
|
||||
let half = 0.5;
|
||||
console.log("half++", half++);
|
||||
let neg = -1;
|
||||
console.log("--neg", --neg);
|
||||
let big = 9007199254740991;
|
||||
console.log("big++", big++);
|
||||
|
||||
// BigInt updates.
|
||||
let count = 10n;
|
||||
console.log("count++", String(count++));
|
||||
let back = 1n;
|
||||
console.log("--back", String(--back));
|
||||
|
||||
// Several declarators in one statement.
|
||||
let x1 = 5,
|
||||
x2 = 6;
|
||||
console.log("x1++ x2--", x1++, x2--);
|
||||
@@ -0,0 +1,60 @@
|
||||
// Module-level bindings whose only later use is a compound, logical or
|
||||
// destructuring update.
|
||||
const show = (label: string, v: unknown): void => console.log(label, JSON.stringify(v));
|
||||
let e = 2;
|
||||
show("e += 1", (e += 1));
|
||||
let f = 2;
|
||||
show("f -= 3", (f -= 3));
|
||||
let g = 2;
|
||||
show("g *= 5", (g *= 5));
|
||||
let h = 2;
|
||||
show("h **= 3", (h **= 3));
|
||||
let i = 7;
|
||||
show("i %= 4", (i %= 4));
|
||||
let j = 6;
|
||||
show("j <<= 1", (j <<= 1));
|
||||
let k = 6;
|
||||
show("k |= 1", (k |= 1));
|
||||
let m = 12;
|
||||
show("m >>>= 2", (m >>>= 2));
|
||||
let s = "x";
|
||||
show("s += y", (s += "y"));
|
||||
let t = "";
|
||||
show("t += 1", (t += 1));
|
||||
|
||||
let present: number | undefined = 4 as number | undefined;
|
||||
show("present ??= 9", (present ??= 9));
|
||||
let absent: number | undefined = undefined as number | undefined;
|
||||
show("absent ??= 9", (absent ??= 9));
|
||||
let label: string | null = null as string | null;
|
||||
show("label ??= none", (label ??= "none"));
|
||||
let zero = 0;
|
||||
show("zero ||= 5", (zero ||= 5));
|
||||
let three = 3;
|
||||
show("three &&= 5", (three &&= 5));
|
||||
let emptyText = "";
|
||||
show("emptyText ||= fallback", (emptyText ||= "fallback"));
|
||||
let on = true;
|
||||
show("on &&= false", (on &&= false));
|
||||
|
||||
let big = 3n;
|
||||
show("big *= 4n", String((big *= 4n)));
|
||||
|
||||
let p = 1;
|
||||
let q = 10;
|
||||
show("[p, q] = [q, p]", ([p, q] = [q, p]));
|
||||
let first = 0;
|
||||
let rest: number[] = [];
|
||||
show("[first, ...rest]", ([first, ...rest] = [7, 8, 9]));
|
||||
let left = "l";
|
||||
let right = "r";
|
||||
show("({ left, right } = ...)", ({ left, right } = { left: right + "!", right: left + "?" }));
|
||||
|
||||
let arr = [1, 2, 3];
|
||||
show("arr[0]++", arr[0]++);
|
||||
let obj = { n: 1, s: "a" };
|
||||
show("obj.n += 2", (obj.n += 2));
|
||||
show("obj.s += b", (obj.s += "b"));
|
||||
let grid = [[1], [2]];
|
||||
grid[1][0] *= 10;
|
||||
show("grid[1][0] *= 10", grid);
|
||||
@@ -0,0 +1,63 @@
|
||||
// Module-level bindings read only by updates inside functions, closures,
|
||||
// class members, generators, async functions and callbacks.
|
||||
function takeTicket(): number {
|
||||
return ticket++;
|
||||
}
|
||||
let ticket = 100;
|
||||
console.log("ticket", takeTicket(), takeTicket());
|
||||
|
||||
const nextId = (): number => ++lastId;
|
||||
let lastId = 50;
|
||||
console.log("lastId", nextId(), nextId());
|
||||
|
||||
let total = 1;
|
||||
function accumulate(by: number): number {
|
||||
return (total *= by);
|
||||
}
|
||||
console.log("total", accumulate(2), accumulate(3));
|
||||
|
||||
class Pool {
|
||||
static take(): number {
|
||||
return free--;
|
||||
}
|
||||
grow(by: number): number {
|
||||
return (free += by);
|
||||
}
|
||||
get size(): number {
|
||||
return ++free - 1;
|
||||
}
|
||||
}
|
||||
let free = 7;
|
||||
console.log("free", Pool.take(), new Pool().grow(10), new Pool().size);
|
||||
|
||||
function* serials(): Generator<number, void, undefined> {
|
||||
for (let i = 0; i < 3; i++) yield serial++;
|
||||
}
|
||||
let serial = 1000;
|
||||
console.log("serial", [...serials()].join(","));
|
||||
|
||||
async function nextAsync(): Promise<number> {
|
||||
await null;
|
||||
return asyncCount++;
|
||||
}
|
||||
let asyncCount = 40;
|
||||
|
||||
let mapped = 0;
|
||||
const doubled = [1, 2, 3].map((x) => x * 2 + mapped++);
|
||||
console.log("mapped", doubled.join(","));
|
||||
|
||||
function makeCounter(): () => number {
|
||||
return () => shared--;
|
||||
}
|
||||
let shared = 3;
|
||||
const c1 = makeCounter();
|
||||
const c2 = makeCounter();
|
||||
console.log("shared", c1(), c2(), c1());
|
||||
|
||||
let fallback: string | undefined = "kept" as string | undefined;
|
||||
const pick = (): string => (fallback ??= "replaced");
|
||||
console.log("fallback", pick());
|
||||
|
||||
nextAsync()
|
||||
.then((v) => nextAsync().then((w) => console.log("asyncCount", v, w)))
|
||||
.catch(() => {});
|
||||
@@ -0,0 +1,44 @@
|
||||
// Top-level var, let and const bindings with later compound updates,
|
||||
// including var hoisting.
|
||||
var hits = 3;
|
||||
console.log("hits++", hits++);
|
||||
var level = 10;
|
||||
function lower(): number {
|
||||
return (level -= 4);
|
||||
}
|
||||
console.log("level", lower(), lower());
|
||||
|
||||
// A var updated only from a function called after its declaration.
|
||||
var late = 5;
|
||||
function early(): number {
|
||||
return late++;
|
||||
}
|
||||
console.log("late", early(), early());
|
||||
|
||||
// Redeclared var keeps one binding.
|
||||
var twice = 1;
|
||||
var twice = twice + 1;
|
||||
console.log("twice++", twice++);
|
||||
|
||||
// const containers updated in place.
|
||||
const counters = { a: 1, b: 2 };
|
||||
counters.a++;
|
||||
console.log("counters.b *= 3", (counters.b *= 3), counters.a);
|
||||
const list = [5, 6];
|
||||
console.log("list[1]--", list[1]--, list.join(","));
|
||||
const nested = { inner: { n: 0 } };
|
||||
console.log("nested.inner.n += 2", (nested.inner.n += 2));
|
||||
|
||||
// let updated in branches and loops only.
|
||||
let flips = 0;
|
||||
for (const v of [true, false, true]) if (v) flips++;
|
||||
console.log("flips", 0);
|
||||
let budget = 5;
|
||||
while (budget > 0) budget -= 2;
|
||||
console.log("budget", budget);
|
||||
let tally = 1;
|
||||
if (Math.random() < 2) tally += 41;
|
||||
console.log("tally done");
|
||||
let ratio = 1;
|
||||
for (let i = 0; i < 4; i++) ratio /= 2;
|
||||
console.log("ratio", 1);
|
||||
@@ -0,0 +1,20 @@
|
||||
// Module-private bindings used only by exported functions.
|
||||
let next = 10;
|
||||
let calls = 0;
|
||||
export function take(): number {
|
||||
calls++;
|
||||
return next++;
|
||||
}
|
||||
export function callsSoFar(): number {
|
||||
return ++calls - 1;
|
||||
}
|
||||
var seq = 5;
|
||||
export const bump = (): number => --seq;
|
||||
let name = "id";
|
||||
export function label(): string {
|
||||
return (name += "+");
|
||||
}
|
||||
let cache: number | undefined;
|
||||
export function cached(): number {
|
||||
return (cache ??= 99);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { take, callsSoFar, label, cached } from "./counter.ts";
|
||||
import { bump, scaled } from "./reexport.ts";
|
||||
console.log(take(), take(), callsSoFar(), bump(), bump());
|
||||
console.log(label(), label(), cached(), cached());
|
||||
console.log(scaled(), scaled(), take());
|
||||
@@ -0,0 +1,5 @@
|
||||
export { take, bump } from "./counter.ts";
|
||||
let local = 0.25;
|
||||
export function scaled(): number {
|
||||
return (local *= 2);
|
||||
}
|
||||
Reference in New Issue
Block a user