mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Keep replace callback parameters checked-dynamic where parseInt or parseFloat expects another type
parseFloat and the text of parseInt take a string, and the radix of parseInt takes a number. An unannotated offset passed as their text, or a capture passed as the radix, is no longer bound to its value's type; it stays checked-dynamic, so the call compiles as it did before the binding.
This commit is contained in:
@@ -115,7 +115,10 @@ const STRING_MEMBERS: ReadonlySet<string> = new Set([
|
||||
|
||||
/** Whether every read of `symbol` in `body` is one that means the same
|
||||
* for an `any` value and for a `kind` value, and lowers for both: a
|
||||
* template span, a Number/String/parseInt/parseFloat argument, a listed
|
||||
* template span, the argument of Number or String, the text argument of
|
||||
* parseInt or parseFloat for a string and the radix of parseInt for a
|
||||
* number (each a parameter of that type, which an `any` value converts
|
||||
* to), a listed
|
||||
* string member, `+`, strict equality, arithmetic and comparisons of
|
||||
* numbers, a returned value, and a string key of an element access on a
|
||||
* string-indexed record or an `any` value. Anything else keeps the
|
||||
@@ -144,13 +147,20 @@ function readsFit(
|
||||
return shape?.indexValue !== undefined && !shape.tuple;
|
||||
}
|
||||
if (ts.isArrowFunction(parent)) return parent.body === node;
|
||||
if (ts.isCallExpression(parent))
|
||||
return (
|
||||
parent.expression !== node &&
|
||||
["Number", "String", "parseInt", "parseFloat"].some((global) =>
|
||||
lowerer.isStdlibGlobal(parent.expression, global),
|
||||
)
|
||||
);
|
||||
if (ts.isCallExpression(parent)) {
|
||||
if (parent.expression === node) return false;
|
||||
const position = parent.arguments.nodes.indexOf(node as ts.Expression);
|
||||
if (
|
||||
lowerer.isStdlibGlobal(parent.expression, "Number") ||
|
||||
lowerer.isStdlibGlobal(parent.expression, "String")
|
||||
)
|
||||
return position === 0;
|
||||
if (lowerer.isStdlibGlobal(parent.expression, "parseFloat"))
|
||||
return position === 0 && kind === "string";
|
||||
if (lowerer.isStdlibGlobal(parent.expression, "parseInt"))
|
||||
return position === 0 ? kind === "string" : position === 1 && kind === "f64";
|
||||
return false;
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(parent)) {
|
||||
const member = parent.name.text;
|
||||
if (kind !== "string" || parent.expression !== node || !STRING_MEMBERS.has(member))
|
||||
|
||||
@@ -69,6 +69,8 @@ console.log(text.replace(/(a)|(b)/g, (m, a, b) => \`\${m}\${a}\${b}\`));
|
||||
console.log(text.replace(/(a)(b)?/g, (m: string, a: string, b: string | undefined, at: number) => m + a + (b ?? "") + at));
|
||||
const half = (n: number): number => n / 2;
|
||||
console.log(text.replace(/b/g, (m, at) => String(half(at))));
|
||||
console.log(text.replace(/b/g, (m, at) => String(parseFloat(at) + parseInt(at))));
|
||||
console.log(text.replace(/(\\d)/g, (m, d) => String(parseInt("11", d))));
|
||||
console.log(text.replace(/(a)(b)(c)/g, (m, a, b, c, at, all, extra) => m + a + b + c + at + all + extra));
|
||||
`);
|
||||
expect(replacerCalls(llvm)).toEqual({
|
||||
@@ -76,7 +78,10 @@ console.log(text.replace(/(a)(b)(c)/g, (m, a, b, c, at, all, extra) => m + a + b
|
||||
// two captures a match can leave unset (bits 1 and 2);
|
||||
// \`string | undefined\` at capture 2 (bit 8 + 2); an offset passed to a
|
||||
// number parameter keeps its checked-dynamic box (bit 1).
|
||||
direct: ["2:0", "2:-2147483648", "3:6", "4:1024", "2:2"],
|
||||
// parseFloat and parseInt take text, and parseInt's radix is a
|
||||
// number: the offset passed as text and the capture passed as the
|
||||
// radix stay checked-dynamic (bit 1), so both calls compile.
|
||||
direct: ["2:0", "2:-2147483648", "3:6", "4:1024", "2:2", "2:2", "2:2"],
|
||||
// Seven parameters exceed the direct calls' limit.
|
||||
boxed: 1,
|
||||
});
|
||||
|
||||
@@ -51,6 +51,11 @@ console.log("ab".replace(/(?<a>a)|(?<b>b)/g, (m, a, b, at, all, groups) => `${m}
|
||||
console.log("ab".replace(/b/, (m, at, all, extra, more) => `${m}:${at}:${all}:${extra}:${more}`));
|
||||
console.log("ab".replace(/(a)(b)/, (m, a, b, at, all, extra, more) => `${m}${a}${b}${at}${all}${extra}${more}`));
|
||||
|
||||
// An offset passed to parseFloat converts with ToString, and captures
|
||||
// passed to parseInt and parseFloat are their text.
|
||||
console.log("a1b22".replace(/\d+/g, (m, at) => `<${parseFloat(at) + parseInt(m, at + 10)}>`));
|
||||
console.log("1.5 2e1".replace(/(\S+)/g, (_m, d) => String(parseFloat(d) + parseInt(d) + Number(d))));
|
||||
|
||||
// Results that are not strings convert with ToString.
|
||||
const counts: Record<string, any> = { a: 1, b: true, c: null, d: undefined, e: { toString: () => "E" } };
|
||||
console.log("a b c d e".replace(/\w/g, (m) => counts[m]));
|
||||
|
||||
Reference in New Issue
Block a user