Browse Source

Introduce a `Class` map (#3315)

* Improve `Class` and `ClassBuilder` ergonomics

* Fix tests

* Apply review

* Fix docs
pull/3331/head
José Julián Espina 1 year ago committed by GitHub
parent
commit
b03aa360f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      boa_engine/src/builtins/intl/segmenter/segments.rs
  2. 15
      boa_engine/src/builtins/iterable/mod.rs
  3. 30
      boa_engine/src/builtins/regexp/mod.rs
  4. 8
      boa_engine/src/builtins/string/mod.rs
  5. 130
      boa_engine/src/class.rs
  6. 14
      boa_engine/src/context/intrinsics.rs
  7. 78
      boa_engine/src/context/mod.rs
  8. 26
      boa_engine/src/host_defined.rs
  9. 19
      boa_engine/src/object/mod.rs
  10. 46
      boa_engine/src/realm.rs
  11. 13
      boa_engine/src/string/common.rs
  12. 38
      boa_engine/src/symbol.rs
  13. 19
      boa_examples/src/bin/classes.rs
  14. 54
      boa_gc/src/cell.rs

6
boa_engine/src/builtins/intl/segmenter/segments.rs

@ -25,11 +25,7 @@ impl IntrinsicObject for Segments {
BuiltInBuilder::with_intrinsic::<Self>(realm) BuiltInBuilder::with_intrinsic::<Self>(realm)
.static_method(Self::containing, js_string!("containing"), 1) .static_method(Self::containing, js_string!("containing"), 1)
.static_method( .static_method(Self::iterator, JsSymbol::iterator(), 0)
Self::iterator,
(JsSymbol::iterator(), js_string!("[Symbol.iterator]")),
0,
)
.build(); .build();
} }

15
boa_engine/src/builtins/iterable/mod.rs

@ -161,11 +161,7 @@ impl IntrinsicObject for Iterator {
let _timer = Profiler::global().start_event("Iterator Prototype", "init"); let _timer = Profiler::global().start_event("Iterator Prototype", "init");
BuiltInBuilder::with_intrinsic::<Self>(realm) BuiltInBuilder::with_intrinsic::<Self>(realm)
.static_method( .static_method(|v, _, _| Ok(v.clone()), JsSymbol::iterator(), 0)
|v, _, _| Ok(v.clone()),
(JsSymbol::iterator(), js_string!("[Symbol.iterator]")),
0,
)
.build(); .build();
} }
@ -187,14 +183,7 @@ impl IntrinsicObject for AsyncIterator {
let _timer = Profiler::global().start_event("AsyncIteratorPrototype", "init"); let _timer = Profiler::global().start_event("AsyncIteratorPrototype", "init");
BuiltInBuilder::with_intrinsic::<Self>(realm) BuiltInBuilder::with_intrinsic::<Self>(realm)
.static_method( .static_method(|v, _, _| Ok(v.clone()), JsSymbol::async_iterator(), 0)
|v, _, _| Ok(v.clone()),
(
JsSymbol::async_iterator(),
js_string!("[Symbol.asyncIterator]"),
),
0,
)
.build(); .build();
} }

30
boa_engine/src/builtins/regexp/mod.rs

