mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Evaluate an fs argument inside satisfies once after a URL path
The arguments after a URL path that are evaluated ahead of the conversion were keyed by the expression without its parentheses. When the fs lowering read through a satisfies wrapper, it lowered the inner expression again, so fs.chmodSync(url, mode() satisfies number) called mode() twice. The saved arguments are now keyed and looked up without their type-only wrappers (satisfies, as, type assertions, !), so each runs once.
This commit is contained in:
@@ -15,6 +15,7 @@ import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { sideEffectFreeOptionValue } from "../surfaces.js";
|
||||
import { stripTypeWrappers } from "../typed-arguments.js";
|
||||
import { type IrExpr, type IrStmt, STRING, URL_T } from "../../../ir/ir.js";
|
||||
|
||||
/** The fs functions whose path parameters accept a URL: the path
|
||||
@@ -142,8 +143,9 @@ function checkUrlPaths(
|
||||
for (let i = first + 1; i < args.length; i++) if (!sideEffectFreeOptionValue(args[i]!)) last = i;
|
||||
const staged: ts.Expression[] = [];
|
||||
for (let i = first + 1; i <= last; i++) {
|
||||
let e = args[i]!;
|
||||
while (ts.isParenthesizedExpression(e)) e = e.expression;
|
||||
// Keyed without type-only wrappers (`satisfies`, `as`, `!`), so a
|
||||
// lowering that reads through them finds the saved value.
|
||||
const e = stripTypeWrappers(args[i]!);
|
||||
// Literals are read by their members, so a whole-value slot cannot
|
||||
// hold them.
|
||||
if (ts.isObjectLiteralExpression(e) || ts.isArrayLiteralExpression(e)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { onlyForwarded } from "./typed-arguments.js";
|
||||
import { onlyForwarded, stripTypeWrappers } from "./typed-arguments.js";
|
||||
import {
|
||||
needsGeneralNodeStreams,
|
||||
noteUntypedStreamFlow,
|
||||
@@ -12046,11 +12046,11 @@ export class Lowerer {
|
||||
return this.stagedArgument(expr) ?? lowerExpr(this, expr);
|
||||
}
|
||||
|
||||
/** The saved value of a staged fs argument (stagedArguments), or null for
|
||||
* every other expression. */
|
||||
/** The saved value of a staged fs argument (stagedArguments, keyed
|
||||
* without type-only wrappers), or null for every other expression. */
|
||||
private stagedArgument(node: ts.Expression): IrExpr | null {
|
||||
if (this.stagedArguments.size === 0) return null;
|
||||
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
||||
node = stripTypeWrappers(node);
|
||||
if (!this.stagedArguments.has(node)) return null;
|
||||
const slot = this.stagedArguments.get(node);
|
||||
if (!slot)
|
||||
|
||||
@@ -103,6 +103,16 @@ console.log("uid", uid);
|
||||
uid = -1;
|
||||
attempt("chown order scheme", () => fs.chownSync(new URL("http://a/"), uid, bumpUid()));
|
||||
console.log("uid", uid);
|
||||
// A later argument with effects inside a type-only wrapper runs once.
|
||||
let modeCalls = 0;
|
||||
function countedMode(): number {
|
||||
modeCalls++;
|
||||
return 0o600;
|
||||
}
|
||||
attempt("satisfies", () => fs.chmodSync(file, countedMode() satisfies number));
|
||||
attempt("as", () => fs.chmodSync(file, countedMode() as number));
|
||||
attempt("non-null", () => fs.chmodSync(file, countedMode()!));
|
||||
console.log("mode calls", modeCalls);
|
||||
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
console.log("done", fs.existsSync(dir));
|
||||
|
||||
Reference in New Issue
Block a user