use boa_ast::scope::{BindingLocator, BindingLocatorScope};
use crate::{
Context, JsError, JsNativeError, JsResult,
environments::Environment,
vm::opcode::{Operation, VaryingOperand},
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct ThrowMutateImmutable;
impl ThrowMutateImmutable {
#[inline(always)]
pub(crate) fn operation(index: VaryingOperand, context: &mut Context) -> JsError {
let name = context
.vm
.frame()
.code_block()
.constant_string(index.into());
JsNativeError::typ()
.with_message(format!(
"cannot mutate an immutable binding '{}'",
name.to_std_string_escaped()
))
.into()
}
}
impl Operation for ThrowMutateImmutable {
const NAME: &'static str = "ThrowMutateImmutable";
const INSTRUCTION: &'static str = "INST - ThrowMutateImmutable";
const COST: u8 = 2;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetName;
impl SetName {
#[inline(always)]
pub(crate) fn operation(
(value, index): (VaryingOperand, VaryingOperand),
context: &mut Context,
) -> JsResult<()> {
let value = context.vm.get_register(value.into()).clone();
let code_block = context.vm.frame().code_block();
let mut binding_locator = code_block.bindings[usize::from(index)].clone();
let strict = code_block.strict();
context.find_runtime_binding(&mut binding_locator)?;
verify_initialized(&binding_locator, context)?;
context.set_binding(&binding_locator, value.clone(), strict)?;
Ok(())
}
}
impl Operation for SetName {
const NAME: &'static str = "SetName";
const INSTRUCTION: &'static str = "INST - SetName";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SetNameByLocator;
impl SetNameByLocator {
#[inline(always)]
pub(crate) fn operation(value: VaryingOperand, context: &mut Context) -> JsResult<()> {
let frame = context.vm.frame_mut();
let strict = frame.code_block.strict();
let binding_locator = frame
.binding_stack
.pop()
.expect("locator should have been popped before");
let value = context.vm.get_register(value.into()).clone();
verify_initialized(&binding_locator, context)?;
context.set_binding(&binding_locator, value.clone(), strict)?;
Ok(())
}
}
impl Operation for SetNameByLocator {
const NAME: &'static str = "SetNameByLocator";
const INSTRUCTION: &'static str = "INST - SetNameByLocator";
const COST: u8 = 4;
}
fn verify_initialized(locator: &BindingLocator, context: &mut Context) -> JsResult<()> {
if !context.is_initialized_binding(locator)? {
let key = locator.name();
let strict = context.vm.frame().code_block.strict();
let message = match locator.scope() {
BindingLocatorScope::GlobalObject if strict => Some(format!(
"cannot assign to uninitialized global property `{}`",
key.to_std_string_escaped()
)),
BindingLocatorScope::GlobalObject => None,
BindingLocatorScope::GlobalDeclarative => Some(format!(
"cannot assign to uninitialized binding `{}`",
key.to_std_string_escaped()
)),
BindingLocatorScope::Stack(index) => match context.environment_expect(index) {
Environment::Declarative(_) => Some(format!(
"cannot assign to uninitialized binding `{}`",
key.to_std_string_escaped()
)),
Environment::Object(_) if strict => Some(format!(
"cannot assign to uninitialized property `{}`",
key.to_std_string_escaped()
)),
Environment::Object(_) => None,
},
};
if let Some(message) = message {
return Err(JsNativeError::reference().with_message(message).into());
}
}
Ok(())
}