///|
fn interpreter_callable_is_constructor(data : ObjectData) -> Bool {
data.bag.properties.contains("prototype") &&
data.class_name != "AsyncFunction" &&
data.class_name != "GeneratorFunction" &&
data.class_name != "AsyncGeneratorFunction"
}
///|
/// Check if a Value is a constructor (has [[Construct]] internal method)
pub fn is_constructor_value(v : Value) -> Bool {
match v {
Object(data) =>
match data.callable {
Some(UserFunc(fd)) => !fd.is_method
Some(UserFuncExt(fd)) => !fd.is_method
Some(NativeCallable(_, _)) => true
Some(NativeCallableWithContext(_, _)) => true
Some(ClassConstructor(_)) => true
Some(ConstructorOnlyCallable(_, _)) => true
Some(BoundFunc(target, _, _)) => is_constructor_value(target)
Some(NonConstructableCallable(_, _)) => false
Some(NonConstructableInterpreterCallable(_, _)) => false
Some(MethodCallable(_, _)) => false
Some(InterpreterCallable(_, _))
| Some(InterpreterCallableWithContext(_, _)) =>
// InterpreterCallable is a constructor if it has a prototype property
// (builtin constructors like Map, Set, Promise have prototype).
// Generator functions are the spec-visible exception: they have a
// prototype property but no [[Construct]].
interpreter_callable_is_constructor(data)
Some(ExecutorCallable(executable)) => executable.is_constructable()
Some(ArrowFunc(_)) | Some(ArrowFuncExt(_)) => false
Some(FuncCallMethod(_)) | Some(FuncApplyMethod(_)) => false
None => false
}
Proxy(pd) => pd.is_constructor
_ => false
}
}
///|
/// Check if a Value is a function (has a callable)
pub fn is_function_value(v : Value) -> Bool {
match v {
Object(data) => data.callable is Some(_)
_ => false
}
}
///|
/// Check if a Value is an object per ES Type(v) is Object (ยง6.1.7).
/// Returns true for any Value variant that represents a JavaScript object
/// reference; false for language primitives (Number, String_, Bool, Null,
/// Undefined, Symbol, BigInt).
pub fn is_object_value(v : Value) -> Bool {
match v {
Object(_) | Array(_) | Map(_) | Set(_) | Promise(_) | Proxy(_) => true
_ => false
}
}
///|
/// Per-interpreter symbol state: ID counter, all-symbols table,
/// and the Symbol.for() / Symbol.keyFor() registry.
/// Passed through setup functions so that closures can capture it
/// without needing an Interpreter reference.
pub(all) struct SymbolState {
symbol_id_counter : Ref[Int]
all_symbols : Map[Int, SymbolData]
global_symbol_registry : Map[String, SymbolData]
symbol_registry_reverse : Map[Int, String]
well_known_symbols_cache : Ref[WellKnownSymbols?]
}
///|
pub fn SymbolState::new() -> SymbolState {
{
symbol_id_counter: { val: 0 },
all_symbols: Map([]),
global_symbol_registry: Map([]),
symbol_registry_reverse: Map([]),
well_known_symbols_cache: { val: None },
}
}
///|
/// Create a new symbol with a unique ID, registering it in this SymbolState.
pub fn SymbolState::new_symbol(
self : SymbolState,
description : String?,
) -> SymbolData {
let id = self.symbol_id_counter.val
self.symbol_id_counter.val = id + 1
let sym : SymbolData = { id, description }
self.all_symbols[id] = sym
sym
}
///|
/// Get a symbol by its ID from this SymbolState.
pub fn SymbolState::get_symbol_by_id(
self : SymbolState,
id : Int,
) -> SymbolData? {
self.all_symbols.get(id)
}
///|
/// Realm-owned well-known symbol identities.
///
/// These values are allocated from the realm's SymbolState before user code can
/// create symbols, preserving the existing well-known symbol IDs while moving
/// ownership out of per-symbol module globals.
pub(all) struct WellKnownSymbols {
iterator : SymbolData
async_iterator : SymbolData
has_instance : SymbolData
is_concat_spreadable : SymbolData
to_primitive : SymbolData
to_string_tag : SymbolData
match_sym : SymbolData
match_all : SymbolData
replace : SymbolData
search : SymbolData
species : SymbolData
split : SymbolData
unscopables : SymbolData
}
///|
fn WellKnownSymbols::WellKnownSymbols(
symbols : SymbolState,
) -> WellKnownSymbols {
{
iterator: symbols.new_symbol(Some("Symbol.iterator")),
to_primitive: symbols.new_symbol(Some("Symbol.toPrimitive")),
to_string_tag: symbols.new_symbol(Some("Symbol.toStringTag")),
has_instance: symbols.new_symbol(Some("Symbol.hasInstance")),
is_concat_spreadable: symbols.new_symbol(Some("Symbol.isConcatSpreadable")),
species: symbols.new_symbol(Some("Symbol.species")),
match_sym: symbols.new_symbol(Some("Symbol.match")),
match_all: symbols.new_symbol(Some("Symbol.matchAll")),
replace: symbols.new_symbol(Some("Symbol.replace")),
search: symbols.new_symbol(Some("Symbol.search")),
split: symbols.new_symbol(Some("Symbol.split")),
unscopables: symbols.new_symbol(Some("Symbol.unscopables")),
async_iterator: symbols.new_symbol(Some("Symbol.asyncIterator")),
}
}
///|
pub fn SymbolState::well_known_symbols(self : SymbolState) -> WellKnownSymbols {
match self.well_known_symbols_cache.val {
Some(symbols) => symbols
None => {
let symbols = WellKnownSymbols(self)
self.well_known_symbols_cache.val = Some(symbols)
symbols
}
}
}
///|
/// Get the Symbol.toStringTag value by walking the prototype chain.
/// Checks symbol properties and evaluates getter descriptors if present.
/// Per spec, errors thrown by @@toStringTag getters propagate to the caller.
pub fn get_tostringtag_value(
data : ObjectData,
well_known_symbols : WellKnownSymbols,
) -> String? raise Error {
let sym = well_known_symbols.to_string_tag
// Walk the object and its prototype chain
let mut current : Value = Object(data)
while true {
match current {
Object(obj_data) => {
// Check for getter descriptor on symbol property
match obj_data.bag.symbol_descriptors.get(sym.id) {
Some(desc) =>
match desc.getter {
Some(getter_fn) =>
// Call the getter with the original object as `this`
match getter_fn {
Object(getter_data) =>
match getter_data.callable {
Some(MethodCallable(_, f)) => {
let result = f(Object(data), [])
match result {
String_(s) => return Some(s)
_ => return None
}
}
Some(NativeCallable(_, f))
| Some(NonConstructableCallable(_, f)) => {
let result = f([Object(data)])
match result {
String_(s) => return Some(s)
_ => return None
}
}
Some(NativeCallableWithContext(_, f)) => {
let result = f(Call, [Object(data)])
match result {
String_(s) => return Some(s)
_ => return None
}
}
_ => return None
}
_ => return None
}
None => ()
}
None => ()
}
// Check direct symbol property value
match obj_data.bag.symbol_properties.get(sym.id) {
Some(String_(s)) => return Some(s)
Some(_) => return None // non-string toStringTag, short-circuit per spec
None => ()
}
current = obj_data.prototype
}
_ => break
}
}
None
}