@ -95,31 +95,11 @@ impl IntrinsicObject for RegExp {
.method(Self::test, js_string!("test"), 1) .method(Self::test, js_string!("test"), 1)
.method(Self::exec, js_string!("exec"), 1) .method(Self::exec, js_string!("exec"), 1)
.method(Self::to_string, js_string!("toString"), 0) .method(Self::to_string, js_string!("toString"), 0)
.method( .method(Self::r#match, JsSymbol::r#match(), 1)
Self::r#match, .method(Self::match_all, JsSymbol::match_all(), 1)
(JsSymbol::r#match(), js_string!("[Symbol.match]")), .method(Self::replace, JsSymbol::replace(), 2)
1, .method(Self::search, JsSymbol::search(), 1)
) .method(Self::split, JsSymbol::split(), 2)
.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,
)
.accessor( .accessor(
js_string!("hasIndices"), js_string!("hasIndices"),
Some(get_has_indices), Some(get_has_indices),

8
boa_engine/src/builtins/string/mod.rs

@ -78,8 +78,6 @@ impl IntrinsicObject for String {
fn init(realm: &Realm) { fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init"); 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) let trim_start = BuiltInBuilder::callable(realm, Self::trim_start)
.length(0) .length(0)
.name(js_string!("trimStart")) .name(js_string!("trimStart"))
@ -150,11 +148,7 @@ impl IntrinsicObject for String {
.method(Self::match_all, js_string!("matchAll"), 1) .method(Self::match_all, js_string!("matchAll"), 1)
.method(Self::replace, js_string!("replace"), 2) .method(Self::replace, js_string!("replace"), 2)
.method(Self::replace_all, js_string!("replaceAll"), 2) .method(Self::replace_all, js_string!("replaceAll"), 2)
.method( .method(Self::iterator, JsSymbol::iterator(), 0)
Self::iterator,
(symbol_iterator, js_string!("[Symbol.iterator]")),
0,
)
.method(Self::search, js_string!("search"), 1) .method(Self::search, js_string!("search"), 1)
.method(Self::at, js_string!("at"), 1); .method(Self::at, js_string!("at"), 1);

130
boa_engine/src/class.rs

@ -8,10 +8,11 @@
//! # class::{Class, ClassBuilder}, //! # class::{Class, ClassBuilder},
//! # Context, JsResult, JsValue, //! # Context, JsResult, JsValue,
//! # JsArgs, //! # JsArgs,
//! # js_string,
//! # }; //! # };
//! # use boa_gc::{Finalize, Trace}; //! # 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)] //! #[derive(Debug, Trace, Finalize)]
//! enum Animal { //! enum Animal {
//! Cat, //! Cat,
@ -26,8 +27,8 @@
//! // We set the length to `1` since we accept 1 arguments in the constructor. //! // We set the length to `1` since we accept 1 arguments in the constructor.
//! const LENGTH: usize = 1; //! const LENGTH: usize = 1;
//! //!
//! // This is what is called when we do `new Animal()` //! // This is what is called when we do `new Animal()` to construct the inner data of the class.
//! fn constructor(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self> { //! fn make_data(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self> {
//! // This is equivalent to `String(arg)`. //! // This is equivalent to `String(arg)`.
//! let kind = args.get_or_undefined(0).to_string(context)?; //! let kind = args.get_or_undefined(0).to_string(context)?;
//! //!
@ -43,7 +44,7 @@
//! /// This is where the object is initialized. //! /// This is where the object is initialized.
//! fn init(class: &mut ClassBuilder) -> JsResult<()> { //! fn init(class: &mut ClassBuilder) -> JsResult<()> {
//! class.method( //! class.method(
//! "speak", //! js_string!("speak"),
//! 0, //! 0,
//! NativeFunction::from_fn_ptr(|this, _args, _ctx| { //! NativeFunction::from_fn_ptr(|this, _args, _ctx| {
//! if let Some(object) = this.as_object() { //! if let Some(object) = this.as_object() {
@ -66,83 +67,68 @@
//! [class-trait]: ./trait.Class.html //! [class-trait]: ./trait.Class.html
use crate::{ use crate::{
context::intrinsics::StandardConstructor,
error::JsNativeError, error::JsNativeError,
js_string,
native_function::NativeFunction, native_function::NativeFunction,
object::{ConstructorBuilder, JsFunction, JsObject, NativeObject, ObjectData, PROTOTYPE}, object::{
ConstructorBuilder, FunctionBinding, JsFunction, JsObject, NativeObject, ObjectData,
PROTOTYPE,
},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
/// Native class. /// Native class.
pub trait Class: NativeObject + Sized { pub trait Class: NativeObject + Sized {
/// The binding name of the object. /// The binding name of this class.
const NAME: &'static str; 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; 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(); const ATTRIBUTES: Attribute = Attribute::all();
/// The constructor of the class. /// Creates the internal data for an instance of this class.
fn constructor(this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self>; ///
/// 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<()>; 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 { /// Creates a new [`JsObject`] with its internal data set to the result of calling `Self::make_data`.
fn raw_constructor( ///
this: &JsValue, /// # 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], args: &[JsValue],
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsValue> ) -> JsResult<JsObject> {
where if new_target.is_undefined() {
Self: Sized,
{
if this.is_undefined() {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message(format!( .with_message(format!(
"cannot call constructor of native class `{}` without new", "cannot call constructor of native class `{}` without new",
T::NAME Self::NAME
)) ))
.into()); .into());
} }
let class = context.global_object().get(js_string!(T::NAME), context)?; let class = context.get_global_class::<Self>().ok_or_else(|| {
let JsValue::Object(ref class_constructor) = class else { JsNativeError::typ().with_message(format!(
return Err(JsNativeError::typ() "could not find native class `{}` in the map of registered classes",
.with_message(format!( Self::NAME
"invalid constructor for native class `{}` ", ))
T::NAME })?;
))
.into());
};
let JsValue::Object(ref class_prototype) = class_constructor.get(PROTOTYPE, context)? let prototype = new_target
else {
return Err(JsNativeError::typ()
.with_message(format!(
"invalid default prototype for native class `{}`",
T::NAME
))
.into());
};
let prototype = this
.as_object() .as_object()
.map(|obj| { .map(|obj| {
obj.get(PROTOTYPE, context) obj.get(PROTOTYPE, context)
@ -150,15 +136,15 @@ impl<T: Class> ClassConstructor for T {
}) })
.transpose()? .transpose()?
.flatten() .flatten()
.unwrap_or_else(|| class_prototype.clone()); .unwrap_or_else(|| class.prototype());
let native_instance = Self::constructor(this, args, context)?; let data = Self::make_data(new_target, args, context)?;
let object_instance = JsObject::from_proto_and_data_with_shared_shape( let instance = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(), context.root_shape(),
prototype, prototype,
ObjectData::native_object(native_instance), ObjectData::native_object(data),
); );
Ok(object_instance.into()) Ok(instance)
} }
} }
@ -171,17 +157,19 @@ pub struct ClassBuilder<'ctx, 'host> {
impl<'ctx, 'host> ClassBuilder<'ctx, 'host> { impl<'ctx, 'host> ClassBuilder<'ctx, 'host> {
pub(crate) fn new<T>(context: &'ctx mut Context<'host>) -> Self pub(crate) fn new<T>(context: &'ctx mut Context<'host>) -> Self
where where
T: ClassConstructor, T: Class,
{ {
let mut builder = let mut builder = ConstructorBuilder::new(
ConstructorBuilder::new(context, NativeFunction::from_fn_ptr(T::raw_constructor)); context,
NativeFunction::from_fn_ptr(|t, a, c| T::construct(t, a, c).map(JsValue::from)),
);
builder.name(T::NAME); builder.name(T::NAME);
builder.length(T::LENGTH); builder.length(T::LENGTH);
Self { builder } Self { builder }
} }
pub(crate) fn build(self) -> JsFunction { pub(crate) fn build(self) -> StandardConstructor {
JsFunction::from_object_unchecked(self.builder.build().into()) self.builder.build()
} }
/// Add a method to the class. /// Add a method to the class.
@ -189,10 +177,9 @@ impl<'ctx, 'host> ClassBuilder<'ctx, 'host> {
/// It is added to `prototype`. /// It is added to `prototype`.
pub fn method<N>(&mut self, name: N, length: usize, function: NativeFunction) -> &mut Self pub fn method<N>(&mut self, name: N, length: usize, function: NativeFunction) -> &mut Self
where where
N: AsRef<str>, N: Into<FunctionBinding>,
{ {
self.builder self.builder.method(function, name, length);
.method(function, js_string!(name.as_ref()), length);
self self
} }
@ -206,10 +193,9 @@ impl<'ctx, 'host> ClassBuilder<'ctx, 'host> {
function: NativeFunction, function: NativeFunction,
) -> &mut Self ) -> &mut Self
where where
N: AsRef<str>, N: Into<FunctionBinding>,
{ {
self.builder self.builder.static_method(function, name, length);
.static_method(function, js_string!(name.as_ref()), length);
self self
} }

14
boa_engine/src/context/intrinsics.rs

@ -58,8 +58,8 @@ impl Intrinsics {
} }
} }
/// Store a builtin constructor (such as `Object`) and its corresponding prototype. /// Stores a constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize, Clone)]
pub struct StandardConstructor { pub struct StandardConstructor {
constructor: JsFunction, constructor: JsFunction,
prototype: JsObject, prototype: JsObject,
@ -75,6 +75,14 @@ impl Default for StandardConstructor {
} }
impl 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. /// Build a constructor with a defined prototype.
fn with_prototype(prototype: JsObject) -> Self { fn with_prototype(prototype: JsObject) -> Self {
Self { Self {
@ -85,7 +93,7 @@ impl StandardConstructor {
/// Return the prototype of the constructor object. /// 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] #[inline]
#[must_use] #[must_use]
pub fn prototype(&self) -> JsObject { pub fn prototype(&self) -> JsObject {

78
boa_engine/src/context/mod.rs

@ -29,7 +29,7 @@ use crate::{
realm::Realm, realm::Realm,
script::Script, script::Script,
vm::{ActiveRunnable, CallFrame, Vm}, vm::{ActiveRunnable, CallFrame, Vm},
JsResult, JsString, JsValue, Source, JsNativeError, JsResult, JsString, JsValue, Source,
}; };
use boa_ast::{expression::Identifier, StatementList}; use boa_ast::{expression::Identifier, StatementList};
use boa_interner::Interner; use boa_interner::Interner;
@ -37,6 +37,8 @@ use boa_profiler::Profiler;
use crate::vm::RuntimeLimits; use crate::vm::RuntimeLimits;
use self::intrinsics::StandardConstructor;
/// ECMAScript context. It is the primary way to interact with the runtime. /// ECMAScript context. It is the primary way to interact with the runtime.
/// ///
/// `Context`s constructed in a thread share the same runtime, therefore it /// `Context`s constructed in a thread share the same runtime, therefore it
@ -317,9 +319,9 @@ impl<'host> Context<'host> {
Ok(()) Ok(())
} }
/// Register a global class of type `T`, where `T` implements `Class`. /// Registers a global class `C` in the currently active realm.
/// ///
/// It will return an error if the global property is already defined. /// Errors if the class has already been registered.
/// ///
/// # Example /// # Example
/// ```ignore /// ```ignore
@ -330,28 +332,74 @@ impl<'host> Context<'host> {
/// // ... /// // ...
/// } /// }
/// ///
/// context.register_global_class::<MyClass>(); /// context.register_global_class::<MyClass>()?;
/// ``` /// ```
pub fn register_global_class<T>(&mut self) -> JsResult<()> pub fn register_global_class<C: Class>(&mut self) -> JsResult<()> {
where if self.realm.has_class::<C>() {
T: Class, return Err(JsNativeError::typ()
{ .with_message("cannot register a class twice")
let mut class_builder = ClassBuilder::new::<T>(self); .into());
T::init(&mut class_builder)?; }
let mut class_builder = ClassBuilder::new::<C>(self);
C::init(&mut class_builder)?;
let class = class_builder.build(); let class = class_builder.build();
let property = PropertyDescriptor::builder() let property = PropertyDescriptor::builder()
.value(class) .value(class.constructor())
.writable(T::ATTRIBUTES.writable()) .writable(C::ATTRIBUTES.writable())
.enumerable(T::ATTRIBUTES.enumerable()) .enumerable(C::ATTRIBUTES.enumerable())
.configurable(T::ATTRIBUTES.configurable()); .configurable(C::ATTRIBUTES.configurable());
self.global_object() self.global_object()
.define_property_or_throw(js_string!(T::NAME), property, self)?; .define_property_or_throw(js_string!(C::NAME), property, self)?;
self.realm.register_class::<C>(class);
Ok(()) Ok(())
} }
/// Removes the global class `C` from the currently active realm, returning the constructor
/// and prototype of the class if `C` was registered.
///
/// # Note
///
/// This makes the constructor return an error on further calls, but note that this won't protect
/// static properties from being accessed within variables that stored the constructor before being
/// unregistered. If you need that functionality, you can use a static accessor that first checks
/// if the class is registered ([`Context::has_global_class`]) before returning the static value.
///
/// # Example
/// ```ignore
/// #[derive(Debug, Trace, Finalize)]
/// struct MyClass;
///
/// impl Class for MyClass {
/// // ...
/// }
///
/// context.register_global_class::<MyClass>()?;
/// // ... code
/// context.unregister_global_class::<MyClass>()?;
/// ```
pub fn unregister_global_class<C: Class>(&mut self) -> JsResult<Option<StandardConstructor>> {
self.global_object()
.delete_property_or_throw(js_string!(C::NAME), self)?;
Ok(self.realm.unregister_class::<C>())
}
/// Checks if the currently active realm has the global class `C` registered.
#[must_use]
pub fn has_global_class<C: Class>(&self) -> bool {
self.realm.has_class::<C>()
}
/// Gets the constructor and prototype of the global class `C` if the currently active realm has
/// that class registered.
#[must_use]
pub fn get_global_class<C: Class>(&self) -> Option<StandardConstructor> {
self.realm.get_class::<C>()
}
/// Gets the string interner. /// Gets the string interner.
#[inline] #[inline]
#[must_use] #[must_use]

26
boa_engine/src/host_defined.rs

@ -73,20 +73,12 @@ impl HostDefined {
/// Panics if [`HostDefined`] field is borrowed. /// Panics if [`HostDefined`] field is borrowed.
#[track_caller] #[track_caller]
pub fn get<T: NativeObject>(&self) -> Option<GcRef<'_, T>> { pub fn get<T: NativeObject>(&self) -> Option<GcRef<'_, T>> {
let state = self.state.borrow(); GcRef::try_map(self.state.borrow(), |state| {
state
.get(&TypeId::of::<T>())
.map(Box::as_ref)
.and_then(<dyn NativeObject>::downcast_ref::<T>)?;
Some(GcRef::map(state, |state| {
state state
.get(&TypeId::of::<T>()) .get(&TypeId::of::<T>())
.map(Box::as_ref) .map(Box::as_ref)
.and_then(<dyn NativeObject>::downcast_ref::<T>) .and_then(<dyn NativeObject>::downcast_ref::<T>)
.expect("Should not fail") })
}))
} }
/// Get type T from [`HostDefined`], if it exits. /// Get type T from [`HostDefined`], if it exits.
@ -96,23 +88,15 @@ impl HostDefined {
/// Panics if [`HostDefined`] field is borrowed. /// Panics if [`HostDefined`] field is borrowed.
#[track_caller] #[track_caller]
pub fn get_mut<T: NativeObject>(&self) -> Option<GcRefMut<'_, HostDefinedMap, T>> { pub fn get_mut<T: NativeObject>(&self) -> Option<GcRefMut<'_, HostDefinedMap, T>> {
let mut state = self.state.borrow_mut(); GcRefMut::try_map(
self.state.borrow_mut(),
state
.get_mut(&TypeId::of::<T>())
.map(Box::as_mut)
.and_then(<dyn NativeObject>::downcast_mut::<T>)?;
Some(GcRefMut::map(
state,
|state: &mut FxHashMap<TypeId, Box<dyn NativeObject>>| { |state: &mut FxHashMap<TypeId, Box<dyn NativeObject>>| {
state state
.get_mut(&TypeId::of::<T>()) .get_mut(&TypeId::of::<T>())
.map(Box::as_mut) .map(Box::as_mut)
.and_then(<dyn NativeObject>::downcast_mut::<T>) .and_then(<dyn NativeObject>::downcast_mut::<T>)
.expect("Should not fail")
}, },
)) )
} }
/// Clears all the objects. /// Clears all the objects.

19
boa_engine/src/object/mod.rs

@ -56,6 +56,7 @@ use crate::{
typed_array::{integer_indexed_object::IntegerIndexed, TypedArrayKind}, typed_array::{integer_indexed_object::IntegerIndexed, TypedArrayKind},
DataView, Date, Promise, RegExp, DataView, Date, Promise, RegExp,
}, },
context::intrinsics::StandardConstructor,
js_string, js_string,
module::ModuleNamespace, module::ModuleNamespace,
native_function::NativeFunction, native_function::NativeFunction,
@ -2004,8 +2005,8 @@ impl Object {
/// ///
/// There are two implementations: /// There are two implementations:
/// - From a single type `T` which implements `Into<FunctionBinding>` which sets the binding /// - From a single type `T` which implements `Into<FunctionBinding>` which sets the binding
/// name and the function name to the same value /// name and the function name to the same value.
/// - From a tuple `(B: Into<PropertyKey>, N: AsRef<str>)` the `B` is the binding name /// - From a tuple `(B: Into<PropertyKey>, N: Into<JsString>)`, where the `B` is the binding name
/// and the `N` is the function name. /// and the `N` is the function name.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FunctionBinding { pub struct FunctionBinding {
@ -2023,6 +2024,16 @@ impl From<JsString> for FunctionBinding {
} }
} }
impl From<JsSymbol> for FunctionBinding {
#[inline]
fn from(binding: JsSymbol) -> Self {
Self {
name: binding.fn_name(),
binding: binding.into(),
}
}
}
impl<B, N> From<(B, N)> for FunctionBinding impl<B, N> From<(B, N)> for FunctionBinding
where where
B: Into<PropertyKey>, B: Into<PropertyKey>,
@ -2510,7 +2521,7 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
/// Build the constructor function object. /// Build the constructor function object.
#[must_use] #[must_use]
pub fn build(mut self) -> JsFunction { pub fn build(mut self) -> StandardConstructor {
// Create the native function // Create the native function
let function = Function::new( let function = Function::new(
FunctionKind::Native { FunctionKind::Native {
@ -2593,6 +2604,6 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
); );
} }
JsFunction::from_object_unchecked(constructor) StandardConstructor::new(JsFunction::from_object_unchecked(constructor), prototype)
} }
} }

46
boa_engine/src/realm.rs

@ -6,8 +6,16 @@
//! //!
//! A realm is represented in this implementation as a Realm struct with the fields specified from the spec. //! A realm is represented in this implementation as a Realm struct with the fields specified from the spec.
use std::any::TypeId;
use rustc_hash::FxHashMap;
use crate::{ use crate::{
context::{intrinsics::Intrinsics, HostHooks}, class::Class,
context::{
intrinsics::{Intrinsics, StandardConstructor},
HostHooks,
},
environments::DeclarativeEnvironment, environments::DeclarativeEnvironment,
module::Module, module::Module,
object::shape::RootShape, object::shape::RootShape,
@ -15,7 +23,6 @@ use crate::{
}; };
use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use rustc_hash::FxHashMap;
/// Representation of a Realm. /// Representation of a Realm.
/// ///
@ -52,6 +59,7 @@ struct Inner {
global_this: JsObject, global_this: JsObject,
template_map: GcRefCell<FxHashMap<u64, JsObject>>, template_map: GcRefCell<FxHashMap<u64, JsObject>>,
loaded_modules: GcRefCell<FxHashMap<JsString, Module>>, loaded_modules: GcRefCell<FxHashMap<JsString, Module>>,
host_classes: GcRefCell<FxHashMap<TypeId, StandardConstructor>>,
host_defined: HostDefined, host_defined: HostDefined,
} }
@ -77,6 +85,7 @@ impl Realm {
global_this, global_this,
template_map: GcRefCell::default(), template_map: GcRefCell::default(),
loaded_modules: GcRefCell::default(), loaded_modules: GcRefCell::default(),
host_classes: GcRefCell::default(),
host_defined: HostDefined::default(), host_defined: HostDefined::default(),
}), }),
}; };
@ -102,6 +111,25 @@ impl Realm {
&self.inner.host_defined &self.inner.host_defined
} }
/// Checks if this `Realm` has the class `C` registered into its class map.
#[must_use]
pub fn has_class<C: Class>(&self) -> bool {
self.inner
.host_classes
.borrow()
.contains_key(&TypeId::of::<C>())
}
/// Gets the constructor and prototype of the class `C` if it is registered in the class map.
#[must_use]
pub fn get_class<C: Class>(&self) -> Option<StandardConstructor> {
self.inner
.host_classes
.borrow()
.get(&TypeId::of::<C>())
.cloned()
}
pub(crate) fn environment(&self) -> &Gc<DeclarativeEnvironment> { pub(crate) fn environment(&self) -> &Gc<DeclarativeEnvironment> {
&self.inner.environment &self.inner.environment
} }
@ -142,6 +170,20 @@ impl Realm {
self.inner.template_map.borrow().get(&site).cloned() self.inner.template_map.borrow().get(&site).cloned()
} }
pub(crate) fn register_class<C: Class>(&self, spec: StandardConstructor) {
self.inner
.host_classes
.borrow_mut()
.insert(TypeId::of::<C>(), spec);
}
pub(crate) fn unregister_class<C: Class>(&self) -> Option<StandardConstructor> {
self.inner
.host_classes
.borrow_mut()
.remove(&TypeId::of::<C>())
}
pub(crate) fn addr(&self) -> *const () { pub(crate) fn addr(&self) -> *const () {
let ptr: *const _ = &*self.inner; let ptr: *const _ = &*self.inner;
ptr.cast() ptr.cast()

13
boa_engine/src/string/common.rs

@ -94,6 +94,19 @@ impl StaticJsStrings {
(SYMBOL_TO_PRIMITIVE, "Symbol.toPrimitive"), (SYMBOL_TO_PRIMITIVE, "Symbol.toPrimitive"),
(SYMBOL_TO_STRING_TAG, "Symbol.toStringTag"), (SYMBOL_TO_STRING_TAG, "Symbol.toStringTag"),
(SYMBOL_UNSCOPABLES, "Symbol.unscopables"), (SYMBOL_UNSCOPABLES, "Symbol.unscopables"),
(FN_SYMBOL_ASYNC_ITERATOR, "[Symbol.asyncIterator]"),
(FN_SYMBOL_HAS_INSTANCE, "[Symbol.hasInstance]"),
(FN_SYMBOL_IS_CONCAT_SPREADABLE, "[Symbol.isConcatSpreadable]"),
(FN_SYMBOL_ITERATOR, "[Symbol.iterator]"),
(FN_SYMBOL_MATCH, "[Symbol.match]"),
(FN_SYMBOL_MATCH_ALL, "[Symbol.matchAll]"),
(FN_SYMBOL_REPLACE, "[Symbol.replace]"),
(FN_SYMBOL_SEARCH, "[Symbol.search]"),
(FN_SYMBOL_SPECIES, "[Symbol.species]"),
(FN_SYMBOL_SPLIT, "[Symbol.split]"),
(FN_SYMBOL_TO_PRIMITIVE, "[Symbol.toPrimitive]"),
(FN_SYMBOL_TO_STRING_TAG, "[Symbol.toStringTag]"),
(FN_SYMBOL_UNSCOPABLES, "[Symbol.unscopables]"),
// Builtins // Builtins
(ARRAY, "Array"), (ARRAY, "Array"),
(ARRAY_BUFFER, "ArrayBuffer"), (ARRAY_BUFFER, "ArrayBuffer"),

38
boa_engine/src/symbol.rs

@ -96,6 +96,24 @@ impl WellKnown {
} }
} }
const fn fn_name(self) -> JsString {
match self {
Self::AsyncIterator => StaticJsStrings::FN_SYMBOL_ASYNC_ITERATOR,
Self::HasInstance => StaticJsStrings::FN_SYMBOL_HAS_INSTANCE,
Self::IsConcatSpreadable => StaticJsStrings::FN_SYMBOL_IS_CONCAT_SPREADABLE,
Self::Iterator => StaticJsStrings::FN_SYMBOL_ITERATOR,
Self::Match => StaticJsStrings::FN_SYMBOL_MATCH,
Self::MatchAll => StaticJsStrings::FN_SYMBOL_MATCH_ALL,
Self::Replace => StaticJsStrings::FN_SYMBOL_REPLACE,
Self::Search => StaticJsStrings::FN_SYMBOL_SEARCH,
Self::Species => StaticJsStrings::FN_SYMBOL_SPECIES,
Self::Split => StaticJsStrings::FN_SYMBOL_SPLIT,
Self::ToPrimitive => StaticJsStrings::FN_SYMBOL_TO_PRIMITIVE,
Self::ToStringTag => StaticJsStrings::FN_SYMBOL_TO_STRING_TAG,
Self::Unscopables => StaticJsStrings::FN_SYMBOL_UNSCOPABLES,
}
}
const fn hash(self) -> u64 { const fn hash(self) -> u64 {
self as u64 self as u64
} }
@ -163,7 +181,7 @@ impl JsSymbol {
}) })
} }
/// Returns the `Symbol`s description. /// Returns the `Symbol` description.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn description(&self) -> Option<JsString> { pub fn description(&self) -> Option<JsString> {
@ -182,6 +200,24 @@ impl JsSymbol {
} }
} }
/// Returns the `Symbol` as a function name.
///
/// Equivalent to `[description]`, but returns the empty string if the symbol doesn't have a
/// description.
#[inline]
#[must_use]
pub fn fn_name(&self) -> JsString {
if let UnwrappedTagged::Tag(tag) = self.repr.unwrap() {
// SAFETY: All tagged reprs always come from `WellKnown` itself, making
// this operation always safe.
let wk = unsafe { WellKnown::from_tag(tag).unwrap_unchecked() };
return wk.fn_name();
}
self.description()
.map(|s| js_string!(utf16!("["), &*s, utf16!("]")))
.unwrap_or_default()
}
/// Returns the `Symbol`s hash. /// Returns the `Symbol`s hash.
/// ///
/// The hash is guaranteed to be unique. /// The hash is guaranteed to be unique.

