Files
scriptc/tests/corpus/missing-read-number-conversions.ts
T
Malte Ubl e52688095d Convert missing number arguments with ToNumber
String.fromCharCode, String.fromCodePoint, the global isNaN and isFinite,
DataView offsets and values, and typed-array slice/subarray bounds took a
`number | undefined` value (an out-of-range read the checker types as
number) through a checked narrow, which threw "undefined is not
representable" or refused to compile. Node converts undefined with
ToNumber: code unit 0, a RangeError for fromCodePoint, isNaN true, offset
0; an undefined slice end is the length.

The Math argument conversion becomes a shared helper that also handles
field reads and any other value lowered as a number-or-undefined union.
2026-10-10 14:46:53 -07:00

96 lines
3.4 KiB
TypeScript

// Builtins that convert a number argument with ToNumber see a missing
// read (an out-of-range typed-array or array read the checker types as
// `number`) as NaN, ToNumber's answer for undefined: String.fromCharCode
// yields code unit 0, fromCodePoint throws a RangeError, Math answers NaN,
// isNaN is true. The missing value reaches them from a module binding, a
// local, a class field and a record field.
const base: number = process.argv.length;
const chars = new Uint16Array(4);
chars[0] = 108;
chars[1] = 66;
const nums: number[] = [7];
const far = base + 100;
const missing = chars[far];
const present = chars[1];
const missingNum = nums[far];
function show(label: string, value: unknown): void {
console.log(label, typeof value === "string" ? JSON.stringify(value) : value);
}
function attempt(label: string, run: () => unknown): void {
try {
show(label, run());
} catch (e) {
console.log(label, "threw", (e as Error).name, (e as Error).message);
}
}
// String.fromCharCode / fromCodePoint.
show("fcc module", String.fromCharCode(chars[0], missing));
show("fcc single", String.fromCharCode(missing));
show("fcc present", String.fromCharCode(chars[0], present));
show("fcc array", String.fromCharCode(missingNum, 65));
attempt("fcp module", () => String.fromCodePoint(missing));
show("fcp present", String.fromCodePoint(present));
// Math functions.
show("abs", Math.abs(missing));
show("floor", Math.floor(missingNum));
show("max", Math.max(missing, 1));
show("min3", Math.min(1, 2, missing));
show("imul", Math.imul(missing, 3));
show("pow", Math.pow(present, 2));
show("hypot", Math.hypot(missing, 3));
// The global number predicates convert; the Number statics do not.
show("isNaN", isNaN(missing));
show("isNaN present", isNaN(present));
show("isFinite", isFinite(missing));
show("isFinite present", isFinite(present));
show("Number.isNaN", Number.isNaN(missing));
show("Number.isFinite", Number.isFinite(missing));
// DataView offsets (ToIndex) and values (ToNumber).
const view = new DataView(new ArrayBuffer(4));
view.setUint8(1, 9);
show("dv get", view.getUint8(missing));
view.setUint8(missing, 5);
show("dv set offset", view.getUint8(0));
view.setUint8(2, missing);
show("dv set value", view.getUint8(2));
// Typed-array ranges: a missing start is 0, a missing end is the length.
show("slice start", chars.slice(missing).length);
show("slice end", chars.slice(1, missing).length);
show("subarray start", chars.subarray(missing).length);
show("subarray end", chars.subarray(2, missing).length);
// The same reads held in a local, a class field and a record field.
function local(i: number, label: string): void {
let code: number | undefined = 0;
code = chars[i];
show(`local fcc ${label}`, String.fromCharCode(97, code));
show(`local max ${label}`, Math.max(code, 1));
show(`local isNaN ${label}`, isNaN(code));
}
local(far, "missing");
local(1, "present");
class Cursor {
code: number | undefined = 0;
}
const cursor = new Cursor();
cursor.code = chars[far];
show("field fcc", String.fromCharCode(97, cursor.code));
show("field abs", Math.abs(cursor.code));
cursor.code = chars[1];
show("field fcc present", String.fromCharCode(97, cursor.code));
const record: { code: number | undefined } = { code: 0 };
record.code = chars[far];
show("record fcc", String.fromCharCode(97, record.code));
show("record max", Math.max(record.code, 1));
show("record isNaN", isNaN(record.code));