Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a Class map #3315

Merged
merged 4 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions boa_engine/src/builtins/intl/segmenter/segments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ impl IntrinsicObject for Segments {

BuiltInBuilder::with_intrinsic::<Self>(realm)
.static_method(Self::containing, js_string!("containing"), 1)
.static_method(
Self::iterator,
(JsSymbol::iterator(), js_string!("[Symbol.iterator]")),
0,
)
.static_method(Self::iterator, JsSymbol::iterator(), 0)
.build();
}

Expand Down
15 changes: 2 additions & 13 deletions boa_engine/src/builtins/iterable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,7 @@ impl IntrinsicObject for Iterator {
let _timer = Profiler::global().start_event("Iterator Prototype", "init");

BuiltInBuilder::with_intrinsic::<Self>(realm)
.static_method(
|v, _, _| Ok(v.clone()),
(JsSymbol::iterator(), js_string!("[Symbol.iterator]")),
0,
)
.static_method(|v, _, _| Ok(v.clone()), JsSymbol::iterator(), 0)
.build();
}

Expand All @@ -187,14 +183,7 @@ impl IntrinsicObject for AsyncIterator {
let _timer = Profiler::global().start_event("AsyncIteratorPrototype", "init");

BuiltInBuilder::with_intrinsic::<Self>(realm)
.static_method(
|v, _, _| Ok(v.clone()),
(
JsSymbol::async_iterator(),
js_string!("[Symbol.asyncIterator]"),
),
0,
)
.static_method(|v, _, _| Ok(v.clone()), JsSymbol::async_iterator(), 0)
.build();
}

