feat(codemode): native coercion parity in interpreter (#37608)

This commit is contained in:
Aiden Cline 2026-07-18 12:01:10 -05:00 committed by GitHub
commit 5f437a09b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 323 additions and 48 deletions

View file

@ -23,6 +23,8 @@ export const dateMethods = new Set([
"getTimezoneOffset",
])
export const dateStatics = new Set(["now", "parse", "UTC"])
export const invokeDateStatic = (name: string, args: Array<unknown>, node: AstNode): number => {
switch (name) {
case "now":

View file

@ -2,6 +2,8 @@ import { type AstNode, InterpreterRuntimeError, supportedSyntaxMessage } from ".
import { typeofValue } from "../interpreter/references.js"
import { copyIn, copyOut } from "../tool-runtime.js"
export const jsonStatics = new Set(["parse", "stringify"])
export const invokeJsonMethod = (name: string, args: Array<unknown>, node: AstNode): unknown => {
switch (name) {
case "stringify": {

View file

@ -6,6 +6,8 @@ import { boundedData, coerceToString } from "./value.js"
export const objectMethodsPreservingIdentity = new Set(["assign", "values", "entries", "fromEntries"])
export const objectStatics = new Set(["keys", "values", "entries", "hasOwn", "is", "assign", "fromEntries"])
export const invokeObjectMethod = (name: string, args: Array<unknown>, node: AstNode): unknown => {
const requireObject = (): Record<string, unknown> => {
const input = args[0]

View file

@ -19,6 +19,8 @@ export const escapeRegexHint =
'To match special characters like ( ) [ ] { } + * ? . literally, escape them with a backslash (e.g. "\\\\(") or test for them with String.includes instead.'
export const toHostRegex = (arg: unknown, method: string, node: AstNode, extraFlags = ""): RegExp => {
// Native parity: an undefined pattern behaves as an empty pattern.
if (arg === undefined) return new RegExp("", extraFlags)
if (arg instanceof CodeModeRegExp) return arg.regex
if (typeof arg === "string") {
try {

View file

@ -61,10 +61,19 @@ export const coerceToString = (value: unknown): string => {
export const coerceToNumber = (value: unknown): number => {
if (value instanceof CodeModeDate) return value.time
if (isCodeModeValue(value)) return Number.NaN
return value !== null && typeof value === "object" && !Array.isArray(value) ? Number.NaN : Number(value)
// Arrays coerce through our own string coercion: host Number(array) joins with host
// ToPrimitive, which throws on the null-prototype objects the interpreter produces.
if (Array.isArray(value)) return Number(coerceToString(value))
return value !== null && typeof value === "object" ? Number.NaN : Number(value)
}
export const invokeCoercion = (ref: CoercionFunction, args: Array<unknown>, node: AstNode): unknown => {
// Native: Number() is 0 and String() is "", unlike their undefined-argument forms; the
// other coercers match native through the undefined-argument path below.
if (args.length === 0) {
if (ref.name === "Number") return 0
if (ref.name === "String") return ""
}
const raw = args[0]
// Error values are plain SafeObjects; the boundedData path below would strip their brand.
if (ref.name === "String" && errorBrandName(raw) !== undefined) return coerceToString(raw)
@ -72,12 +81,16 @@ export const invokeCoercion = (ref: CoercionFunction, args: Array<unknown>, node
if (ref.name === "Boolean") return true
if (ref.name === "Number") return coerceToNumber(raw)
if (ref.name === "String") return coerceToString(raw)
if (ref.name === "isFinite") return Number.isFinite(coerceToNumber(raw))
if (ref.name === "isNaN") return Number.isNaN(coerceToNumber(raw))
if (ref.name === "parseInt") return parseInt(coerceToString(raw))
return parseFloat(coerceToString(raw))
}
const value = boundedData(raw, `${ref.name} input`)
if (ref.name === "Number") return coerceToNumber(value)
if (ref.name === "Boolean") return Boolean(value)
if (ref.name === "isFinite") return Number.isFinite(coerceToNumber(value))
if (ref.name === "isNaN") return Number.isNaN(coerceToNumber(value))
if (ref.name === "parseInt") {
const radix = args[1]
if (radix !== undefined && typeof radix !== "number") {