mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Format checked-dynamic arguments under %d and %i as Node does (#1488)
This commit is contained in:
@@ -2011,6 +2011,47 @@ function formatOArg(lowerer: Lowerer, node: ts.Expression, depth: number, loc: S
|
||||
return inspectExpr(lowerer, value.type, value, numLit(0, loc), numLit(depth, loc), loc);
|
||||
}
|
||||
|
||||
/** %d and %i over a checked-dynamic argument, as Node's formatWithOptions
|
||||
* reads it: a bigint prints as `${n}n`, a symbol as NaN, and any other
|
||||
* value as `number` (Number(arg) for %d, parseInt(String(arg)) for %i)
|
||||
* formatted. The argument evaluates once. */
|
||||
function formatDynInteger(
|
||||
lowerer: Lowerer,
|
||||
value: IrExpr,
|
||||
number: (held: IrExpr) => IrExpr,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const held = lowerer.declareHiddenLocal("%formatArg", DYN);
|
||||
const ref = (): IrExpr => varRef(held.id, DYN, loc);
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "varDecl", localId: held.id, init: value, loc }],
|
||||
result: {
|
||||
kind: "ternary",
|
||||
cond: { kind: "dynTest", test: "bigint", value: ref(), type: BOOL, loc },
|
||||
then: {
|
||||
kind: "strConcat",
|
||||
left: { kind: "libCall", fn: "dyn.toStringCoerce", args: [ref()], type: STRING, loc },
|
||||
right: strLit("n", loc),
|
||||
type: STRING,
|
||||
loc,
|
||||
},
|
||||
else_: {
|
||||
kind: "ternary",
|
||||
cond: { kind: "dynTest", test: "symbol", value: ref(), type: BOOL, loc },
|
||||
then: strLit("NaN", loc),
|
||||
else_: { kind: "libCall", fn: "insp.f64", args: [number(ref())], type: STRING, loc },
|
||||
type: STRING,
|
||||
loc,
|
||||
},
|
||||
type: STRING,
|
||||
loc,
|
||||
},
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** %d and %i over the argument types Node formats without a number
|
||||
* conversion: a bigint prints as `${n}n` (formatBigInt) and a symbol as NaN;
|
||||
* null is Number(null) (0) under %d and parseInt("null") (NaN) under %i, and
|
||||
@@ -2141,19 +2182,20 @@ export function lowerFormatCall(
|
||||
};
|
||||
}
|
||||
// A checked-dynamic argument (`console.log('... actual %d.',
|
||||
// context.actual)` — test/common's exit report): VALIDATE it as a
|
||||
// number (dynCheck) and format. A non-number dyn value throws the
|
||||
// catchable TypeError where Node would print its ToNumber (NaN
|
||||
// for objects) — it throws instead of returning a wrong result
|
||||
// (SEMANTICS.md).
|
||||
// context.actual)` — test/common's exit report): Number(arg).
|
||||
if (value.type.kind === "dyn") {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "insp.f64",
|
||||
args: [{ kind: "dynCheck", value, type: F64, loc }],
|
||||
type: STRING,
|
||||
return formatDynInteger(
|
||||
lowerer,
|
||||
value,
|
||||
(held) => ({
|
||||
kind: "libCall",
|
||||
fn: "dyn.numberConstructor",
|
||||
args: [held],
|
||||
type: F64,
|
||||
loc,
|
||||
}),
|
||||
loc,
|
||||
};
|
||||
);
|
||||
}
|
||||
lowerer.noLowering(
|
||||
`util.format %d of '${lowerer.fmt(value.type)}' values`,
|
||||
@@ -2185,6 +2227,24 @@ export function lowerFormatCall(
|
||||
};
|
||||
return { kind: "libCall", fn: "insp.f64", args: [parsed], type: STRING, loc };
|
||||
}
|
||||
// parseInt(String(arg)) over a checked-dynamic argument.
|
||||
if (value.type.kind === "dyn") {
|
||||
return formatDynInteger(
|
||||
lowerer,
|
||||
value,
|
||||
(held) => ({
|
||||
kind: "libCall",
|
||||
fn: "num.parseInt",
|
||||
args: [
|
||||
{ kind: "libCall", fn: "dyn.toStringCoerce", args: [held], type: STRING, loc },
|
||||
numLit(0, loc),
|
||||
],
|
||||
type: F64,
|
||||
loc,
|
||||
}),
|
||||
loc,
|
||||
);
|
||||
}
|
||||
lowerer.noLowering(`util.format %i of '${lowerer.fmt(value.type)}' values`, node);
|
||||
break;
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6356-format-integer-dyn.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// util.format's %d and %i over checked-dynamic arguments, as Node reads
|
||||
// them: a bigint prints as `${n}n`, a symbol as NaN, and any other value
|
||||
// as Number(arg) (%d) or parseInt(String(arg)) (%i).
|
||||
import { format } from "node:util";
|
||||
const values: unknown[] = [{}, [5], [1, 2], "12px", " 0x1f", "", null, undefined, true, 3.9, -0, 10n, Symbol("s"), new Date(0), { valueOf: () => 7 }, NaN, "1e3"];
|
||||
for (const v of values) console.log(format("%d|%i", v, v));
|
||||
console.log(format("%d and %i", 2n ** 70n, -5n));
|
||||
const report: unknown = { actual: 3 };
|
||||
console.log("... actual %d.", (report as { actual: unknown }).actual);
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2000, "san": 2000 }
|
||||
Reference in New Issue
Block a user