diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 02b2ff784e..9ef6b162f7 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -366,10 +366,7 @@ impl Array { // 7. If IsConstructor(C) is false, throw a TypeError exception. if let Some(c) = c.as_constructor() { // 8. Return ? Construct(C, « 𝔽(length) »). - Ok(c.construct(&[JsValue::new(length)], Some(c), context)? - .as_object() - .expect("constructing an object should always return an object") - .clone()) + c.construct(&[JsValue::new(length)], Some(c), context) } else { context.throw_type_error("Symbol.species must be a constructor") } @@ -418,13 +415,7 @@ impl Array { // b. Else, // i. Let A be ? ArrayCreate(0en). let a = match this.as_constructor() { - Some(constructor) => constructor - .construct(&[], None, context)? - .as_object() - .cloned() - .ok_or_else(|| { - context.construct_type_error("Object constructor didn't return an object") - })?, + Some(constructor) => constructor.construct(&[], None, context)?, _ => Self::array_create(0, None, context)?, }; @@ -497,13 +488,7 @@ impl Array { // 10. Else, // a. Let A be ? ArrayCreate(len). let a = match this.as_constructor() { - Some(constructor) => constructor - .construct(&[len.into()], None, context)? - .as_object() - .cloned() - .ok_or_else(|| { - context.construct_type_error("Object constructor didn't return an object") - })?, + Some(constructor) => constructor.construct(&[len.into()], None, context)?, _ => Self::array_create(len, None, context)?, }; @@ -579,13 +564,7 @@ impl Array { // 5. Else, // a. Let A be ? ArrayCreate(len). let a = match this.as_constructor() { - Some(constructor) => constructor - .construct(&[len.into()], None, context)? - .as_object() - .cloned() - .ok_or_else(|| { - context.construct_type_error("object constructor didn't return an object") - })?, + Some(constructor) => constructor.construct(&[len.into()], None, context)?, _ => Self::array_create(len, None, context)?, }; diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 647e4bdf12..8fe4c5dac7 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -243,13 +243,9 @@ impl ArrayBuffer { // 16. Let new be ? Construct(ctor, « 𝔽(newLen) »). let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; - // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). - let new_obj = new.as_object().cloned().ok_or_else(|| { - context.construct_type_error("ArrayBuffer constructor returned non-object value") - })?; - { - let new_obj = new_obj.borrow(); + let new_obj = new.borrow(); + // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| { context.construct_type_error("ArrayBuffer constructor returned invalid object") })?; @@ -264,44 +260,50 @@ impl ArrayBuffer { } } // 20. If SameValue(new, O) is true, throw a TypeError exception. - if JsValue::same_value(&new, this) { + if this + .as_object() + .map(|obj| JsObject::equals(obj, &new)) + .unwrap_or_default() + { 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`"); + { + let mut new_obj_borrow = new.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"); - } + // 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"); + } - // 22. NOTE: Side-effects of the above steps may have detached O. - // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. - if Self::is_detached_buffer(o) { - return context - .throw_type_error("ArrayBuffer detached while ArrayBuffer.slice was running"); - } + // 22. NOTE: Side-effects of the above steps may have detached O. + // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. + if Self::is_detached_buffer(o) { + return context + .throw_type_error("ArrayBuffer detached while ArrayBuffer.slice was running"); + } - // 24. Let fromBuf be O.[[ArrayBufferData]]. - let from_buf = o - .array_buffer_data - .as_ref() - .expect("ArrayBuffer cannot be detached here"); + // 24. Let fromBuf be O.[[ArrayBufferData]]. + let from_buf = o + .array_buffer_data + .as_ref() + .expect("ArrayBuffer cannot be detached here"); - // 25. Let toBuf be new.[[ArrayBufferData]]. - let to_buf = new_array_buffer - .array_buffer_data - .as_mut() - .expect("ArrayBuffer cannot be detached here"); + // 25. Let toBuf be new.[[ArrayBufferData]]. + let to_buf = new_array_buffer + .array_buffer_data + .as_mut() + .expect("ArrayBuffer cannot be detached here"); - // 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). - copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len); + // 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). + copy_data_block_bytes(to_buf, 0, from_buf, first as usize, new_len); + } // 27. Return new. - Ok(new) + Ok(new.into()) } /// `25.1.2.1 AllocateArrayBuffer ( constructor, byteLength )` diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 4c82327955..1f39077784 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -103,7 +103,7 @@ pub(crate) fn create_throw_type_error(context: &mut Context) -> JsObject { context.intrinsics().constructors().function().prototype(), ObjectData::function(Function::Native { function: throw_type_error, - constructor: false, + constructor: None, }), ); diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 19bd4a8cea..f6c2957d58 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -108,7 +108,7 @@ impl ThisMode { } } -#[derive(Debug, Trace, Finalize, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum ConstructorKind { Base, Derived, @@ -178,12 +178,14 @@ pub enum Function { Native { #[unsafe_ignore_trace] function: NativeFunctionSignature, - constructor: bool, + #[unsafe_ignore_trace] + constructor: Option, }, Closure { #[unsafe_ignore_trace] function: Box, - constructor: bool, + #[unsafe_ignore_trace] + constructor: Option, captures: Captures, }, Ordinary { @@ -205,6 +207,11 @@ impl fmt::Debug for Function { impl Function { /// Returns true if the function object is a constructor. pub fn is_constructor(&self) -> bool { + self.constructor().is_some() + } + + /// Returns the constructor kind if the function is constructable, or `None` otherwise. + pub fn constructor(&self) -> Option { match self { Self::Native { constructor, .. } | Self::Closure { constructor, .. } => *constructor, Self::Ordinary { code, .. } | Self::Generator { code, .. } => code.constructor, @@ -251,7 +258,7 @@ pub(crate) fn make_builtin_fn( .prototype(), ObjectData::function(Function::Native { function, - constructor: false, + constructor: None, }), ); let attribute = PropertyDescriptor::builder() @@ -417,7 +424,7 @@ impl BuiltInFunctionObject { prototype, ObjectData::function(Function::Native { function: |_, _, _| Ok(JsValue::undefined()), - constructor: true, + constructor: Some(ConstructorKind::Base), }), ); diff --git a/boa_engine/src/builtins/generator_function/mod.rs b/boa_engine/src/builtins/generator_function/mod.rs index 19d761c5f8..70161a144a 100644 --- a/boa_engine/src/builtins/generator_function/mod.rs +++ b/boa_engine/src/builtins/generator_function/mod.rs @@ -21,6 +21,8 @@ use crate::{ }; use boa_profiler::Profiler; +use super::function::ConstructorKind; + /// The internal representation on a `Generator` object. #[derive(Debug, Clone, Copy)] pub struct GeneratorFunction; @@ -71,7 +73,7 @@ impl BuiltIn for GeneratorFunction { constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().data = ObjectData::function(Function::Native { function: Self::constructor, - constructor: true, + constructor: Some(ConstructorKind::Base), }); prototype.set_prototype(Some( @@ -124,7 +126,7 @@ impl GeneratorFunction { prototype, ObjectData::function(Function::Native { function: |_, _, _| Ok(JsValue::undefined()), - constructor: true, + constructor: Some(ConstructorKind::Base), }), ); diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 8f7c8c9933..2c4382bd76 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -13,7 +13,7 @@ use crate::{ job::JobCallback, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, ObjectData, + JsFunction, JsObject, ObjectData, }, property::Attribute, symbol::WellKnownSymbols, @@ -39,10 +39,14 @@ macro_rules! if_abrupt_reject_promise { // 1. If value is an abrupt completion, then Err(value) => { // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). - $context.call(&$capability.reject, &JsValue::undefined(), &[value])?; + $context.call( + &$capability.reject.clone().into(), + &JsValue::undefined(), + &[value], + )?; // b. Return capability.[[Promise]]. - return Ok($capability.promise.clone()); + return Ok($capability.promise.clone().into()); } // 2. Else if value is a Completion Record, set value to value.[[Value]]. Ok(value) => value, @@ -83,20 +87,9 @@ enum ReactionType { #[derive(Debug, Clone, Trace, Finalize)] struct PromiseCapability { - promise: JsValue, - resolve: JsValue, - reject: JsValue, -} - -#[derive(Debug, Trace, Finalize)] -struct PromiseCapabilityCaptures { - promise_capability: Gc>, -} - -#[derive(Debug, Trace, Finalize)] -struct ReactionJobCaptures { - reaction: ReactionRecord, - argument: JsValue, + promise: JsObject, + resolve: JsFunction, + reject: JsFunction, } impl PromiseCapability { @@ -107,6 +100,12 @@ impl PromiseCapability { /// /// [spec]: https://tc39.es/ecma262/#sec-newpromisecapability fn new(c: &JsValue, context: &mut Context) -> JsResult { + #[derive(Debug, Clone, Trace, Finalize)] + struct RejectResolve { + reject: JsValue, + resolve: JsValue, + } + match c.as_constructor() { // 1. If IsConstructor(C) is false, throw a TypeError exception. None => context.throw_type_error("PromiseCapability: expected constructor"), @@ -115,20 +114,17 @@ impl PromiseCapability { // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). // 3. Let promiseCapability be the PromiseCapability Record { [[Promise]]: undefined, [[Resolve]]: undefined, [[Reject]]: undefined }. - let promise_capability = Gc::new(boa_gc::Cell::new(Self { - promise: JsValue::Undefined, - reject: JsValue::Undefined, - resolve: JsValue::Undefined, + let promise_capability = Gc::new(boa_gc::Cell::new(RejectResolve { + reject: JsValue::undefined(), + resolve: JsValue::undefined(), })); // 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called: // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). let executor = FunctionBuilder::closure_with_captures( context, - |_this, args: &[JsValue], captures: &mut PromiseCapabilityCaptures, context| { - let promise_capability: &mut Self = - &mut captures.promise_capability.try_borrow_mut().expect("msg"); - + |_this, args: &[JsValue], captures, context| { + let mut promise_capability = captures.borrow_mut(); // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. if !promise_capability.resolve.is_undefined() { return context.throw_type_error( @@ -154,9 +150,7 @@ impl PromiseCapability { // e. Return undefined. Ok(JsValue::Undefined) }, - PromiseCapabilityCaptures { - promise_capability: promise_capability.clone(), - }, + promise_capability.clone(), ) .name("") .length(2) @@ -166,29 +160,37 @@ impl PromiseCapability { // 6. Let promise be ? Construct(C, « executor »). let promise = c.construct(&[executor], Some(&c), context)?; - let promise_capability: &mut Self = - &mut promise_capability.try_borrow_mut().expect("msg"); + let promise_capability = promise_capability.borrow(); let resolve = promise_capability.resolve.clone(); let reject = promise_capability.reject.clone(); // 7. If IsCallable(promiseCapability.[[Resolve]]) is false, throw a TypeError exception. - if !resolve.is_callable() { - return context - .throw_type_error("promiseCapability.[[Resolve]] is not callable"); - } + let resolve = resolve + .as_object() + .cloned() + .and_then(JsFunction::from_object) + .ok_or_else(|| { + context + .construct_type_error("promiseCapability.[[Resolve]] is not callable") + })?; // 8. If IsCallable(promiseCapability.[[Reject]]) is false, throw a TypeError exception. - if !reject.is_callable() { - return context - .throw_type_error("promiseCapability.[[Reject]] is not callable"); - } + let reject = reject + .as_object() + .cloned() + .and_then(JsFunction::from_object) + .ok_or_else(|| { + context.construct_type_error("promiseCapability.[[Reject]] is not callable") + })?; // 9. Set promiseCapability.[[Promise]] to promise. - promise_capability.reject = promise; - // 10. Return promiseCapability. - Ok(promise_capability.clone()) + Ok(PromiseCapability { + promise, + resolve, + reject, + }) } } } @@ -246,13 +248,6 @@ struct ResolvingFunctionsRecord { reject: JsValue, } -#[derive(Debug, Trace, Finalize)] -struct RejectResolveCaptures { - promise: JsObject, - #[unsafe_ignore_trace] - already_resolved: Rc>, -} - impl Promise { const LENGTH: usize = 1; @@ -298,10 +293,10 @@ impl Promise { }), ); - // // 8. Let resolvingFunctions be CreateResolvingFunctions(promise). + // 8. Let resolvingFunctions be CreateResolvingFunctions(promise). let resolving_functions = Self::create_resolving_functions(&promise, context); - // // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ). + // 9. Let completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)be ). let completion = context.call( executor, &JsValue::Undefined, @@ -331,6 +326,13 @@ impl Promise { promise: &JsObject, context: &mut Context, ) -> ResolvingFunctionsRecord { + #[derive(Debug, Trace, Finalize)] + struct RejectResolveCaptures { + promise: JsObject, + #[unsafe_ignore_trace] + already_resolved: Rc>, + } + // 1. Let alreadyResolved be the Record { [[Value]]: false }. let already_resolved = Rc::new(Cell::new(false)); @@ -739,8 +741,8 @@ impl Promise { next_promise.invoke( "then", &[ - result_capability.resolve.clone(), - result_capability.reject.clone(), + result_capability.resolve.clone().into(), + result_capability.reject.clone().into(), ], context, )?; @@ -750,7 +752,7 @@ impl Promise { iterator_record.set_done(true); // ii. Return resultCapability.[[Promise]]. - return Ok(result_capability.promise.clone()); + return Ok(result_capability.promise.clone().into()); } } } @@ -774,13 +776,13 @@ impl Promise { // 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »). context.call( - &promise_capability.reject, + &promise_capability.reject.clone().into(), &JsValue::undefined(), &[r.clone()], )?; // 4. Return promiseCapability.[[Promise]]. - Ok(promise_capability.promise.clone()) + Ok(promise_capability.promise.clone().into()) } /// `Promise.resolve ( x )` @@ -1131,7 +1133,7 @@ impl Promise { // 14. Else, // a. Return resultCapability.[[Promise]]. - Some(result_capability) => result_capability.promise.clone(), + Some(result_capability) => result_capability.promise.clone().into(), } } @@ -1160,10 +1162,14 @@ impl Promise { let promise_capability = PromiseCapability::new(&c.into(), context)?; // 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). - context.call(&promise_capability.resolve, &JsValue::undefined(), &[x])?; + context.call( + &promise_capability.resolve.clone().into(), + &JsValue::undefined(), + &[x], + )?; // 4. Return promiseCapability.[[Promise]]. - Ok(promise_capability.promise.clone()) + Ok(promise_capability.promise.clone().into()) } /// `GetPromiseResolve ( promiseConstructor )` diff --git a/boa_engine/src/builtins/promise/promise_job.rs b/boa_engine/src/builtins/promise/promise_job.rs index 093b3cba46..30b486e93e 100644 --- a/boa_engine/src/builtins/promise/promise_job.rs +++ b/boa_engine/src/builtins/promise/promise_job.rs @@ -1,4 +1,4 @@ -use super::{Promise, PromiseCapability, ReactionJobCaptures}; +use super::{Promise, PromiseCapability}; use crate::{ builtins::promise::{ReactionRecord, ReactionType}, job::JobCallback, @@ -20,6 +20,12 @@ impl PromiseJob { argument: JsValue, context: &mut Context, ) -> JobCallback { + #[derive(Debug, Trace, Finalize)] + struct ReactionJobCaptures { + reaction: ReactionRecord, + argument: JsValue, + } + // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called: let job = FunctionBuilder::closure_with_captures( context, @@ -77,13 +83,13 @@ impl PromiseJob { // h. If handlerResult is an abrupt completion, then Err(value) => { // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). - context.call(reject, &JsValue::Undefined, &[value]) + context.call(&reject.clone().into(), &JsValue::Undefined, &[value]) } // i. Else, Ok(value) => { // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). - context.call(resolve, &JsValue::Undefined, &[value]) + context.call(&resolve.clone().into(), &JsValue::Undefined, &[value]) } } } diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index 9835c35834..52da25618f 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -126,7 +126,9 @@ impl Reflect { .create_list_from_array_like(&[], context)?; // 5. Return ? Construct(target, args, newTarget). - target.construct(&args, Some(new_target), context) + target + .construct(&args, Some(new_target), context) + .map(JsValue::from) } /// Defines a property on an object. diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 49f419b17a..485e46ee17 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -1184,9 +1184,6 @@ impl RegExp { // 6. Let matcher be ? Construct(C, « R, flags »). let matcher = c.construct(&[this.clone(), flags.clone().into()], Some(&c), context)?; - let matcher = matcher - .as_object() - .expect("construct must always return an Object"); // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")). let last_index = regexp.get("lastIndex", context)?.to_length(context)?; @@ -1579,11 +1576,6 @@ impl RegExp { Some(&constructor), context, )?; - 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"))?; // 11. Let A be ! ArrayCreate(0). let a = Array::array_create(0, None, context).expect("this ArrayCreate call must not fail"); @@ -1610,7 +1602,7 @@ impl RegExp { // 16. If size is 0, then if size == 0 { // a. Let z be ? RegExpExec(splitter, S). - let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; + let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?; // b. If z is not null, return A. if result.is_some() { @@ -1636,7 +1628,7 @@ impl RegExp { splitter.set("lastIndex", JsValue::new(q), true, context)?; // b. Let z be ? RegExpExec(splitter, S). - let result = Self::abstract_exec(splitter, arg_str.clone(), context)?; + let result = Self::abstract_exec(&splitter, arg_str.clone(), context)?; // c. If z is null, set q to AdvanceStringIndex(S, q, unicodeMatching). // d. Else, diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 2fcf0ba16c..61625f8d51 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -2970,11 +2970,8 @@ impl TypedArray { // 1. Let newTypedArray be ? Construct(constructor, argumentList). let new_typed_array = constructor.construct(args, Some(constructor), context)?; + let obj_borrow = new_typed_array.borrow(); // 2. Perform ? ValidateTypedArray(newTypedArray). - let obj = new_typed_array - .as_object() - .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; - let obj_borrow = obj.borrow(); let o = obj_borrow .as_typed_array() .ok_or_else(|| context.construct_type_error("Value is not a typed array object"))?; @@ -2994,7 +2991,7 @@ impl TypedArray { } // 4. Return newTypedArray. - Ok(obj.clone()) + Ok(new_typed_array.clone()) } /// diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler.rs index d354ebfba2..ee0c3c43d1 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/boa_engine/src/bytecompiler.rs @@ -1,5 +1,5 @@ use crate::{ - builtins::function::ThisMode, + builtins::function::{ConstructorKind, ThisMode}, environments::{BindingLocator, CompileTimeEnvironment}, syntax::ast::{ node::{ @@ -81,7 +81,7 @@ impl<'b> ByteCompiler<'b> { #[inline] pub fn new(name: Sym, strict: bool, context: &'b mut Context) -> Self { Self { - code_block: CodeBlock::new(name, 0, strict, false), + code_block: CodeBlock::new(name, 0, strict, None), literals_map: FxHashMap::default(), names_map: FxHashMap::default(), bindings_map: FxHashMap::default(), @@ -1927,15 +1927,20 @@ impl<'b> ByteCompiler<'b> { ) -> JsResult> { let strict = strict || body.strict(); let length = parameters.length(); - let mut code = CodeBlock::new(name.unwrap_or(Sym::EMPTY_STRING), length, strict, true); + let mut code = CodeBlock::new( + name.unwrap_or(Sym::EMPTY_STRING), + length, + strict, + Some(ConstructorKind::Base), + ); if let FunctionKind::Arrow = kind { - code.constructor = false; + code.constructor = None; code.this_mode = ThisMode::Lexical; } if generator { - code.constructor = false; + code.constructor = None; } let mut compiler = ByteCompiler { @@ -2536,7 +2541,16 @@ impl<'b> ByteCompiler<'b> { /// A class declaration binds the resulting class object to it's identifier. /// A class expression leaves the resulting class object on the stack for following operations. fn class(&mut self, class: &Class, expression: bool) -> JsResult<()> { - let mut code = CodeBlock::new(class.name(), 0, true, true); + let mut code = CodeBlock::new( + class.name(), + 0, + true, + Some(if class.super_ref().is_some() { + ConstructorKind::Derived + } else { + ConstructorKind::Base + }), + ); code.computed_field_names = Some(gc::GcCell::new(vec![])); let mut compiler = ByteCompiler { code_block: code, diff --git a/boa_engine/src/object/internal_methods/bound_function.rs b/boa_engine/src/object/internal_methods/bound_function.rs index 1b504d44b3..7675e7272a 100644 --- a/boa_engine/src/object/internal_methods/bound_function.rs +++ b/boa_engine/src/object/internal_methods/bound_function.rs @@ -70,7 +70,7 @@ fn bound_function_exotic_construct( arguments_list: &[JsValue], new_target: &JsObject, context: &mut Context, -) -> JsResult { +) -> JsResult { let object = obj.borrow(); let bound_function = object .as_bound_function() diff --git a/boa_engine/src/object/internal_methods/function.rs b/boa_engine/src/object/internal_methods/function.rs index c80ace69ee..26dcc86b87 100644 --- a/boa_engine/src/object/internal_methods/function.rs +++ b/boa_engine/src/object/internal_methods/function.rs @@ -55,6 +55,6 @@ fn function_construct( args: &[JsValue], new_target: &JsObject, context: &mut Context, -) -> JsResult { +) -> JsResult { obj.construct_internal(args, &new_target.clone().into(), context) } diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index 4e660fbf3b..7591378778 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -261,7 +261,7 @@ impl JsObject { args: &[JsValue], new_target: &JsObject, context: &mut Context, - ) -> JsResult { + ) -> JsResult { let _timer = Profiler::global().start_event("Object::__construct__", "object"); let func = self.borrow().data.internal_methods.__construct__; func.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")( @@ -324,7 +324,7 @@ pub(crate) struct InternalObjectMethods { pub(crate) __call__: Option JsResult>, pub(crate) __construct__: - Option JsResult>, + Option JsResult>, } /// Abstract operation `OrdinaryGetPrototypeOf`. diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index 3359153b20..c70175923d 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -946,7 +946,7 @@ fn proxy_exotic_construct( args: &[JsValue], new_target: &JsObject, context: &mut Context, -) -> JsResult { +) -> JsResult { // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. // 3. Assert: Type(handler) is Object. @@ -984,9 +984,9 @@ fn proxy_exotic_construct( )?; // 10. If Type(newObj) is not Object, throw a TypeError exception. - if !new_obj.is_object() { - return context.throw_type_error("Proxy trap constructor returned non-object value"); - } + let new_obj = new_obj.as_object().cloned().ok_or_else(|| { + context.construct_type_error("Proxy trap constructor returned non-object value") + })?; // 11. Return newObj. Ok(new_obj) diff --git a/boa_engine/src/object/jsfunction.rs b/boa_engine/src/object/jsfunction.rs index 70cca94d72..8d45504885 100644 --- a/boa_engine/src/object/jsfunction.rs +++ b/boa_engine/src/object/jsfunction.rs @@ -1,6 +1,6 @@ use crate::{ object::{JsObject, JsObjectType}, - Context, JsResult, JsValue, + JsValue, }; use boa_gc::{Finalize, Trace}; use std::ops::Deref; @@ -17,16 +17,14 @@ impl JsFunction { Self { inner: object } } - /// Create a [`JsFunction`] from a [`JsObject`], if the object is not a function throw a `TypeError`. + /// Create a [`JsFunction`] from a [`JsObject`], or return `None` if the object is not a function. /// /// This does not clone the fields of the function, it only does a shallow clone of the object. #[inline] - pub fn from_object(object: JsObject, context: &mut Context) -> JsResult { - if object.borrow().is_function() { - Ok(Self::from_object_unchecked(object)) - } else { - context.throw_type_error("object is not an Function") - } + pub fn from_object(object: JsObject) -> Option { + object + .is_callable() + .then(|| Self::from_object_unchecked(object)) } } diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 2bd8d95c37..31e10166a1 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -28,7 +28,8 @@ use crate::{ array_buffer::ArrayBuffer, function::arguments::Arguments, function::{ - arguments::ParameterMap, BoundFunction, Captures, Function, NativeFunctionSignature, + arguments::ParameterMap, BoundFunction, Captures, ConstructorKind, Function, + NativeFunctionSignature, }, generator::Generator, map::map_iterator::MapIterator, @@ -1460,7 +1461,7 @@ impl<'context> FunctionBuilder<'context> { context, function: Function::Native { function, - constructor: false, + constructor: None, }, name: JsString::default(), length: 0, @@ -1477,7 +1478,7 @@ impl<'context> FunctionBuilder<'context> { context, function: Function::Closure { function: Box::new(move |this, args, _, context| function(this, args, context)), - constructor: false, + constructor: None, captures: Captures::new(()), }, name: JsString::default(), @@ -1511,7 +1512,7 @@ impl<'context> FunctionBuilder<'context> { })?; function(this, args, captures, context) }), - constructor: false, + constructor: None, captures: Captures::new(captures), }, name: JsString::default(), @@ -1559,7 +1560,7 @@ impl<'context> FunctionBuilder<'context> { ref mut constructor, .. } => { - *constructor = yes; + *constructor = yes.then(|| ConstructorKind::Base); } Function::Ordinary { .. } | Function::Generator { .. } => { unreachable!("function must be native or closure"); @@ -1716,7 +1717,7 @@ pub struct ConstructorBuilder<'context> { name: JsString, length: usize, callable: bool, - constructor: bool, + constructor: Option, inherit: Option, custom_prototype: Option, } @@ -1748,7 +1749,7 @@ impl<'context> ConstructorBuilder<'context> { length: 0, name: JsString::default(), callable: true, - constructor: true, + constructor: Some(ConstructorKind::Base), inherit: None, custom_prototype: None, has_prototype_property: true, @@ -1770,7 +1771,7 @@ impl<'context> ConstructorBuilder<'context> { length: 0, name: JsString::default(), callable: true, - constructor: true, + constructor: Some(ConstructorKind::Base), inherit: None, custom_prototype: None, } @@ -1967,7 +1968,7 @@ impl<'context> ConstructorBuilder<'context> { /// Default is `true` #[inline] pub fn constructor(&mut self, constructor: bool) -> &mut Self { - self.constructor = constructor; + self.constructor = constructor.then(|| ConstructorKind::Base); self } diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index 5a8ef6accb..744ef15d61 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -328,7 +328,7 @@ impl JsObject { args: &[JsValue], new_target: Option<&JsObject>, context: &mut Context, - ) -> JsResult { + ) -> JsResult { // 1. If newTarget is not present, set newTarget to F. let new_target = new_target.unwrap_or(self); // 2. If argumentsList is not present, set argumentsList to a new empty List. diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 99d896be55..412d11ce74 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -5,7 +5,7 @@ use crate::{ builtins::{ function::{ - arguments::Arguments, Captures, ClosureFunctionSignature, Function, + arguments::Arguments, Captures, ClosureFunctionSignature, ConstructorKind, Function, NativeFunctionSignature, ThisMode, }, generator::{Generator, GeneratorContext, GeneratorState}, @@ -62,10 +62,11 @@ pub struct CodeBlock { /// Is this function in strict mode. pub(crate) strict: bool, - /// Is this function a constructor. - pub(crate) constructor: bool, + /// Constructor type of this function, or `None` if the function is not constructable. + #[unsafe_ignore_trace] + pub(crate) constructor: Option, - /// [[ThisMode]] + /// \[\[ThisMode\]\] pub(crate) this_mode: ThisMode, /// Parameters passed to this function. @@ -108,7 +109,7 @@ pub struct CodeBlock { impl CodeBlock { /// Constructs a new `CodeBlock`. - pub fn new(name: Sym, length: u32, strict: bool, constructor: bool) -> Self { + pub fn new(name: Sym, length: u32, strict: bool, constructor: Option) -> Self { Self { code: Vec::new(), literals: Vec::new(), @@ -588,7 +589,7 @@ impl JsObject { function, constructor, } => { - if *constructor { + if constructor.is_some() { construct = true; } @@ -868,17 +869,28 @@ impl JsObject { args: &[JsValue], this_target: &JsValue, context: &mut Context, - ) -> JsResult { + ) -> JsResult { let this_function_object = self.clone(); // let mut has_parameter_expressions = false; + let create_this = |context| { + let prototype = + get_prototype_from_constructor(this_target, StandardConstructors::object, context)?; + Ok(Self::from_proto_and_data(prototype, ObjectData::ordinary())) + }; + if !self.is_constructor() { return context.throw_type_error("not a constructor function"); } + let constructor; + let body = { let object = self.borrow(); let function = object.as_function().expect("not a function"); + constructor = function + .constructor() + .expect("Already checked that the function was a constructor"); match function { Function::Native { function, .. } => FunctionBody::Native { @@ -901,9 +913,31 @@ impl JsObject { }; match body { - FunctionBody::Native { function, .. } => function(this_target, args, context), + FunctionBody::Native { function, .. } => match function(this_target, args, context)? { + JsValue::Object(ref o) => Ok(o.clone()), + val => { + if constructor.is_base() || val.is_undefined() { + create_this(context) + } else { + context.throw_type_error( + "Derived constructor can only return an Object or undefined", + ) + } + } + }, FunctionBody::Closure { function, captures } => { - (function)(this_target, args, captures, context) + match (function)(this_target, args, captures, context)? { + JsValue::Object(ref o) => Ok(o.clone()), + val => { + if constructor.is_base() || val.is_undefined() { + create_this(context) + } else { + context.throw_type_error( + "Derived constructor can only return an Object or undefined", + ) + } + } + } } FunctionBody::Ordinary { code, @@ -911,27 +945,14 @@ impl JsObject { } => { std::mem::swap(&mut environments, &mut context.realm.environments); - let this = { - // If the prototype of the constructor is not an object, then use the default object - // prototype as prototype for the new object - // see - // see - let prototype = get_prototype_from_constructor( - this_target, - StandardConstructors::object, - context, - )?; - let this = Self::from_proto_and_data(prototype, ObjectData::ordinary()); - - // Set computed class field names if they exist. - if let Some(fields) = &code.computed_field_names { - for key in fields.borrow().iter().rev() { - context.vm.push(key); - } - } + let this = create_this(context)?; - this - }; + // Set computed class field names if they exist. + if let Some(fields) = &code.computed_field_names { + for key in fields.borrow().iter().rev() { + context.vm.push(key); + } + } if code.params.has_expressions() { context.realm.environments.push_function( @@ -1034,10 +1055,21 @@ impl JsObject { let (result, _) = result?; - if result.is_object() { - Ok(result) - } else { - Ok(frame.this.clone()) + match result { + JsValue::Object(ref obj) => Ok(obj.clone()), + val => { + if constructor.is_base() || val.is_undefined() { + Ok(frame + .this + .as_object() + .cloned() + .expect("13. Assert: Type(thisBinding) is Object.")) + } else { + context.throw_type_error( + "Derived constructor can only return an Object or undefined", + ) + } + } } } FunctionBody::Generator { .. } => { diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 7ceee54749..84692c9185 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -1481,9 +1481,9 @@ impl Context { [compile_environments_index as usize] .clone(); - let is_constructor = self.vm.frame().code.constructor; + let constructor = self.vm.frame().code.constructor; let is_lexical = self.vm.frame().code.this_mode.is_lexical(); - let this = if is_constructor || !is_lexical { + let this = if constructor.is_some() || !is_lexical { self.vm.frame().this.clone() } else { JsValue::undefined()