Expand Down
30 changes: 5 additions & 25 deletions boa_engine/src/builtins/regexp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,31 +95,11 @@ impl IntrinsicObject for RegExp {
.method(Self::test, js_string!("test"), 1)
.method(Self::exec, js_string!("exec"), 1)
.method(Self::to_string, js_string!("toString"), 0)
.method(
Self::r#match,
(JsSymbol::r#match(), js_string!("[Symbol.match]")),
1,
)
.method(
Self::match_all,
(JsSymbol::match_all(), js_string!("[Symbol.matchAll]")),
1,
)
.method(
Self::replace,
(JsSymbol::replace(), js_string!("[Symbol.replace]")),
2,
)
.method(
Self::search,
(JsSymbol::search(), js_string!("[Symbol.search]")),
1,
)
.method(
Self::split,
(JsSymbol::split(), js_string!("[Symbol.split]")),
2,
)
.method(Self::r#match, JsSymbol::r#match(), 1)
.method(Self::match_all, JsSymbol::match_all(), 1)
.method(Self::replace, JsSymbol::replace(), 2)
.method(Self::search, JsSymbol::search(), 1)
.method(Self::split, JsSymbol::split(), 2)
.accessor(
js_string!("hasIndices"),
Some(get_has_indices),
Expand Down
8 changes: 1 addition & 7 deletions boa_engine/src/builtins/string/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ impl IntrinsicObject for String {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init");

let symbol_iterator = JsSymbol::iterator();

let trim_start = BuiltInBuilder::callable(realm, Self::trim_start)
.length(0)
.name(js_string!("trimStart"))
Expand Down Expand Up @@ -150,11 +148,7 @@ impl IntrinsicObject for String {
.method(Self::match_all, js_string!("matchAll"), 1)
.method(Self::replace, js_string!("replace"), 2)
.method(Self::replace_all, js_string!("replaceAll"), 2)
.method(
Self::iterator,
(symbol_iterator, js_string!("[Symbol.iterator]")),
0,
)
.method(Self::iterator, JsSymbol::iterator(), 0)
.method(Self::search, js_string!("search"), 1)
.method(Self::at, js_string!("at"), 1);

Expand Down
130 changes: 58 additions & 72 deletions boa_engine/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
//! # class::{Class, ClassBuilder},
//! # Context, JsResult, JsValue,
//! # JsArgs,
//! # js_string,
//! # };
//! # use boa_gc::{Finalize, Trace};
//! #
//! // This does not have to be an enum it can also be a struct.
//! // Can also be a struct containing `Trace` types.
//! #[derive(Debug, Trace, Finalize)]
//! enum Animal {
//! Cat,
Expand All @@ -26,8 +27,8 @@
//! // We set the length to `1` since we accept 1 arguments in the constructor.
//! const LENGTH: usize = 1;
//!
//! // This is what is called when we do `new Animal()`
//! fn constructor(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self> {
//! // This is what is called when we do `new Animal()` to construct the inner data of the class.
//! fn make_data(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self> {
//! // This is equivalent to `String(arg)`.
//! let kind = args.get_or_undefined(0).to_string(context)?;
//!
Expand All @@ -43,7 +44,7 @@
//! /// This is where the object is initialized.
//! fn init(class: &mut ClassBuilder) -> JsResult<()> {
//! class.method(
//! "speak",
//! js_string!("speak"),
//! 0,
//! NativeFunction::from_fn_ptr(|this, _args, _ctx| {
//! if let Some(object) = this.as_object() {
Expand All @@ -66,99 +67,84 @@
//! [class-trait]: ./trait.Class.html

use crate::{
context::intrinsics::StandardConstructor,
error::JsNativeError,
js_string,
native_function::NativeFunction,
object::{ConstructorBuilder, JsFunction, JsObject, NativeObject, ObjectData, PROTOTYPE},
object::{
ConstructorBuilder, FunctionBinding, JsFunction, JsObject, NativeObject, ObjectData,
PROTOTYPE,
},
property::{Attribute, PropertyDescriptor, PropertyKey},
Context, JsResult, JsValue,
};

/// Native class.
pub trait Class: NativeObject + Sized {
/// The binding name of the object.
/// The binding name of this class.
const NAME: &'static str;
/// The amount of arguments the class `constructor` takes, default is `0`.
/// The amount of arguments this class' constructor takes. Default is `0`.
const LENGTH: usize = 0;
/// The attributes the class will be binded with, default is `writable`, `enumerable`, `configurable`.
/// The property attributes of this class' constructor in the global object.
/// Default is `writable`, `enumerable`, `configurable`.
const ATTRIBUTES: Attribute = Attribute::all();

/// The constructor of the class.
fn constructor(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self>;
/// Creates the internal data for an instance of this class.
///
/// This method can also be called the "native constructor" of this class.
fn make_data(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self>;

/// Initializes the internals and the methods of the class.
/// Initializes the properties and methods of this class.
fn init(class: &mut ClassBuilder<'_, '_>) -> JsResult<()>;
}

/// This is a wrapper around `Class::constructor` that sets the internal data of a class.
///
/// This is automatically implemented, when a type implements `Class`.
pub trait ClassConstructor: Class {
/// The raw constructor that matches the `NativeFunction` signature.
fn raw_constructor(
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue>
where
Self: Sized;
}

impl<T: Class> ClassConstructor for T {
fn raw_constructor(
this: &JsValue,
/// Creates a new [`JsObject`] with its internal data set to the result of calling `Self::make_data`.
///
/// # Note
///
/// This will throw an error if this class is not registered in the context's active realm.
/// See [`Context::register_global_class`].
///
/// # Warning
///
/// Overriding this method could be useful for certain usages, but incorrectly implementing this
/// could lead to weird errors like missing inherited methods or incorrect internal data.
fn construct(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue>
where
Self: Sized,
{
if this.is_undefined() {
) -> JsResult<JsObject> {
if new_target.is_undefined() {
return Err(JsNativeError::typ()
.with_message(format!(
"cannot call constructor of native class `{}` without new",
T::NAME
Self::NAME
))
.into());
}

let class = context.global_object().get(js_string!(T::NAME), context)?;
let JsValue::Object(ref class_constructor) = class else {
return Err(JsNativeError::typ()
.with_message(format!(
"invalid constructor for native class `{}` ",
T::NAME
))
.into());
};
let class = context.get_global_class::<Self>().ok_or_else(|| {
JsNativeError::typ().with_message(format!(
"could not find native class `{}` in the map of registered classes",
Self::NAME
))
})?;

let JsValue::Object(ref class_prototype) = class_constructor.get(PROTOTYPE, context)?
else {
return Err(JsNativeError::typ()
.with_message(format!(
"invalid default prototype for native class `{}`",
T::NAME
))
.into());
};

let prototype = this
let prototype = new_target
.as_object()
.map(|obj| {
obj.get(PROTOTYPE, context)
.map(|val| val.as_object().cloned())
})
.transpose()?
.flatten()
.unwrap_or_else(|| class_prototype.clone());
.unwrap_or_else(|| class.prototype());

let native_instance = Self::constructor(this, args, context)?;
let object_instance = JsObject::from_proto_and_data_with_shared_shape(
let data = Self::make_data(new_target, args, context)?;
let instance = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::native_object(native_instance),
ObjectData::native_object(data),
);
Ok(object_instance.into())
Ok(instance)
}
}

Expand All @@ -171,28 +157,29 @@ pub struct ClassBuilder<'ctx, 'host> {
impl<'ctx, 'host> ClassBuilder<'ctx, 'host> {
pub(crate) fn new<T>(context: &'ctx mut Context<'host>) -> Self
where
T: ClassConstructor,
T: Class,
{
let mut builder =
ConstructorBuilder::new(context, NativeFunction::from_fn_ptr(T::raw_constructor));
let mut builder = ConstructorBuilder::new(
context,
NativeFunction::from_fn_ptr(|t, a, c| T::construct(t, a, c).map(JsValue::from)),
);
builder.name(T::NAME);
builder.length(T::LENGTH);
Self { builder }
}

pub(crate) fn build(self) -> JsFunction {
JsFunction::from_object_unchecked(self.builder.build().into())
pub(crate) fn build(self) -> StandardConstructor {
self.builder.build()
}

/// Add a method to the class.
///
/// It is added to `prototype`.
pub fn method<N>(&mut self, name: N, length: usize, function: NativeFunction) -> &mut Self
where
N: AsRef<str>,
N: Into<FunctionBinding>,
{
self.builder
.method(function, js_string!(name.as_ref()), length);
self.builder.method(function, name, length);
self
}

Expand All @@ -206,10 +193,9 @@ impl<'ctx, 'host> ClassBuilder<'ctx, 'host> {
function: NativeFunction,
) -> &mut Self
where
N: AsRef<str>,
N: Into<FunctionBinding>,
{
self.builder
.static_method(function, js_string!(name.as_ref()), length);
self.builder.static_method(function, name, length);
self
}

Expand Down
14 changes: 11 additions & 3 deletions boa_engine/src/context/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ impl Intrinsics {
}
}

/// Store a builtin constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Trace, Finalize)]
/// Stores a constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Trace, Finalize, Clone)]
pub struct StandardConstructor {
constructor: JsFunction,
prototype: JsObject,
Expand All @@ -75,6 +75,14 @@ impl Default for StandardConstructor {
}

impl StandardConstructor {
/// Creates a new `StandardConstructor` from the constructor and the prototype.
pub(crate) fn new(constructor: JsFunction, prototype: JsObject) -> Self {
Self {
constructor,
prototype,
}
}

/// Build a constructor with a defined prototype.
fn with_prototype(prototype: JsObject) -> Self {
Self {
Expand All @@ -85,7 +93,7 @@ impl StandardConstructor {

/// Return the prototype of the constructor object.
///
/// This is the same as `Object.prototype`, `Array.prototype`, etc
/// This is the same as `Object.prototype`, `Array.prototype`, etc.
#[inline]
#[must_use]
pub fn prototype(&self) -> JsObject {
Expand Down
Loading