///|
// ToPrimitive is a runtime-owned child operation. Its state is deliberately
// independent of any executor frame so property updates and coercing addition
// share the same lookup/call/fallback pipeline.
priv enum ExecutorToPrimitiveHint {
ExecutorToPrimitiveDefault
ExecutorToPrimitiveNumber
ExecutorToPrimitiveString
}
///|
fn executor_to_primitive_hint_string(hint : ExecutorToPrimitiveHint) -> String {
match hint {
ExecutorToPrimitiveDefault => "default"
ExecutorToPrimitiveNumber => "number"
ExecutorToPrimitiveString => "string"
}
}
///|
priv enum ExecutorToPrimitivePhase {
ExecutorToPrimitiveNeedInput(Value)
ExecutorToPrimitiveAwaitToPrimitiveLookup(Value)
ExecutorToPrimitiveHaveToPrimitiveMethod(Value, Value)
ExecutorToPrimitiveAwaitToPrimitiveCall
ExecutorToPrimitiveHaveToPrimitiveResult(Value)
ExecutorToPrimitiveAwaitValueOfLookup(Value)
ExecutorToPrimitiveHaveValueOfMethod(Value, Value)
ExecutorToPrimitiveAwaitValueOfCall(Value)
ExecutorToPrimitiveHaveValueOfResult(Value, Value)
ExecutorToPrimitiveAwaitToStringLookup(Value)
ExecutorToPrimitiveHaveToStringMethod(Value, Value)
ExecutorToPrimitiveAwaitToStringCall(Value)
ExecutorToPrimitiveHaveToStringResult(Value, Value)
ExecutorToPrimitiveComplete(Value)
}
///|
priv struct ExecutorToPrimitiveOperation {
hint : ExecutorToPrimitiveHint
loc : @token.Loc
mut tried_value_of : Bool
mut phase : ExecutorToPrimitivePhase
}
///|
fn ExecutorToPrimitiveOperation::ExecutorToPrimitiveOperation(
input : Value,
hint : ExecutorToPrimitiveHint,
loc : @token.Loc,
) -> ExecutorToPrimitiveOperation {
{
hint,
loc,
tried_value_of: false,
phase: ExecutorToPrimitiveNeedInput(input),
}
}
///|
fn ExecutorToPrimitiveOperation::begin_to_string_lookup(
self : ExecutorToPrimitiveOperation,
current : Value,
) -> ExecutorActivationStep {
self.phase = ExecutorToPrimitiveAwaitToStringLookup(current)
executor_activation_managed_property_get(current, "toString", self.loc)
}
///|
fn ExecutorToPrimitiveOperation::step(
self : ExecutorToPrimitiveOperation,
interp : Interpreter,
) -> ExecutorActivationStep raise Error {
match self.phase {
ExecutorToPrimitiveNeedInput(input) =>
if is_js_object(input) {
let to_primitive = interp.realm_state.well_known_symbols.to_primitive
self.phase = ExecutorToPrimitiveAwaitToPrimitiveLookup(input)
executor_activation_symbol_property_get(input, to_primitive, self.loc)
} else {
self.phase = ExecutorToPrimitiveComplete(input)
ExecutorActivationNormal(input)
}
ExecutorToPrimitiveHaveToPrimitiveMethod(current, to_primitive) =>
match to_primitive {
Undefined | Null =>
match self.hint {
ExecutorToPrimitiveString => self.begin_to_string_lookup(current)
ExecutorToPrimitiveDefault | ExecutorToPrimitiveNumber => {
self.tried_value_of = true
self.phase = ExecutorToPrimitiveAwaitValueOfLookup(current)
executor_activation_managed_property_get(
current,
"valueOf",
self.loc,
)
}
}
_ if is_callable(to_primitive) => {
self.phase = ExecutorToPrimitiveAwaitToPrimitiveCall
ExecutorActivationCall(
ExecutorCallRequest(
callee=to_primitive,
this_value=current,
args=[String_(executor_to_primitive_hint_string(self.hint))],
loc=self.loc,
),
)
}
_ =>
raise @errors.TypeError(
message="Symbol.toPrimitive is not a function",
)
}
ExecutorToPrimitiveHaveToPrimitiveResult(result) =>
if is_js_object(result) {
raise @errors.TypeError(
message="Cannot convert object to primitive value",
)
} else {
self.phase = ExecutorToPrimitiveComplete(result)
ExecutorActivationNormal(result)
}
ExecutorToPrimitiveHaveValueOfMethod(current, value_of) =>
if is_callable(value_of) {
self.phase = ExecutorToPrimitiveAwaitValueOfCall(current)
ExecutorActivationCall(
ExecutorCallRequest(
callee=value_of,
this_value=current,
args=[],
loc=self.loc,
),
)
} else if self.hint is ExecutorToPrimitiveString {
raise @errors.TypeError(
message="Cannot convert object to primitive value",
)
} else {
self.tried_value_of = true
self.begin_to_string_lookup(current)
}
ExecutorToPrimitiveHaveValueOfResult(current, value_of_result) =>
if is_js_object(value_of_result) {
if self.hint is ExecutorToPrimitiveString {
raise @errors.TypeError(
message="Cannot convert object to primitive value",
)
} else {
self.tried_value_of = true
self.begin_to_string_lookup(current)
}
} else {
self.phase = ExecutorToPrimitiveComplete(value_of_result)
ExecutorActivationNormal(value_of_result)
}
ExecutorToPrimitiveHaveToStringMethod(current, to_string) =>
if is_callable(to_string) {
self.phase = ExecutorToPrimitiveAwaitToStringCall(current)
ExecutorActivationCall(
ExecutorCallRequest(
callee=to_string,
this_value=current,
args=[],
loc=self.loc,
),
)
} else if self.hint is ExecutorToPrimitiveString && !self.tried_value_of {
self.tried_value_of = true
self.phase = ExecutorToPrimitiveAwaitValueOfLookup(current)
executor_activation_managed_property_get(current, "valueOf", self.loc)
} else {
raise @errors.TypeError(
message="Cannot convert object to primitive value",
)
}
ExecutorToPrimitiveHaveToStringResult(current, result) =>
if is_js_object(result) {
if self.hint is ExecutorToPrimitiveString && !self.tried_value_of {
self.tried_value_of = true
self.phase = ExecutorToPrimitiveAwaitValueOfLookup(current)
executor_activation_managed_property_get(current, "valueOf", self.loc)
} else {
raise @errors.TypeError(
message="Cannot convert object to primitive value",
)
}
} else {
self.phase = ExecutorToPrimitiveComplete(result)
ExecutorActivationNormal(result)
}
ExecutorToPrimitiveComplete(value) => {
ignore(value)
raise @errors.InternalError(
message="ToPrimitive operation stepped after completion",
)
}
ExecutorToPrimitiveAwaitToPrimitiveLookup(_)
| ExecutorToPrimitiveAwaitToPrimitiveCall
| ExecutorToPrimitiveAwaitValueOfLookup(_)
| ExecutorToPrimitiveAwaitValueOfCall(_)
| ExecutorToPrimitiveAwaitToStringLookup(_)
| ExecutorToPrimitiveAwaitToStringCall(_) =>
raise @errors.InternalError(
message="ToPrimitive operation stepped while awaiting a child completion",
)
}
}
///|
// ToPropertyKey is kept as a runtime child operation so computed deletion
// never performs guest-capable key conversion from the VM. The operation
// deliberately returns Symbols unchanged and applies ToString only after the
// String-hint ToPrimitive result is known to be non-Symbol.
priv enum ExecutorToPropertyKeyPhase {
ExecutorToPropertyKeyNeedInput(Value)
ExecutorToPropertyKeyAwaitToPrimitive(ExecutorToPrimitiveOperation)
ExecutorToPropertyKeyComplete(Value)
}
///|
priv struct ExecutorToPropertyKeyOperation {
loc : @token.Loc
mut phase : ExecutorToPropertyKeyPhase
}
///|
fn ExecutorToPropertyKeyOperation::ExecutorToPropertyKeyOperation(
input : Value,
loc : @token.Loc,
) -> ExecutorToPropertyKeyOperation {
{ loc, phase: ExecutorToPropertyKeyNeedInput(input), }
}
///|
fn finish_executor_to_property_key(
value : Value,
interp : Interpreter,
) -> Value raise Error {
match value {
Symbol(_) => value
_ => String_(to_js_string(value, interp=Some(interp)))
}
}
///|
// Primitive ToPropertyKey conversion has no guest-capable step. Computed
// call preparation uses this same runtime owner to seal a primitive key before
// deciding whether property lookup itself needs a managed request.
fn executor_to_property_key_primitive(value : Value) -> Value raise Error {
match value {
Symbol(_) => value
_ => String_(to_js_string(value))
}
}
///|
fn ExecutorToPropertyKeyOperation::step(
self : ExecutorToPropertyKeyOperation,
interp : Interpreter,
) -> ExecutorActivationStep raise Error {
match self.phase {
ExecutorToPropertyKeyNeedInput(input) =>
match input {
Symbol(_) => {
self.phase = ExecutorToPropertyKeyComplete(input)
ExecutorActivationNormal(input)
}
_ if is_js_object(input) => {
let operation = ExecutorToPrimitiveOperation(
input,
ExecutorToPrimitiveString,
self.loc,
)
match operation.step(interp) {
ExecutorActivationNormal(value) => {
let key = finish_executor_to_property_key(value, interp)
self.phase = ExecutorToPropertyKeyComplete(key)
ExecutorActivationNormal(key)
}
step => {
self.phase = ExecutorToPropertyKeyAwaitToPrimitive(operation)
step
}
}
}
_ => {
let key = finish_executor_to_property_key(input, interp)
self.phase = ExecutorToPropertyKeyComplete(key)
ExecutorActivationNormal(key)
}
}
ExecutorToPropertyKeyAwaitToPrimitive(operation) =>
match operation.step(interp) {
ExecutorActivationNormal(value) => {
let key = finish_executor_to_property_key(value, interp)
self.phase = ExecutorToPropertyKeyComplete(key)
ExecutorActivationNormal(key)
}
step => {
self.phase = ExecutorToPropertyKeyAwaitToPrimitive(operation)
step
}
}
ExecutorToPropertyKeyComplete(value) => {
ignore(value)
raise @errors.InternalError(
message="ToPropertyKey operation stepped after completion",
)
}
}
}
///|
fn ExecutorToPropertyKeyOperation::deliver_activation_completion(
self : ExecutorToPropertyKeyOperation,
completion : ExecutorActivationCompletion,
) -> Unit raise Error {
match completion {
ExecutorActivationCompletionAbrupt(error) => raise error
ExecutorActivationCompletionReference(_) =>
raise @errors.InternalError(
message="ToPropertyKey received an unexpected binding reference completion",
)
ExecutorActivationCompletionNormal(value) =>
match self.phase {
ExecutorToPropertyKeyAwaitToPrimitive(operation) =>
operation.deliver_activation_completion(
ExecutorActivationCompletionNormal(value),
)
ExecutorToPropertyKeyNeedInput(_) | ExecutorToPropertyKeyComplete(_) =>
raise @errors.InternalError(
message="ToPropertyKey received an unexpected child completion",
)
}
}
}
///|
fn ExecutorToPrimitiveOperation::deliver_activation_completion(
self : ExecutorToPrimitiveOperation,
completion : ExecutorActivationCompletion,
) -> Unit raise Error {
match completion {
ExecutorActivationCompletionAbrupt(error) => raise error
ExecutorActivationCompletionReference(_) =>
raise @errors.InternalError(
message="ToPrimitive received an unexpected binding reference completion",
)
ExecutorActivationCompletionNormal(value) =>
match self.phase {
ExecutorToPrimitiveAwaitToPrimitiveLookup(current) =>
self.phase = ExecutorToPrimitiveHaveToPrimitiveMethod(current, value)
ExecutorToPrimitiveAwaitToPrimitiveCall =>
self.phase = ExecutorToPrimitiveHaveToPrimitiveResult(value)
ExecutorToPrimitiveAwaitValueOfLookup(current) =>
self.phase = ExecutorToPrimitiveHaveValueOfMethod(current, value)
ExecutorToPrimitiveAwaitValueOfCall(current) =>
self.phase = ExecutorToPrimitiveHaveValueOfResult(current, value)
ExecutorToPrimitiveAwaitToStringLookup(current) =>
self.phase = ExecutorToPrimitiveHaveToStringMethod(current, value)
ExecutorToPrimitiveAwaitToStringCall(current) =>
self.phase = ExecutorToPrimitiveHaveToStringResult(current, value)
ExecutorToPrimitiveNeedInput(_)
| ExecutorToPrimitiveHaveToPrimitiveMethod(_, _)
| ExecutorToPrimitiveHaveToPrimitiveResult(_)
| ExecutorToPrimitiveHaveValueOfMethod(_, _)
| ExecutorToPrimitiveHaveValueOfResult(_, _)
| ExecutorToPrimitiveHaveToStringMethod(_, _)
| ExecutorToPrimitiveHaveToStringResult(_, _)
| ExecutorToPrimitiveComplete(_) =>
raise @errors.InternalError(
message="ToPrimitive operation received an unexpected child completion",
)
}
}
}
///|
// The synchronous boundary is decided by runtime-owned value semantics. A
// primitive pair can finish without entering the activation coordinator;
// object-like operands still use the resumable request below so every
// conversion hook follows the managed guest-entry path.
pub enum ExecutorCoercingAdditionStart {
ExecutorCoercingAdditionCompleted(Value)
ExecutorCoercingAdditionSuspended(ExecutorCoercingAdditionRequest)
}
///|
pub fn begin_executor_coercing_addition(
left : Value,
right : Value,
loc : @token.Loc,
) -> ExecutorCoercingAdditionStart raise Error {
if is_js_object(left) || is_js_object(right) {
ExecutorCoercingAdditionSuspended(
ExecutorCoercingAdditionRequest(left~, right~, loc~),
)
} else {
ExecutorCoercingAdditionCompleted(
finish_primitive_addition(None, left, right, loc),
)
}
}
///|
fn finish_primitive_addition(
interp : Interpreter?,
left : Value,
right : Value,
_loc : @token.Loc,
) -> Value raise Error {
match (left, right) {
(String_(a), String_(b)) => String_(a + b)
(String_(a), b) => String_(a + to_js_string(b, interp~))
(a, String_(b)) => String_(to_js_string(a, interp~) + b)
(a, b) => Number(to_number(a, interp~) + to_number(b, interp~))
}
}
///|
priv enum ExecutorCoercingAdditionPhase {
ExecutorCoercingAdditionNeedLeft
ExecutorCoercingAdditionAwaitLeft(ExecutorToPrimitiveOperation)
ExecutorCoercingAdditionHaveLeft(Value)
ExecutorCoercingAdditionAwaitRight(Value, ExecutorToPrimitiveOperation)
ExecutorCoercingAdditionComplete(Value)
}
///|
priv struct ExecutorCoercingAdditionFrame {
request : ExecutorCoercingAdditionRequest
mut phase : ExecutorCoercingAdditionPhase
}
///|
fn ExecutorCoercingAdditionFrame::ExecutorCoercingAdditionFrame(
request : ExecutorCoercingAdditionRequest,
) -> ExecutorCoercingAdditionFrame {
{ request, phase: ExecutorCoercingAdditionNeedLeft, }
}
///|
fn ExecutorCoercingAdditionFrame::step_frame(
self : ExecutorCoercingAdditionFrame,
interp : Interpreter,
) -> ExecutorActivationStep raise Error {
match self.phase {
ExecutorCoercingAdditionNeedLeft => {
let operation = ExecutorToPrimitiveOperation(
self.request.left,
ExecutorToPrimitiveDefault,
self.request.loc,
)
match operation.step(interp) {
ExecutorActivationNormal(value) => {
self.phase = ExecutorCoercingAdditionHaveLeft(value)
self.step_frame(interp)
}
step => {
self.phase = ExecutorCoercingAdditionAwaitLeft(operation)
step
}
}
}
ExecutorCoercingAdditionAwaitLeft(operation) =>
match operation.step(interp) {
ExecutorActivationNormal(value) => {
self.phase = ExecutorCoercingAdditionHaveLeft(value)
self.step_frame(interp)
}
step => {
self.phase = ExecutorCoercingAdditionAwaitLeft(operation)
step
}
}
ExecutorCoercingAdditionHaveLeft(left) => {
let operation = ExecutorToPrimitiveOperation(
self.request.right,
ExecutorToPrimitiveDefault,
self.request.loc,
)
match operation.step(interp) {
ExecutorActivationNormal(right) => {
let result = finish_primitive_addition(
Some(interp),
left,
right,
self.request.loc,
)
self.phase = ExecutorCoercingAdditionComplete(result)
ExecutorActivationNormal(result)
}
step => {
self.phase = ExecutorCoercingAdditionAwaitRight(left, operation)
step
}
}
}
ExecutorCoercingAdditionAwaitRight(left, operation) =>
match operation.step(interp) {
ExecutorActivationNormal(right) => {
let result = finish_primitive_addition(
Some(interp),
left,
right,
self.request.loc,
)
self.phase = ExecutorCoercingAdditionComplete(result)
ExecutorActivationNormal(result)
}
step => {
self.phase = ExecutorCoercingAdditionAwaitRight(left, operation)
step
}
}
ExecutorCoercingAdditionComplete(value) => {
ignore(value)
raise @errors.InternalError(
message="coercing addition frame stepped after completion",
)
}
}
}
///|
impl ExecutorActivationFrame for ExecutorCoercingAdditionFrame with fn step(
self,
interp,
) {
self.step_frame(interp)
}
///|
impl ExecutorActivationFrame for ExecutorCoercingAdditionFrame with fn deliver_activation_completion(
self,
completion,
) {
match self.phase {
ExecutorCoercingAdditionAwaitLeft(operation) =>
operation.deliver_activation_completion(completion)
ExecutorCoercingAdditionAwaitRight(_, operation) =>
operation.deliver_activation_completion(completion)
ExecutorCoercingAdditionNeedLeft
| ExecutorCoercingAdditionHaveLeft(_)
| ExecutorCoercingAdditionComplete(_) =>
raise @errors.InternalError(
message="coercing addition received an unexpected child completion",
)
}
}