feat(codemode): support JSON callbacks (#38006)

This commit is contained in:
Aiden Cline 2026-07-20 22:25:14 -05:00 committed by GitHub
commit 065b108bba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 431 additions and 61 deletions

View file

@ -8,6 +8,7 @@ import {
GlobalNamespace,
IntrinsicReference,
InterpreterRuntimeError,
JsonMethodReference,
PromiseCapabilityFunction,
PromiseNamespace,
UriFunction,
@ -25,7 +26,6 @@ import {
isCodeModeValue,
} from "../values.js"
import { dateSetterArgumentCount, invokeDateMethod, invokeDateStatic } from "../stdlib/date.js"
import { invokeJsonMethod } from "../stdlib/json.js"
import { invokeMathMethod } from "../stdlib/math.js"
import { invokeNumberMethod, invokeNumberStatic } from "../stdlib/number.js"
import { invokeObjectMethod } from "../stdlib/object.js"
@ -54,6 +54,7 @@ export type SupportedCallback =
| UriFunction
| PromiseCapabilityFunction
| GlobalMethodReference
| JsonMethodReference
| IntrinsicReference
| ErrorConstructorReference
| GlobalNamespace
@ -65,6 +66,7 @@ export const isSupportedCallback = (value: unknown): value is SupportedCallback
value instanceof UriFunction ||
value instanceof PromiseCapabilityFunction ||
value instanceof GlobalMethodReference ||
value instanceof JsonMethodReference ||
value instanceof IntrinsicReference ||
value instanceof ErrorConstructorReference ||
// Callable namespaces dispatch like JS: Array/Object/Date/RegExp construct,
@ -168,7 +170,7 @@ export const invokeGlobalMethod = (ref: GlobalMethodReference, args: Array<unkno
if (ref.namespace === "Map" || ref.namespace === "Set" || ref.namespace === "URLSearchParams") {
throw new InterpreterRuntimeError(`${ref.namespace}.${ref.name} is not available in CodeMode.`, node)
}
return invokeJsonMethod(ref.name, args, node)
throw new InterpreterRuntimeError(`${ref.namespace}.${ref.name} is not available in CodeMode.`, node)
}
const requireDataArgument = (name: string, index: number, arg: unknown, node: AstNode): unknown => {

View file

@ -99,11 +99,15 @@ export class GlobalNamespace {
export class GlobalMethodReference {
constructor(
readonly namespace: GlobalNamespaceName | "Number" | "String",
readonly namespace: Exclude<GlobalNamespaceName, "JSON"> | "Number" | "String",
readonly name: string,
) {}
}
export class JsonMethodReference {
constructor(readonly name: "parse" | "stringify") {}
}
export class CoercionFunction {
constructor(readonly name: "Number" | "String" | "Boolean" | "parseInt" | "parseFloat" | "isFinite" | "isNaN") {}
}

View file

@ -7,6 +7,7 @@ import {
GlobalNamespace,
InterpreterRuntimeError,
IntrinsicReference,
JsonMethodReference,
PromiseCapabilityFunction,
PromiseInstanceMethodReference,
PromiseMethodReference,
@ -23,6 +24,7 @@ export const isRuntimeReference = (value: unknown): boolean =>
value instanceof IntrinsicReference ||
value instanceof GlobalNamespace ||
value instanceof GlobalMethodReference ||
value instanceof JsonMethodReference ||
value instanceof PromiseNamespace ||
value instanceof PromiseMethodReference ||
value instanceof PromiseInstanceMethodReference ||
@ -82,12 +84,7 @@ export const containsOpaqueReference = (value: unknown): boolean => {
}
// Reject cycles before mutation so later boundary walks remain safe.
export const rejectCircularInsertion = (
container: object,
value: unknown,
label: string,
node: AstNode,
): void => {
export const rejectCircularInsertion = (container: object, value: unknown, label: string, node: AstNode): void => {
const pending: Array<Iterator<unknown>> = [[value].values()]
const seen = new Set<object>()
while (pending.length > 0) {
@ -111,6 +108,7 @@ export const typeofValue = (value: unknown): string => {
value instanceof CoercionFunction ||
value instanceof IntrinsicReference ||
value instanceof GlobalMethodReference ||
value instanceof JsonMethodReference ||
value instanceof PromiseMethodReference ||
value instanceof PromiseInstanceMethodReference ||
value instanceof PromiseNamespace ||

View file

@ -19,6 +19,7 @@ import {
IntrinsicReference,
InterpreterRuntimeError,
isRecord,
JsonMethodReference,
type MemberReference,
OptionalShortCircuit,
PromiseCapabilityFunction,
@ -55,7 +56,7 @@ import { ScopeStack } from "./scope.js"
import { arrayMethods, mapMethods, mapStatics, setMethods, spreadItems } from "../stdlib/collections.js"
import { consoleMethods, formatConsoleMessage } from "../stdlib/console.js"
import { dateMethods, dateStatics } from "../stdlib/date.js"
import { jsonStatics } from "../stdlib/json.js"
import { invokeJsonMethod, jsonStatics, type JsonMethodName } from "../stdlib/json.js"
import { mathConstants, mathMethods } from "../stdlib/math.js"
import { numberConstants, numberMethods, numberStatics } from "../stdlib/number.js"
import { objectMethodsPreservingIdentity, objectStatics } from "../stdlib/object.js"
@ -102,7 +103,6 @@ import {
const globalStaticMembers: Partial<Record<GlobalNamespaceName, Set<string>>> = {
Object: objectStatics,
Math: mathMethods,
JSON: jsonStatics,
Array: arrayStatics,
console: consoleMethods,
Date: dateStatics,
@ -1624,6 +1624,9 @@ export class Interpreter<R> {
}
return boundedData(invokeGlobalMethod(callable, args, node), `${callable.namespace}.${callable.name} result`)
}
if (callable instanceof JsonMethodReference) {
return yield* invokeJsonMethod(self.runner, callable.name, args, node)
}
if (callable instanceof CoercionFunction) {
return boundedData(invokeCoercion(callable, args, node), `${callable.name} result`)
}
@ -1889,6 +1892,7 @@ export class Interpreter<R> {
| PromiseInstanceMethodReference
| IntrinsicReference
| GlobalMethodReference
| JsonMethodReference
| ComputedValue
| typeof OptionalShortCircuit
| undefined,
@ -1936,6 +1940,10 @@ export class Interpreter<R> {
if (objectValue.name === "Math" && mathConstants.has(key)) {
return new ComputedValue((Math as unknown as Record<string, number>)[key])
}
if (objectValue.name === "JSON") {
if (jsonStatics.has(key)) return new JsonMethodReference(key as JsonMethodName)
return new ComputedValue(undefined)
}
if (globalStaticMembers[objectValue.name]?.has(key)) {
return new GlobalMethodReference(objectValue.name, key)
}
@ -2065,7 +2073,8 @@ export class Interpreter<R> {
reference instanceof PromiseMethodReference ||
reference instanceof PromiseInstanceMethodReference ||
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference
reference instanceof GlobalMethodReference ||
reference instanceof JsonMethodReference
)
return reference
if (Array.isArray(reference.target)) {
@ -2100,6 +2109,7 @@ export class Interpreter<R> {
reference instanceof PromiseInstanceMethodReference ||
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference ||
reference instanceof JsonMethodReference ||
reference.target instanceof CodeModeURL
) {
throw new InterpreterRuntimeError("Only data fields may be deleted in CodeMode.", target, "InvalidDataValue")
@ -2127,7 +2137,8 @@ export class Interpreter<R> {
reference instanceof PromiseMethodReference ||
reference instanceof PromiseInstanceMethodReference ||
reference instanceof IntrinsicReference ||
reference instanceof GlobalMethodReference
reference instanceof GlobalMethodReference ||
reference instanceof JsonMethodReference
) {
throw new InterpreterRuntimeError("Only data fields may be assigned in CodeMode.", node)
}