Skip to content

Commit

Permalink
Enable mutation for Closure type inside closures
Browse files Browse the repository at this point in the history
  • Loading branch information
jedel1043 committed Oct 11, 2021
1 parent 7a17da0 commit 62fa5da
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 370 deletions.
15 changes: 13 additions & 2 deletions boa/examples/closures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ fn main() -> Result<(), JsValue> {
println!("Called `closure`");
// `variable` is captured from the main function.
println!("variable = {}", variable);
println!();

// We return the moved variable as a `JsValue`.
Ok(JsValue::new(variable))
Expand Down Expand Up @@ -52,7 +53,7 @@ fn main() -> Result<(), JsValue> {

// Now, we execute some operations that return a `Clone` type
let clone_variable = BigStruct {
greeting: JsString::from("Hello from Javascript!"),
greeting: JsString::from("Hello!"),
object,
};

Expand All @@ -73,7 +74,11 @@ fn main() -> Result<(), JsValue> {
captures.greeting.as_str(),
]);

// We can also mutate the moved data inside the closure.
captures.greeting = format!("{} Hello!", captures.greeting).into();

println!("{}", message);
println!();

// We convert `message` into `Jsvalue` to be able to return it.
Ok(message.into())
Expand All @@ -100,7 +105,13 @@ fn main() -> Result<(), JsValue> {

assert_eq!(
context.eval("createMessage()")?,
"message from `Boa dev`: Hello from Javascript!".into()
"message from `Boa dev`: Hello!".into()
);

// The data mutates between calls
assert_eq!(
context.eval("createMessage(); createMessage();")?,
"message from `Boa dev`: Hello! Hello! Hello!".into()
);

// We have moved `Clone` variables into a closure and executed that closure
Expand Down
41 changes: 22 additions & 19 deletions boa/src/builtins/array_buffer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,32 +232,35 @@ impl ArrayBuffer {
let new = ctor.construct(&[new_len.into()], &ctor.clone().into(), context)?;

// 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
let new_obj = if let Some(obj) = new.as_object() {
obj.clone()
} else {
return context.throw_type_error("ArrayBuffer constructor returned non-object value");
};
let mut new_obj_borrow = new_obj.borrow_mut();
let new_array_buffer = if let Some(b) = new_obj_borrow.as_array_buffer_mut() {
b
} else {
return context.throw_type_error("ArrayBuffer constructor returned invalid object");
};

// TODO: Shared Array Buffer
// 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.
let new_obj = new.as_object().cloned().ok_or_else(|| {
context.construct_type_error("ArrayBuffer constructor returned non-object value")
})?;

// 19. If IsDetachedBuffer(new) is true, throw a TypeError exception.
if Self::is_detached_buffer(new_array_buffer) {
return context
.throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer");
{
let new_obj = new_obj.borrow();
let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| {
context.construct_type_error("ArrayBuffer constructor returned invalid object")
})?;

// TODO: Shared Array Buffer
// 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.

// 19. If IsDetachedBuffer(new) is true, throw a TypeError exception.
if new_array_buffer.is_detached_buffer() {
return context
.throw_type_error("ArrayBuffer constructor returned detached ArrayBuffer");
}
}

// 20. If SameValue(new, O) is true, throw a TypeError exception.
if JsValue::same_value(&new, this) {
return context.throw_type_error("New ArrayBuffer is the same as this ArrayBuffer");
}

let mut new_obj_borrow = new_obj.borrow_mut();
let new_array_buffer = new_obj_borrow
.as_array_buffer_mut()
.expect("Already checked that `new_obj` was an `ArrayBuffer`");

// 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
if new_array_buffer.array_buffer_byte_length < new_len {
return context.throw_type_error("New ArrayBuffer length too small");
Expand Down
86 changes: 30 additions & 56 deletions boa/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function

use std::{
any::Any,
borrow::Cow,
fmt,
ops::{Deref, DerefMut},
};

use dyn_clone::DynClone;
use gc::{Gc, GcCell};

use crate::value::IntegerOrInfinity;
use crate::{
builtins::BuiltIn,
context::StandardObjects,
Expand All @@ -38,6 +39,10 @@ use crate::{
property::PropertyKey,
JsString,
};
use crate::{
object::{Ref, RefMut},
value::IntegerOrInfinity,
};

use super::JsArgs;

Expand Down Expand Up @@ -67,16 +72,12 @@ impl<T: Copy> DynCopy for T {}
/// be callable from Javascript, but most of the time the compiler
/// is smart enough to correctly infer the types.
pub trait ClosureFunctionSignature:
Fn(&JsValue, &[JsValue], &mut Captures, &mut Context) -> JsResult<JsValue>
+ DynCopy
+ DynClone
+ 'static
Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult<JsValue> + DynCopy + DynClone + 'static
{
}

// The `Copy` bound automatically infers `DynCopy` and `DynClone`
impl<T> ClosureFunctionSignature for T where
T: Fn(&JsValue, &[JsValue], &mut Captures, &mut Context) -> JsResult<JsValue> + Copy + 'static
T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult<JsValue> + Copy + 'static
{
}

Expand Down Expand Up @@ -125,71 +126,44 @@ impl ConstructorKind {
}
}

/// Wrapper for `Box<dyn NativeObject>` that allows passing additional
/// Wrapper for `Gc<GcCell<dyn NativeObject>>` that allows passing additional
/// captures through a `Copy` closure.
///
/// Any type implementing `Trace + Any + Debug`
/// can be used as a capture context, so you can pass e.g. a String,
/// a tuple or even a full struct.
///
/// You can downcast to any type and handle the fail case as you like
/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref`
/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast
/// fails.
#[derive(Debug, Trace, Finalize)]
pub struct Captures(Box<dyn NativeObject>);
/// You can cast to `Any` with `as_any`, `as_mut_any` and downcast
/// with `Any::downcast_ref` and `Any::downcast_mut` to recover the original
/// type.
#[derive(Clone, Debug, Trace, Finalize)]
pub struct Captures(Gc<GcCell<Box<dyn NativeObject>>>);

impl Captures {
/// Creates a new capture context.
pub(crate) fn new<T>(captures: T) -> Self
where
T: NativeObject,
{
Self(Box::new(captures))
}

/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or `None` otherwise.
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: NativeObject,
{
self.0.deref().as_any().downcast_ref::<T>()
Self(Gc::new(GcCell::new(Box::new(captures))))
}

/// Mutably downcasts `Captures` to the specified type, returning a
/// mutable reference to the downcasted type if successful or `None` otherwise.
pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: NativeObject,
{
self.0.deref_mut().as_mut_any().downcast_mut::<T>()
}

/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_ref<T>(&self, context: &mut Context) -> JsResult<&T>
where
T: NativeObject,
{
self.0
.deref()
.as_any()
.downcast_ref::<T>()
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
/// Casts `Captures` to `Any`
///
/// # Panics
///
/// Panics if it's already borrowed as `&mut Any`
pub fn as_any(&self) -> gc::GcCellRef<'_, dyn Any> {
Ref::map(self.0.borrow(), |data| data.deref().as_any())
}

/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_mut<T>(&mut self, context: &mut Context) -> JsResult<&mut T>
where
T: NativeObject,
{
self.0
.deref_mut()
.as_mut_any()
.downcast_mut::<T>()
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
/// Mutably casts `Captures` to `Any`
///
/// # Panics
///
/// Panics if it's already borrowed as `&mut Any`
pub fn as_mut_any(&self) -> gc::GcCellRefMut<'_, Box<dyn NativeObject>, dyn Any> {
RefMut::map(self.0.borrow_mut(), |data| data.deref_mut().as_mut_any())
}
}

Expand Down Expand Up @@ -220,7 +194,7 @@ pub enum Function {
},
#[cfg(feature = "vm")]
VmOrdinary {
code: gc::Gc<crate::vm::CodeBlock>,
code: Gc<crate::vm::CodeBlock>,
environment: Environment,
},
}
Expand Down
2 changes: 1 addition & 1 deletion boa/src/builtins/regexp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1596,7 +1596,7 @@ impl RegExp {
let splitter = splitter
.as_object()
// todo: remove when we handle realms on `get_prototype_from_constructor` and make `construct` always return a `JsObject`
.ok_or_else(||context.construct_type_error("constructor did not return an object"))?;
.ok_or_else(|| context.construct_type_error("constructor did not return an object"))?;

// 11. Let A be ! ArrayCreate(0).
let a = Array::array_create(0, None, context).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion boa/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ impl Context {
args: &[JsValue],
) -> JsResult<JsValue> {
f.as_callable()
.ok_or_else(|| self.construct_type_error("not a function"))
.ok_or_else(|| self.construct_type_error("Value is not callable"))
.and_then(|obj| obj.call(this, args, self))
}

Expand Down
Loading

0 comments on commit 62fa5da

Please sign in to comment.