use super::{Native, install_constructor, link_constructor, methods};
use crate::heap::HeapError;
use crate::heap::object::{ObjectKind, flags};
use crate::runtime::Names;
use crate::shape::PropertyFlags;
use crate::value::{ObjectId, Value};
use crate::vm::error::{self as vm_error, ErrorKind};
use crate::vm::native::CallSite;
use crate::vm::object as internal;
use crate::vm::{Cx, Throw, iter, ops};
pub(super) fn install(cx: &mut Cx<'_>) -> Result<(), HeapError> {
let names = cx.realm.names;
let hidden = PropertyFlags::data(true, false, true);
let mut error_constructor = None;
for kind in ErrorKind::ALL {
let prototype = cx.realm.intrinsic(kind.prototype());
let constructor = install_constructor(cx, Native::error(kind))?;
if let Some(error) = error_constructor {
cx.heap.set_prototype(constructor, Some(error))?;
} else {
error_constructor = Some(constructor);
methods(cx, constructor, &[Native::ErrorIsError])?;
methods(cx, prototype, &[Native::ErrorToString])?;
}
let name = cx.heap.intern(kind.name())?;
cx.heap.add_property(
prototype,
Names::key(names.name),
hidden,
Value::string(name),
)?;
let empty = Value::string(names.empty);
cx.heap
.add_property(prototype, Names::key(names.message), hidden, empty)?;
link_constructor(cx, constructor, prototype)?;
}
Ok(())
}
fn construct(
cx: &mut Cx<'_>,
site: &CallSite,
kind: ErrorKind,
message: Value,
options: Value,
) -> Result<ObjectId, Throw> {
let new_target = if site.construct {
site.new_target
} else {
site.function
};
let prototype = internal::prototype_from_constructor(cx, new_target, kind.prototype())?;
let object = cx
.heap
.alloc_object(ObjectKind::Ordinary, Some(prototype), 2)
.map_err(|error| vm_error::heap_error(cx, error))?;
cx.heap.set_object_flag(object, flags::ERROR);
cx.heap.roots().push(Value::object(object));
let names = cx.realm.names;
let hidden = PropertyFlags::data(true, false, true);
if !message.is_undefined() {
let message = ops::to_string(cx, message)?;
cx.heap.roots().push(message);
cx.heap
.add_property(object, Names::key(names.message), hidden, message)
.map_err(|error| vm_error::heap_error(cx, error))?;
}
if let Some(options) = options.as_object()
&& internal::has_property(cx, options, Names::key(names.cause))?
{
let cause = internal::get_property(cx, options, Names::key(names.cause))?;
cx.heap.roots().push(cause);
cx.heap
.add_property(object, Names::key(names.cause), hidden, cause)
.map_err(|error| vm_error::heap_error(cx, error))?;
}
Ok(object)
}
fn add_stack(cx: &mut Cx<'_>, object: ObjectId) -> Result<Value, Throw> {
vm_error::add_stack(cx, object, true).map_err(|error| vm_error::heap_error(cx, error))?;
Ok(Value::object(object))
}
fn native_error(cx: &mut Cx<'_>, site: CallSite, kind: ErrorKind) -> Result<Value, Throw> {
let (message, options) = (site.arg(cx, 0), site.arg(cx, 1));
let object = construct(cx, &site, kind, message, options)?;
add_stack(cx, object)
}
macro_rules! constructors {
($($name:ident => $kind:ident;)*) => {
$(
pub(super) fn $name(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
native_error(cx, site, ErrorKind::$kind)
}
)*
};
}
constructors! {
error => Error;
eval_error => Eval;
range_error => Range;
reference_error => Reference;
syntax_error => Syntax;
type_error => Type;
internal_error => Internal;
uri_error => Uri;
}
pub(super) fn aggregate_error(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let (errors, message, options) = (site.arg(cx, 0), site.arg(cx, 1), site.arg(cx, 2));
let object = construct(cx, &site, ErrorKind::Aggregate, message, options)?;
let list = iter::collect(cx, errors)?;
let array = internal::array_from_list(cx, &list)?;
let key = Names::key(cx.realm.names.errors);
cx.heap
.add_property(
object,
key,
PropertyFlags::data(true, false, true),
Value::object(array),
)
.map_err(|error| vm_error::heap_error(cx, error))?;
add_stack(cx, object)
}
pub(super) fn to_string(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let this = site.this(cx);
let Some(object) = this.as_object() else {
return Err(vm_error::type_error(cx, "not an object"));
};
ops::error_text(cx, object)
}
pub(super) fn is_error(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
let value = site.arg(cx, 0);
Ok(Value::bool(value.as_object().is_some_and(|object| {
cx.heap.header(object).has(flags::ERROR)
})))
}