19
boa_examples/src/bin/classes.rs

@ -64,18 +64,19 @@ impl Class for Person {
// NOTE: The default value of `LENGTH` is `0`. // NOTE: The default value of `LENGTH` is `0`.
const LENGTH: usize = 2; const LENGTH: usize = 2;
// This is what is called when we construct a `Person` with the expression `new Person()`. // This is what is internally called when we construct a `Person` with the expression `new Person()`.
fn constructor(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self> { fn make_data(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self> {
// We get the first argument. If it is unavailable we default to `undefined`, // We get the first argument. If it is unavailable we default to `undefined`,
// and then we call `to_string()`. // and then we call `to_string()`.
// //
// This is equivalent to `String(arg)`. // This is equivalent to `String(arg)`.
let name = args.get_or_undefined(0).to_string(context)?; let name = args.get_or_undefined(0).to_string(context)?;
// We get the second argument. If it is unavailable we default to `undefined`, // We get the second argument. If it is unavailable we default to `undefined`,
// and then we call `to_u32`. // and then we call `to_u32`.
// //
// This is equivalent to `arg | 0`. // This is equivalent to `arg | 0`.
let age = args.get(1).cloned().unwrap_or_default().to_u32(context)?; let age = args.get_or_undefined(1).to_u32(context)?;
// We construct a new native struct `Person` // We construct a new native struct `Person`
let person = Person { name, age }; let person = Person { name, age };
@ -88,14 +89,18 @@ impl Class for Person {
// We add a inheritable method `sayHello` with `0` arguments of length. // We add a inheritable method `sayHello` with `0` arguments of length.
// //
// This function is added to the `Person` prototype. // This function is added to the `Person` prototype.
class.method("sayHello", 0, NativeFunction::from_fn_ptr(Self::say_hello)); class.method(
js_string!("sayHello"),
0,
NativeFunction::from_fn_ptr(Self::say_hello),
);
// We add a static method `is` using a closure, but it must be convertible // We add a static method `is` using a closure, but it must be convertible
// to a NativeFunction. // to a `NativeFunction`. The `NativeFunction` API has more information on which type of
// This means it must not contain state, or the code won't compile. // Rust functions can be used to create `NativeFunction`s.
// //
// This function is added to the `Person` class. // This function is added to the `Person` class.
class.static_method( class.static_method(
"is", js_string!("is"),
1, 1,
NativeFunction::from_fn_ptr(|_this, args, _ctx| { NativeFunction::from_fn_ptr(|_this, args, _ctx| {
if let Some(arg) = args.get(0) { if let Some(arg) = args.get(0) {

54
boa_gc/src/cell.rs

@ -269,6 +269,31 @@ impl<'a, T: ?Sized> GcRef<'a, T> {
} }
} }
/// Tries to make a new `GcCellRef` from a component of the borrowed data, returning `None`
/// if the mapping function returns `None`.
///
/// The `GcCell` is already immutably borrowed, so this cannot fail.
///
/// This is an associated function that needs to be used as `GcCellRef::try_map(...)`.
/// A method would interfere with methods of the same name on the contents
/// of a `GcCellRef` used through `Deref`.
pub fn try_map<U, F>(orig: Self, f: F) -> Option<GcRef<'a, U>>
where
U: ?Sized,
F: FnOnce(&T) -> Option<&U>,
{
let ret = GcRef {
flags: orig.flags,
value: f(orig.value)?,
};
// We have to tell the compiler not to call the destructor of GcCellRef,
// because it will update the borrow flags.
std::mem::forget(orig);
Some(ret)
}
/// Makes a new `GcCellRef` from a component of the borrowed data. /// Makes a new `GcCellRef` from a component of the borrowed data.
/// ///
/// The `GcCell` is already immutably borrowed, so this cannot fail. /// The `GcCell` is already immutably borrowed, so this cannot fail.
@ -362,6 +387,35 @@ pub struct GcRefMut<'a, T: ?Sized + 'static, U: ?Sized = T> {
} }
impl<'a, T: ?Sized, U: ?Sized> GcRefMut<'a, T, U> { impl<'a, T: ?Sized, U: ?Sized> GcRefMut<'a, T, U> {
/// Tries to make a new `GcCellRefMut` for a component of the borrowed data, returning `None`
/// if the mapping function returns `None`.
///
/// The `GcCellRefMut` is already mutably borrowed, so this cannot fail.
///
/// This is an associated function that needs to be used as
/// `GcCellRefMut::map(...)`. A method would interfere with methods of the same
/// name on the contents of a `GcCell` used through `Deref`.
pub fn try_map<V, F>(orig: Self, f: F) -> Option<GcRefMut<'a, T, V>>
where
V: ?Sized,
F: FnOnce(&mut U) -> Option<&mut V>,
{
#[allow(trivial_casts)]
// SAFETY: This is safe as `GcCellRefMut` is already borrowed, so the value is rooted.
let value = unsafe { &mut *(orig.value as *mut U) };
let ret = GcRefMut {
gc_cell: orig.gc_cell,
value: f(value)?,
};
// We have to tell the compiler not to call the destructor of GcCellRef,
// because it will update the borrow flags.
std::mem::forget(orig);
Some(ret)
}
/// Makes a new `GcCellRefMut` for a component of the borrowed data, e.g., an enum /// Makes a new `GcCellRefMut` for a component of the borrowed data, e.g., an enum
/// variant. /// variant.
/// ///

Loading…
Cancel
Save