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. 126
      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)
.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();
}

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");
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();
}
@ -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();
}

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::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),

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

@ -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"))
@ -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);

126
boa_engine/src/class.rs

@ -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,
@ -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)?;
//!
@ -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() {
@ -66,83 +67,68 @@
//! [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.
/// Creates a new [`JsObject`] with its internal data set to the result of calling `Self::make_data`.
///
/// 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,
/// # 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
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
))
.into());
};
})?;
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)
@ -150,15 +136,15 @@ impl<T: Class> ClassConstructor for T {
})
.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)
}
}
@ -171,17 +157,19 @@ 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.
@ -189,10 +177,9 @@ impl<'ctx, 'host> ClassBuilder<'ctx, 'host> {
/// 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
}
@ -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
}

14
boa_engine/src/context/intrinsics.rs

@ -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,
@ -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 {
@ -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 {

78
boa_engine/src/context/mod.rs

@ -29,7 +29,7 @@ use crate::{
realm::Realm,
script::Script,
vm::{ActiveRunnable, CallFrame, Vm},
JsResult, JsString, JsValue, Source,
JsNativeError, JsResult, JsString, JsValue, Source,
};
use boa_ast::{expression::Identifier, StatementList};
use boa_interner::Interner;
@ -37,6 +37,8 @@ use boa_profiler::Profiler;
use crate::vm::RuntimeLimits;
use self::intrinsics::StandardConstructor;
/// ECMAScript context. It is the primary way to interact with the runtime.
///
/// `Context`s constructed in a thread share the same runtime, therefore it
@ -317,9 +319,9 @@ impl<'host> Context<'host> {
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
/// ```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<()>
where
T: Class,
{
let mut class_builder = ClassBuilder::new::<T>(self);
T::init(&mut class_builder)?;
pub fn register_global_class<C: Class>(&mut self) -> JsResult<()> {
if self.realm.has_class::<C>() {
return Err(JsNativeError::typ()
.with_message("cannot register a class twice")
.into());
}
let mut class_builder = ClassBuilder::new::<C>(self);
C::init(&mut class_builder)?;
let class = class_builder.build();
let property = PropertyDescriptor::builder()
.value(class)
.writable(T::ATTRIBUTES.writable())
.enumerable(T::ATTRIBUTES.enumerable())
.configurable(T::ATTRIBUTES.configurable());
.value(class.constructor())
.writable(C::ATTRIBUTES.writable())
.enumerable(C::ATTRIBUTES.enumerable())
.configurable(C::ATTRIBUTES.configurable());
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(())
}
/// 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.
#[inline]
#[must_use]

26
boa_engine/src/host_defined.rs

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

19
boa_engine/src/object/mod.rs

@ -56,6 +56,7 @@ use crate::{
typed_array::{integer_indexed_object::IntegerIndexed, TypedArrayKind},
DataView, Date, Promise, RegExp,
},
context::intrinsics::StandardConstructor,
js_string,
module::ModuleNamespace,
native_function::NativeFunction,
@ -2004,8 +2005,8 @@ impl Object {
///
/// There are two implementations:
/// - From a single type `T` which implements `Into<FunctionBinding>` which sets the binding
/// name and the function name to the same value
/// - From a tuple `(B: Into<PropertyKey>, N: AsRef<str>)` the `B` is the binding name
/// name and the function name to the same value.
/// - From a tuple `(B: Into<PropertyKey>, N: Into<JsString>)`, where the `B` is the binding name
/// and the `N` is the function name.
#[derive(Debug, Clone)]
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
where
B: Into<PropertyKey>,
@ -2510,7 +2521,7 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
/// Build the constructor function object.
#[must_use]
pub fn build(mut self) -> JsFunction {
pub fn build(mut self) -> StandardConstructor {
// Create the native function
let function = Function::new(
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.
use std::any::TypeId;
use rustc_hash::FxHashMap;
use crate::{
context::{intrinsics::Intrinsics, HostHooks},
class::Class,
context::{
intrinsics::{Intrinsics, StandardConstructor},
HostHooks,
},
environments::DeclarativeEnvironment,
module::Module,
object::shape::RootShape,
@ -15,7 +23,6 @@ use crate::{
};
use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_profiler::Profiler;
use rustc_hash::FxHashMap;
/// Representation of a Realm.
///
@ -52,6 +59,7 @@ struct Inner {
global_this: JsObject,
template_map: GcRefCell<FxHashMap<u64, JsObject>>,
loaded_modules: GcRefCell<FxHashMap<JsString, Module>>,
host_classes: GcRefCell<FxHashMap<TypeId, StandardConstructor>>,
host_defined: HostDefined,
}
@ -77,6 +85,7 @@ impl Realm {
global_this,
template_map: GcRefCell::default(),
loaded_modules: GcRefCell::default(),
host_classes: GcRefCell::default(),
host_defined: HostDefined::default(),
}),
};
@ -102,6 +111,25 @@ impl Realm {
&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> {
&self.inner.environment
}
@ -142,6 +170,20 @@ impl Realm {
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 () {
let ptr: *const _ = &*self.inner;
ptr.cast()

13
boa_engine/src/string/common.rs

@ -94,6 +94,19 @@ impl StaticJsStrings {
(SYMBOL_TO_PRIMITIVE, "Symbol.toPrimitive"),
(SYMBOL_TO_STRING_TAG, "Symbol.toStringTag"),
(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
(ARRAY, "Array"),
(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 {
self as u64
}
@ -163,7 +181,7 @@ impl JsSymbol {
})
}
/// Returns the `Symbol`s description.
/// Returns the `Symbol` description.
#[inline]
#[must_use]
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.
///
/// 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`.
const LENGTH: usize = 2;
// This is what is called when we construct a `Person` with the expression `new Person()`.
fn constructor(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResult<Self> {
// This is what is internally called when we construct a `Person` with the expression `new Person()`.
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`,
// and then we call `to_string()`.
//
// This is equivalent to `String(arg)`.
let name = args.get_or_undefined(0).to_string(context)?;
// We get the second argument. If it is unavailable we default to `undefined`,
// and then we call `to_u32`.
//
// 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`
let person = Person { name, age };
@ -88,14 +89,18 @@ impl Class for Person {
// We add a inheritable method `sayHello` with `0` arguments of length.
//
// 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
// to a NativeFunction.
// This means it must not contain state, or the code won't compile.
// to a `NativeFunction`. The `NativeFunction` API has more information on which type of
// Rust functions can be used to create `NativeFunction`s.
//
// This function is added to the `Person` class.
class.static_method(
"is",
js_string!("is"),
1,
NativeFunction::from_fn_ptr(|_this, args, _ctx| {
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.
///
/// 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> {
/// 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
/// variant.
///

Loading…
Cancel
Save