@@ -10,6 +10,7 @@ import {
1010 ErrorConstructorReference ,
1111 GlobalMethodReference ,
1212 GlobalNamespace ,
13+ type GlobalNamespaceName ,
1314 getArray ,
1415 getBoolean ,
1516 getNode ,
@@ -34,7 +35,7 @@ import {
3435 UriFunction ,
3536} from "./model.js"
3637import { caughtErrorValue , constructErrorValue } from "./errors.js"
37- import { type CallbackRunner , invokeArrayFrom , invokeGlobalMethod , invokeIntrinsic } from "./methods.js"
38+ import { arrayStatics , type CallbackRunner , invokeArrayFrom , invokeGlobalMethod , invokeIntrinsic } from "./methods.js"
3839import {
3940 constructPromise ,
4041 invokePromiseInstanceMethod ,
@@ -46,17 +47,19 @@ import { containsOpaqueReference, isRuntimeReference, rejectCircularInsertion, t
4647import { ScopeStack } from "./scope.js"
4748import { arrayMethods , mapMethods , setMethods , spreadItems } from "../stdlib/collections.js"
4849import { consoleMethods , formatConsoleMessage } from "../stdlib/console.js"
49- import { dateMethods } from "../stdlib/date.js"
50- import { mathConstants } from "../stdlib/math.js"
50+ import { dateMethods , dateStatics } from "../stdlib/date.js"
51+ import { jsonStatics } from "../stdlib/json.js"
52+ import { mathConstants , mathMethods } from "../stdlib/math.js"
5153import { numberConstants , numberMethods , numberStatics } from "../stdlib/number.js"
52- import { objectMethodsPreservingIdentity } from "../stdlib/object.js"
54+ import { objectMethodsPreservingIdentity , objectStatics } from "../stdlib/object.js"
5355import { promiseStatics } from "../stdlib/promise.js"
5456import { escapeRegexHint , regexpMethods , regexpProperties , regexFailureReason } from "../stdlib/regexp.js"
5557import { stringMethods , stringStatics } from "../stdlib/string.js"
5658import {
5759 urlMethods ,
5860 urlProperties ,
5961 urlSearchParamsMethods ,
62+ urlStatics ,
6063 urlWritableProperties ,
6164 invokeUriFunction ,
6265 uriArgument ,
@@ -83,6 +86,32 @@ import {
8386 CodeModeURLSearchParams ,
8487} from "../values.js"
8588
89+ const globalStaticMembers : Partial < Record < GlobalNamespaceName , Set < string > > > = {
90+ Object : objectStatics ,
91+ Math : mathMethods ,
92+ JSON : jsonStatics ,
93+ Array : arrayStatics ,
94+ console : consoleMethods ,
95+ Date : dateStatics ,
96+ URL : urlStatics ,
97+ }
98+
99+ const calleeDescription = ( callee : AstNode ) : string => {
100+ if ( callee . type === "Identifier" ) return getString ( callee , "name" )
101+ if ( callee . type === "MemberExpression" ) {
102+ const object = getNode ( callee , "object" )
103+ const property = getNode ( callee , "property" )
104+ const key =
105+ callee . computed !== true && property . type === "Identifier"
106+ ? getString ( property , "name" )
107+ : property . type === "Literal" && typeof property . value === "string"
108+ ? property . value
109+ : undefined
110+ if ( object . type === "Identifier" && key !== undefined ) return `${ getString ( object , "name" ) } .${ key } `
111+ }
112+ return "The called value"
113+ }
114+
86115const instanceofValue = ( lhs : unknown , rhs : unknown , node : AstNode ) : boolean => {
87116 if ( rhs instanceof ErrorConstructorReference ) {
88117 const brand = errorBrandName ( lhs )
@@ -199,6 +228,8 @@ export class Interpreter<R> {
199228 globalScope . set ( "console" , { mutable : false , value : new GlobalNamespace ( "console" ) } )
200229 globalScope . set ( "parseInt" , { mutable : false , value : new CoercionFunction ( "parseInt" ) } )
201230 globalScope . set ( "parseFloat" , { mutable : false , value : new CoercionFunction ( "parseFloat" ) } )
231+ globalScope . set ( "isFinite" , { mutable : false , value : new CoercionFunction ( "isFinite" ) } )
232+ globalScope . set ( "isNaN" , { mutable : false , value : new CoercionFunction ( "isNaN" ) } )
202233 globalScope . set ( "Date" , { mutable : false , value : new GlobalNamespace ( "Date" ) } )
203234 globalScope . set ( "RegExp" , { mutable : false , value : new GlobalNamespace ( "RegExp" ) } )
204235 globalScope . set ( "Map" , { mutable : false , value : new GlobalNamespace ( "Map" ) } )
@@ -1454,10 +1485,23 @@ export class Interpreter<R> {
14541485 throw new InterpreterRuntimeError ( `Unsupported update operator '${ operator } '.` , node )
14551486 }
14561487
1488+ // CodeMode numeric coercion, not host Number(): null-prototype data objects would make
1489+ // the host throw during ToPrimitive, and opaque runtime references must reject clearly.
1490+ const operand = ( current : unknown ) : number => {
1491+ if ( containsOpaqueReference ( current ) ) {
1492+ throw new InterpreterRuntimeError (
1493+ `'${ operator } ' requires a data value in CodeMode.` ,
1494+ argument ,
1495+ "InvalidDataValue" ,
1496+ )
1497+ }
1498+ return coerceToNumber ( current )
1499+ }
1500+
14571501 if ( argument . type === "Identifier" ) {
14581502 return Effect . sync ( ( ) => {
14591503 const name = getString ( argument , "name" )
1460- const current = Number ( this . scopes . get ( name , argument ) )
1504+ const current = operand ( this . scopes . get ( name , argument ) )
14611505 const next = current + increment
14621506 this . scopes . set ( name , next , argument )
14631507 return prefix ? next : current
@@ -1466,7 +1510,7 @@ export class Interpreter<R> {
14661510
14671511 if ( argument . type === "MemberExpression" ) {
14681512 return this . modifyMember ( argument , ( current ) => {
1469- const value = Number ( current )
1513+ const value = operand ( current )
14701514 const next = value + increment
14711515 return Effect . succeed ( { write : true , next, result : prefix ? next : value } )
14721516 } )
@@ -1563,6 +1607,9 @@ export class Interpreter<R> {
15631607 callable . settle ( args [ 0 ] )
15641608 return undefined
15651609 }
1610+ if ( callable === undefined || callable === null ) {
1611+ throw new InterpreterRuntimeError ( `${ calleeDescription ( callee ) } is not a function.` , callee ) . as ( "TypeError" )
1612+ }
15661613 throw new InterpreterRuntimeError ( "Only tools are callable in CodeMode." , callee )
15671614 } )
15681615 }
@@ -1833,16 +1880,18 @@ export class Interpreter<R> {
18331880 }
18341881
18351882 if ( objectValue instanceof GlobalNamespace ) {
1836- if ( typeof key !== "string" || isBlockedMember ( key ) ) {
1837- throw new InterpreterRuntimeError (
1838- `${ objectValue . name } .${ String ( key ) } is not available in CodeMode.` ,
1839- propertyNode ,
1840- )
1883+ if ( typeof key === "string" && isBlockedMember ( key ) ) {
1884+ throw new InterpreterRuntimeError ( `${ objectValue . name } .${ key } is not available in CodeMode.` , propertyNode )
18411885 }
1886+ if ( typeof key !== "string" ) return new ComputedValue ( undefined )
18421887 if ( objectValue . name === "Math" && mathConstants . has ( key ) ) {
18431888 return new ComputedValue ( ( Math as unknown as Record < string , number > ) [ key ] )
18441889 }
1845- return new GlobalMethodReference ( objectValue . name , key )
1890+ if ( globalStaticMembers [ objectValue . name ] ?. has ( key ) ) {
1891+ return new GlobalMethodReference ( objectValue . name , key )
1892+ }
1893+ // Unknown static members read as undefined so feature detection works like native JS.
1894+ return new ComputedValue ( undefined )
18461895 }
18471896
18481897 if ( typeof objectValue === "string" ) {
@@ -1858,12 +1907,21 @@ export class Interpreter<R> {
18581907 return new ComputedValue ( undefined )
18591908 }
18601909
1861- if ( objectValue instanceof CoercionFunction && typeof key === "string" && ! isBlockedMember ( key ) ) {
1910+ if ( objectValue instanceof CoercionFunction ) {
1911+ if ( typeof key === "string" && isBlockedMember ( key ) ) {
1912+ throw new InterpreterRuntimeError ( `${ objectValue . name } .${ key } is not available in CodeMode.` , propertyNode )
1913+ }
1914+ if ( typeof key !== "string" ) return new ComputedValue ( undefined )
18621915 if ( objectValue . name === "Number" && numberConstants . has ( key ) ) {
18631916 return new ComputedValue ( ( Number as unknown as Record < string , number > ) [ key ] )
18641917 }
1865- if ( objectValue . name === "Number" && numberStatics . has ( key ) ) return new GlobalMethodReference ( "Number" , key )
1866- if ( objectValue . name === "String" && stringStatics . has ( key ) ) return new GlobalMethodReference ( "String" , key )
1918+ if ( objectValue . name === "Number" && numberStatics . has ( key ) ) {
1919+ return new GlobalMethodReference ( "Number" , key )
1920+ }
1921+ if ( objectValue . name === "String" && stringStatics . has ( key ) ) {
1922+ return new GlobalMethodReference ( "String" , key )
1923+ }
1924+ return new ComputedValue ( undefined )
18671925 }
18681926
18691927 if ( objectValue instanceof CodeModeDate ) {
0 commit comments