Browse Source

Add newTarget to construct (#1045)

* Add newTarget to construct

* Fix construct for self-mutating function

* Implement suggestions from review

Co-authored-by: tofpie <tofpie@users.noreply.github.com>
pull/1069/head
tofpie 3 years ago committed by GitHub
parent
commit
a7dd470cc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 50
      boa/src/builtins/array/mod.rs
  2. 30
      boa/src/builtins/boolean/mod.rs
  3. 30
      boa/src/builtins/date/mod.rs
  4. 21
      boa/src/builtins/error/eval.rs
  5. 22
      boa/src/builtins/error/mod.rs
  6. 24
      boa/src/builtins/error/range.rs
  7. 22
      boa/src/builtins/error/reference.rs
  8. 22
      boa/src/builtins/error/syntax.rs
  9. 22
      boa/src/builtins/error/type.rs
  10. 22
      boa/src/builtins/error/uri.rs
  11. 20
      boa/src/builtins/function/mod.rs
  12. 39
      boa/src/builtins/map/mod.rs
  13. 22
      boa/src/builtins/number/mod.rs
  14. 24
      boa/src/builtins/object/mod.rs
  15. 24
      boa/src/builtins/regexp/mod.rs
  16. 34
      boa/src/builtins/string/mod.rs
  17. 9
      boa/src/builtins/symbol/mod.rs
  18. 3
      boa/src/environment/lexical_environment.rs
  19. 74
      boa/src/object/gcobject.rs
  20. 8
      boa/src/object/mod.rs
  21. 2
      boa/src/syntax/ast/node/new/mod.rs

50
boa/src/builtins/array/mod.rs

@ -17,7 +17,7 @@ use crate::{
builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}, builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator},
builtins::BuiltIn, builtins::BuiltIn,
gc::GcObject, gc::GcObject,
object::{ConstructorBuilder, FunctionBuilder, ObjectData}, object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE},
property::{Attribute, DataDescriptor}, property::{Attribute, DataDescriptor},
value::{same_value_zero, IntegerOrInfinity, Value}, value::{same_value_zero, IntegerOrInfinity, Value},
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
@ -108,12 +108,21 @@ impl BuiltIn for Array {
impl Array { impl Array {
const LENGTH: usize = 1; const LENGTH: usize = 1;
fn constructor(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> { fn constructor(new_target: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().array_object().prototype());
// Delegate to the appropriate constructor based on the number of arguments // Delegate to the appropriate constructor based on the number of arguments
match args.len() { match args.len() {
0 => Array::construct_array_empty(this, context), 0 => Array::construct_array_empty(prototype, context),
1 => Array::construct_array_length(this, &args[0], context), 1 => Array::construct_array_length(prototype, &args[0], context),
_ => Array::construct_array_values(this, args, context), _ => Array::construct_array_values(prototype, args, context),
} }
} }
@ -123,10 +132,8 @@ impl Array {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-array-constructor-array /// [spec]: https://tc39.es/ecma262/#sec-array-constructor-array
fn construct_array_empty(this: &Value, context: &mut Context) -> Result<Value> { fn construct_array_empty(proto: GcObject, context: &mut Context) -> Result<Value> {
let prototype = context.standard_objects().array_object().prototype(); Array::array_create(0, Some(proto), context)
Array::array_create(this, 0, Some(prototype), context)
} }
/// By length constructor for `Array`. /// By length constructor for `Array`.
@ -136,12 +143,11 @@ impl Array {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-array-len /// [spec]: https://tc39.es/ecma262/#sec-array-len
fn construct_array_length( fn construct_array_length(
this: &Value, prototype: GcObject,
length: &Value, length: &Value,
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
let prototype = context.standard_objects().array_object().prototype(); let array = Array::array_create(0, Some(prototype), context)?;
let array = Array::array_create(this, 0, Some(prototype), context)?;
if !length.is_number() { if !length.is_number() {
array.set_property(0, DataDescriptor::new(length, Attribute::all())); array.set_property(0, DataDescriptor::new(length, Attribute::all()));
@ -163,13 +169,12 @@ impl Array {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-array-items /// [spec]: https://tc39.es/ecma262/#sec-array-items
fn construct_array_values( fn construct_array_values(
this: &Value, prototype: GcObject,
items: &[Value], items: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
let prototype = context.standard_objects().array_object().prototype();
let items_len = items.len().try_into().map_err(interror_to_value)?; let items_len = items.len().try_into().map_err(interror_to_value)?;
let array = Array::array_create(this, items_len, Some(prototype), context)?; let array = Array::array_create(items_len, Some(prototype), context)?;
for (k, item) in items.iter().enumerate() { for (k, item) in items.iter().enumerate() {
array.set_property(k, DataDescriptor::new(item.clone(), Attribute::all())); array.set_property(k, DataDescriptor::new(item.clone(), Attribute::all()));
@ -185,7 +190,6 @@ impl Array {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-arraycreate /// [spec]: https://tc39.es/ecma262/#sec-arraycreate
fn array_create( fn array_create(
this: &Value,
length: u32, length: u32,
prototype: Option<GcObject>, prototype: Option<GcObject>,
context: &mut Context, context: &mut Context,
@ -194,21 +198,23 @@ impl Array {
Some(prototype) => prototype, Some(prototype) => prototype,
None => context.standard_objects().array_object().prototype(), None => context.standard_objects().array_object().prototype(),
}; };
let array = Value::new_object(context);
this.as_object() array
.expect("this should be an array object") .as_object()
.expect("'array' should be an object")
.set_prototype_instance(prototype.into()); .set_prototype_instance(prototype.into());
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Array); array.set_data(ObjectData::Array);
let length = DataDescriptor::new( let length = DataDescriptor::new(
length, length,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
); );
this.set_property("length", length); array.set_property("length", length);
Ok(this.clone()) Ok(array)
} }
/// Creates a new `Array` instance. /// Creates a new `Array` instance.
@ -217,7 +223,7 @@ impl Array {
array.set_data(ObjectData::Array); array.set_data(ObjectData::Array);
array array
.as_object() .as_object()
.expect("array object") .expect("'array' should be an object")
.set_prototype_instance(context.standard_objects().array_object().prototype().into()); .set_prototype_instance(context.standard_objects().array_object().prototype().into());
let length = DataDescriptor::new( let length = DataDescriptor::new(
Value::from(0), Value::from(0),

30
boa/src/builtins/boolean/mod.rs

@ -14,7 +14,7 @@ mod tests;
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
property::Attribute, property::Attribute,
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
}; };
@ -56,12 +56,34 @@ impl Boolean {
/// `[[Construct]]` Create a new boolean object /// `[[Construct]]` Create a new boolean object
/// ///
/// `[[Call]]` Creates a new boolean primitive /// `[[Call]]` Creates a new boolean primitive
pub(crate) fn constructor(this: &Value, args: &[Value], _: &mut Context) -> Result<Value> { pub(crate) fn constructor(
new_target: &Value,
args: &[Value],
context: &mut Context,
) -> Result<Value> {
// Get the argument, if any // Get the argument, if any
let data = args.get(0).map(|x| x.to_boolean()).unwrap_or(false); let data = args.get(0).map(|x| x.to_boolean()).unwrap_or(false);
this.set_data(ObjectData::Boolean(data)); if new_target.is_undefined() {
return Ok(Value::from(data));
}
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().object_object().prototype());
let boolean = Value::new_object(context);
boolean
.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
boolean.set_data(ObjectData::Boolean(data));
Ok(Value::from(data)) Ok(boolean)
} }
/// An Utility function used to get the internal `[[BooleanData]]`. /// An Utility function used to get the internal `[[BooleanData]]`.

30
boa/src/builtins/date/mod.rs

@ -4,7 +4,7 @@ mod tests;
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
gc::{empty_trace, Finalize, Trace}, gc::{empty_trace, Finalize, Trace},
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
property::Attribute, property::Attribute,
value::{PreferredType, Value}, value::{PreferredType, Value},
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
@ -324,18 +324,32 @@ impl Date {
/// [spec]: https://tc39.es/ecma262/#sec-date-constructor /// [spec]: https://tc39.es/ecma262/#sec-date-constructor
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
if this.is_global() { if new_target.is_undefined() {
Self::make_date_string() Self::make_date_string()
} else if args.is_empty() {
Self::make_date_now(this)
} else if args.len() == 1 {
Self::make_date_single(this, args, context)
} else { } else {
Self::make_date_multiple(this, args, context) let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().object_object().prototype());
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let this = obj.into();
if args.is_empty() {
Self::make_date_now(&this)
} else if args.len() == 1 {
Self::make_date_single(&this, args, context)
} else {
Self::make_date_multiple(&this, args, context)
}
} }
} }

21
boa/src/builtins/error/eval.rs

@ -11,6 +11,7 @@
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror //! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError
use crate::object::PROTOTYPE;
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData},
@ -57,17 +58,31 @@ impl EvalError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().error_object().prototype());
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let this = Value::from(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", message.to_string(context)?, context)?; if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, context)?;
}
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error); this.set_data(ObjectData::Error);
Ok(this.clone()) Ok(this)
} }
} }

22
boa/src/builtins/error/mod.rs

@ -12,7 +12,7 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, Result, Value, Context, Result, Value,
@ -74,18 +74,32 @@ impl Error {
/// ///
/// Create a new error object. /// Create a new error object.
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().error_object().prototype());
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let this = Value::from(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", message.to_string(context)?, context)?; if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, context)?;
}
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error); this.set_data(ObjectData::Error);
Ok(this.clone()) Ok(this)
} }
/// `Error.prototype.toString()` /// `Error.prototype.toString()`

24
boa/src/builtins/error/range.rs

@ -11,13 +11,13 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, Result, Value, Context, Result, Value,
}; };
/// JavaScript `RangeError` impleentation. /// JavaScript `RangeError` implementation.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct RangeError; pub(crate) struct RangeError;
@ -55,17 +55,31 @@ impl RangeError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().error_object().prototype());
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let this = Value::from(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", message.to_string(context)?, context)?; if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, context)?;
}
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error); this.set_data(ObjectData::Error);
Ok(this.clone()) Ok(this)
} }
} }

22
boa/src/builtins/error/reference.rs

@ -11,7 +11,7 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, Result, Value, Context, Result, Value,
@ -54,17 +54,31 @@ impl ReferenceError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().error_object().prototype());
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let this = Value::from(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", message.to_string(context)?, context)?; if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, context)?;
}
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error); this.set_data(ObjectData::Error);
Ok(this.clone()) Ok(this)
} }
} }

22
boa/src/builtins/error/syntax.rs

@ -13,7 +13,7 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, Result, Value, Context, Result, Value,
@ -57,17 +57,31 @@ impl SyntaxError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().error_object().prototype());
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let this = Value::from(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", message.to_string(context)?, context)?; if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, context)?;
}
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error); this.set_data(ObjectData::Error);
Ok(this.clone()) Ok(this)
} }
} }

22
boa/src/builtins/error/type.rs

@ -17,7 +17,7 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
property::Attribute, property::Attribute,
BoaProfiler, Context, Result, Value, BoaProfiler, Context, Result, Value,
}; };
@ -60,17 +60,31 @@ impl TypeError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().error_object().prototype());
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let this = Value::from(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", message.to_string(context)?, context)?; if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, context)?;
}
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error); this.set_data(ObjectData::Error);
Ok(this.clone()) Ok(this)
} }
} }

22
boa/src/builtins/error/uri.rs

@ -12,7 +12,7 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, Result, Value, Context, Result, Value,
@ -56,17 +56,31 @@ impl UriError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().error_object().prototype());
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let this = Value::from(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", message.to_string(context)?, context)?; if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, context)?;
}
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error); this.set_data(ObjectData::Error);
Ok(this.clone()) Ok(this)
} }
} }

20
boa/src/builtins/function/mod.rs

@ -11,6 +11,7 @@
//! [spec]: https://tc39.es/ecma262/#sec-function-objects //! [spec]: https://tc39.es/ecma262/#sec-function-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
use crate::object::PROTOTYPE;
use crate::{ use crate::{
builtins::{Array, BuiltIn}, builtins::{Array, BuiltIn},
environment::lexical_environment::Environment, environment::lexical_environment::Environment,
@ -255,12 +256,27 @@ pub struct BuiltInFunctionObject;
impl BuiltInFunctionObject { impl BuiltInFunctionObject {
pub const LENGTH: usize = 1; pub const LENGTH: usize = 1;
fn constructor(this: &Value, _: &[Value], _: &mut Context) -> Result<Value> { fn constructor(new_target: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().object_object().prototype());
let this = Value::new_object(context);
this.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
this.set_data(ObjectData::Function(Function::BuiltIn( this.set_data(ObjectData::Function(Function::BuiltIn(
BuiltInFunction(|_, _, _| Ok(Value::undefined())), BuiltInFunction(|_, _, _| Ok(Value::undefined())),
FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE,
))); )));
Ok(this.clone()) Ok(this)
} }
fn prototype(_: &Value, _: &[Value], _: &mut Context) -> Result<Value> { fn prototype(_: &Value, _: &[Value], _: &mut Context) -> Result<Value> {

39
boa/src/builtins/map/mod.rs

@ -70,22 +70,33 @@ impl Map {
/// Create a new map /// Create a new map
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
// Set Prototype if new_target.is_undefined() {
let prototype = context return context.throw_type_error("Map requires new");
}
let map_prototype = context
.global_object() .global_object()
.clone() .clone()
.get_field("Map", context)? .get_field("Map", context)?
.get_field(PROTOTYPE, context)?; .get_field(PROTOTYPE, context)?
.as_object()
this.as_object() .expect("'Map' global property should be an object");
.expect("this is map object") let prototype = new_target
.set_prototype_instance(prototype); .as_object()
// This value is used by console.log and other routines to match Object type .and_then(|obj| {
// to its Javascript Identifier (global constructor method name) obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or(map_prototype);
let mut obj = context.construct_object();
obj.set_prototype_instance(prototype.into());
let this = Value::from(obj);
// add our arguments in // add our arguments in
let data = match args.len() { let data = match args.len() {
@ -122,12 +133,14 @@ impl Map {
}, },
}; };
// finally create length property // finally create size property
Self::set_size(this, data.len()); Self::set_size(&this, data.len());
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Map(data)); this.set_data(ObjectData::Map(data));
Ok(this.clone()) Ok(this)
} }
/// `Map.prototype.entries()` /// `Map.prototype.entries()`

22
boa/src/builtins/number/mod.rs

@ -16,7 +16,7 @@
use super::function::make_builtin_fn; use super::function::make_builtin_fn;
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
property::Attribute, property::Attribute,
value::{AbstractRelation, IntegerOrInfinity, Value}, value::{AbstractRelation, IntegerOrInfinity, Value},
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
@ -154,7 +154,7 @@ impl Number {
/// `Number( value )` /// `Number( value )`
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
@ -162,9 +162,25 @@ impl Number {
Some(ref value) => value.to_numeric_number(context)?, Some(ref value) => value.to_numeric_number(context)?,
None => 0.0, None => 0.0,
}; };
if new_target.is_undefined() {
return Ok(Value::from(data));
}
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().object_object().prototype());
let this = Value::new_object(context);
this.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
this.set_data(ObjectData::Number(data)); this.set_data(ObjectData::Number(data));
Ok(Value::from(data)) Ok(this)
} }
/// This function returns a `Result` of the number `Value`. /// This function returns a `Result` of the number `Value`.

24
boa/src/builtins/object/mod.rs

@ -15,7 +15,9 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::{ConstructorBuilder, Object as BuiltinObject, ObjectData, ObjectInitializer}, object::{
ConstructorBuilder, Object as BuiltinObject, ObjectData, ObjectInitializer, PROTOTYPE,
},
property::Attribute, property::Attribute,
property::DataDescriptor, property::DataDescriptor,
property::PropertyDescriptor, property::PropertyDescriptor,
@ -78,7 +80,25 @@ impl BuiltIn for Object {
impl Object { impl Object {
const LENGTH: usize = 1; const LENGTH: usize = 1;
fn constructor(_: &Value, args: &[Value], context: &mut Context) -> Result<Value> { fn constructor(new_target: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
if !new_target.is_undefined() {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().object_object().prototype());
let object = Value::new_object(context);
object
.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
return Ok(object);
}
if let Some(arg) = args.get(0) { if let Some(arg) = args.get(0) {
if !arg.is_null_or_undefined() { if !arg.is_null_or_undefined() {
return Ok(arg.to_object(context)?.into()); return Ok(arg.to_object(context)?.into());

24
boa/src/builtins/regexp/mod.rs

@ -12,7 +12,7 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
gc::{empty_trace, Finalize, Trace}, gc::{empty_trace, Finalize, Trace},
object::{ConstructorBuilder, ObjectData}, object::{ConstructorBuilder, ObjectData, PROTOTYPE},
property::{Attribute, DataDescriptor}, property::{Attribute, DataDescriptor},
value::{RcString, Value}, value::{RcString, Value},
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
@ -98,7 +98,25 @@ impl RegExp {
pub(crate) const LENGTH: usize = 2; pub(crate) const LENGTH: usize = 2;
/// Create a new `RegExp` /// Create a new `RegExp`
pub(crate) fn constructor(this: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> { pub(crate) fn constructor(
new_target: &Value,
args: &[Value],
ctx: &mut Context,
) -> Result<Value> {
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), ctx)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| ctx.standard_objects().regexp_object().prototype());
let this = Value::new_object(ctx);
this.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
let arg = args.get(0).ok_or_else(Value::undefined)?; let arg = args.get(0).ok_or_else(Value::undefined)?;
let (regex_body, mut regex_flags) = match arg { let (regex_body, mut regex_flags) = match arg {
@ -186,7 +204,7 @@ impl RegExp {
this.set_data(ObjectData::RegExp(Box::new(regexp))); this.set_data(ObjectData::RegExp(Box::new(regexp)));
Ok(this.clone()) Ok(this)
} }
// /// `RegExp.prototype.dotAll` // /// `RegExp.prototype.dotAll`

34
boa/src/builtins/string/mod.rs

@ -13,6 +13,8 @@ pub mod string_iterator;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use crate::builtins::Symbol;
use crate::object::PROTOTYPE;
use crate::property::DataDescriptor; use crate::property::DataDescriptor;
use crate::{ use crate::{
builtins::{string::string_iterator::StringIterator, Array, BuiltIn, RegExp}, builtins::{string::string_iterator::StringIterator, Array, BuiltIn, RegExp},
@ -131,26 +133,50 @@ impl String {
/// ///
/// <https://tc39.es/ecma262/#sec-string-constructor-string-value> /// <https://tc39.es/ecma262/#sec-string-constructor-string-value>
pub(crate) fn constructor( pub(crate) fn constructor(
this: &Value, new_target: &Value,
args: &[Value], args: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
// This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
let string = match args.get(0) { let string = match args.get(0) {
Some(value) if value.is_symbol() && new_target.is_undefined() => {
Symbol::to_string(value, &[], context)?
.as_string()
.expect("'Symbol::to_string' returns 'Value::String'")
.clone()
}
Some(ref value) => value.to_string(context)?, Some(ref value) => value.to_string(context)?,
None => RcString::default(), None => RcString::default(),
}; };
if new_target.is_undefined() {
return Ok(string.into());
}
let prototype = new_target
.as_object()
.and_then(|obj| {
obj.get(&PROTOTYPE.into(), obj.clone().into(), context)
.map(|o| o.as_object())
.transpose()
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().object_object().prototype());
let this = Value::new_object(context);
this.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
let length = DataDescriptor::new( let length = DataDescriptor::new(
Value::from(string.encode_utf16().count()), Value::from(string.encode_utf16().count()),
Attribute::NON_ENUMERABLE, Attribute::NON_ENUMERABLE,
); );
this.set_property("length", length); this.set_property("length", length);
this.set_data(ObjectData::String(string.clone())); this.set_data(ObjectData::String(string));
Ok(Value::from(string)) Ok(this)
} }
fn this_string_value(this: &Value, context: &mut Context) -> Result<RcString> { fn this_string_value(this: &Value, context: &mut Context) -> Result<RcString> {

9
boa/src/builtins/symbol/mod.rs

@ -315,7 +315,14 @@ impl Symbol {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-symbol-description /// [spec]: https://tc39.es/ecma262/#sec-symbol-description
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol
pub(crate) fn constructor(_: &Value, args: &[Value], context: &mut Context) -> Result<Value> { pub(crate) fn constructor(
new_target: &Value,
args: &[Value],
context: &mut Context,
) -> Result<Value> {
if new_target.is_undefined() {
return context.throw_type_error("Symbol is not a constructor");
}
let description = match args.get(0) { let description = match args.get(0) {
Some(ref value) if !value.is_undefined() => Some(value.to_string(context)?), Some(ref value) if !value.is_undefined() => Some(value.to_string(context)?),
_ => None, _ => None,

3
boa/src/environment/lexical_environment.rs

@ -266,13 +266,14 @@ pub fn new_function_environment(
this: Option<Value>, this: Option<Value>,
outer: Option<Environment>, outer: Option<Environment>,
binding_status: BindingStatus, binding_status: BindingStatus,
new_target: Value,
) -> Environment { ) -> Environment {
let mut func_env = FunctionEnvironmentRecord { let mut func_env = FunctionEnvironmentRecord {
env_rec: FxHashMap::default(), env_rec: FxHashMap::default(),
function: f, function: f,
this_binding_status: binding_status, this_binding_status: binding_status,
home_object: Value::undefined(), home_object: Value::undefined(),
new_target: Value::undefined(), new_target,
outer_env: outer, // this will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects outer_env: outer, // this will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects
this_value: Value::undefined(), this_value: Value::undefined(),
}; };

74
boa/src/object/gcobject.rs

@ -40,7 +40,8 @@ pub struct GcObject(Gc<GcCell<Object>>);
/// This is needed for the call method since we cannot mutate the function itself since we /// This is needed for the call method since we cannot mutate the function itself since we
/// already borrow it so we get the function body clone it then drop the borrow and run the body /// already borrow it so we get the function body clone it then drop the borrow and run the body
enum FunctionBody { enum FunctionBody {
BuiltIn(NativeFunction), BuiltInFunction(NativeFunction),
BuiltInConstructor(NativeFunction),
Ordinary(RcStatementList), Ordinary(RcStatementList),
} }
@ -119,8 +120,12 @@ impl GcObject {
let f_body = if let Some(function) = self.borrow().as_function() { let f_body = if let Some(function) = self.borrow().as_function() {
if function.is_callable() { if function.is_callable() {
match function { match function {
Function::BuiltIn(BuiltInFunction(function), _) => { Function::BuiltIn(BuiltInFunction(function), flags) => {
FunctionBody::BuiltIn(*function) if flags.is_constructable() {
FunctionBody::BuiltInConstructor(*function)
} else {
FunctionBody::BuiltInFunction(*function)
}
} }
Function::Ordinary { Function::Ordinary {
body, body,
@ -144,6 +149,7 @@ impl GcObject {
} else { } else {
BindingStatus::Uninitialized BindingStatus::Uninitialized
}, },
Value::undefined(),
); );
// Add argument bindings to the function environment // Add argument bindings to the function environment
@ -182,7 +188,8 @@ impl GcObject {
}; };
match f_body { match f_body {
FunctionBody::BuiltIn(func) => func(this, args, context), FunctionBody::BuiltInFunction(func) => func(this, args, context),
FunctionBody::BuiltInConstructor(func) => func(&Value::undefined(), args, context),
FunctionBody::Ordinary(body) => { FunctionBody::Ordinary(body) => {
let result = body.run(context); let result = body.run(context);
context.realm_mut().environment.pop(); context.realm_mut().environment.pop();
@ -199,29 +206,18 @@ impl GcObject {
/// Panics if the object is currently mutably borrowed. /// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget> // <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[track_caller] #[track_caller]
pub fn construct(&self, args: &[Value], context: &mut Context) -> Result<Value> { pub fn construct(
// If the prototype of the constructor is not an object, then use the default object &self,
// prototype as prototype for the new object args: &[Value],
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor> new_target: Value,
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor> context: &mut Context,
let proto = self.get(&PROTOTYPE.into(), self.clone().into(), context)?; ) -> Result<Value> {
let proto = if proto.is_object() {
proto
} else {
context
.standard_objects()
.object_object()
.prototype()
.into()
};
let this: Value = Object::create(proto).into();
let this_function_object = self.clone(); let this_function_object = self.clone();
let body = if let Some(function) = self.borrow().as_function() { let body = if let Some(function) = self.borrow().as_function() {
if function.is_constructable() { if function.is_constructable() {
match function { match function {
Function::BuiltIn(BuiltInFunction(function), _) => { Function::BuiltIn(BuiltInFunction(function), _) => {
FunctionBody::BuiltIn(*function) FunctionBody::BuiltInConstructor(*function)
} }
Function::Ordinary { Function::Ordinary {
body, body,
@ -229,11 +225,31 @@ impl GcObject {
environment, environment,
flags, flags,
} => { } => {
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let proto = new_target.as_object().unwrap().get(
&PROTOTYPE.into(),
new_target.clone(),
context,
)?;
let proto = if proto.is_object() {
proto
} else {
context
.standard_objects()
.object_object()
.prototype()
.into()
};
let this = Value::from(Object::create(proto));
// Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall> // <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment( let local_env = new_function_environment(
this_function_object, this_function_object,
Some(this.clone()), Some(this),
Some(environment.clone()), Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if flags.is_lexical_this_mode() { if flags.is_lexical_this_mode() {
@ -241,6 +257,7 @@ impl GcObject {
} else { } else {
BindingStatus::Uninitialized BindingStatus::Uninitialized
}, },
new_target.clone(),
); );
// Add argument bindings to the function environment // Add argument bindings to the function environment
@ -272,7 +289,10 @@ impl GcObject {
} }
} }
} else { } else {
let name = this.get_field("name", context)?.display().to_string(); let name = self
.get(&"name".into(), self.clone().into(), context)?
.display()
.to_string();
return context.throw_type_error(format!("{} is not a constructor", name)); return context.throw_type_error(format!("{} is not a constructor", name));
} }
} else { } else {
@ -280,10 +300,7 @@ impl GcObject {
}; };
match body { match body {
FunctionBody::BuiltIn(function) => { FunctionBody::BuiltInConstructor(function) => function(&new_target, args, context),
function(&this, args, context)?;
Ok(this)
}
FunctionBody::Ordinary(body) => { FunctionBody::Ordinary(body) => {
let _ = body.run(context); let _ = body.run(context);
@ -291,6 +308,7 @@ impl GcObject {
let binding = context.realm_mut().environment.get_this_binding(); let binding = context.realm_mut().environment.get_this_binding();
binding.map_err(|e| e.to_error(context)) binding.map_err(|e| e.to_error(context))
} }
FunctionBody::BuiltInFunction(_) => unreachable!("Cannot have a function in construct"),
} }
} }

8
boa/src/object/mod.rs

@ -828,7 +828,7 @@ impl<'context> ObjectInitializer<'context> {
/// Builder for creating constructors objects, like `Array`. /// Builder for creating constructors objects, like `Array`.
pub struct ConstructorBuilder<'context> { pub struct ConstructorBuilder<'context> {
context: &'context mut Context, context: &'context mut Context,
constrcutor_function: NativeFunction, constructor_function: NativeFunction,
constructor_object: GcObject, constructor_object: GcObject,
prototype: GcObject, prototype: GcObject,
name: Option<String>, name: Option<String>,
@ -858,7 +858,7 @@ impl<'context> ConstructorBuilder<'context> {
pub fn new(context: &'context mut Context, constructor: NativeFunction) -> Self { pub fn new(context: &'context mut Context, constructor: NativeFunction) -> Self {
Self { Self {
context, context,
constrcutor_function: constructor, constructor_function: constructor,
constructor_object: GcObject::new(Object::default()), constructor_object: GcObject::new(Object::default()),
prototype: GcObject::new(Object::default()), prototype: GcObject::new(Object::default()),
length: 0, length: 0,
@ -877,7 +877,7 @@ impl<'context> ConstructorBuilder<'context> {
) -> Self { ) -> Self {
Self { Self {
context, context,
constrcutor_function: constructor, constructor_function: constructor,
constructor_object: object.constructor, constructor_object: object.constructor,
prototype: object.prototype, prototype: object.prototype,
length: 0, length: 0,
@ -1020,7 +1020,7 @@ impl<'context> ConstructorBuilder<'context> {
pub fn build(&mut self) -> GcObject { pub fn build(&mut self) -> GcObject {
// Create the native function // Create the native function
let function = Function::BuiltIn( let function = Function::BuiltIn(
self.constrcutor_function.into(), self.constructor_function.into(),
FunctionFlags::from_parameters(self.callable, self.constructable), FunctionFlags::from_parameters(self.callable, self.constructable),
); );

2
boa/src/syntax/ast/node/new/mod.rs

@ -69,7 +69,7 @@ impl Executable for New {
} }
match func_object { match func_object {
Value::Object(ref object) => object.construct(&v_args, context), Value::Object(ref object) => object.construct(&v_args, object.clone().into(), context),
_ => context _ => context
.throw_type_error(format!("{} is not a constructor", self.expr().to_string(),)), .throw_type_error(format!("{} is not a constructor", self.expr().to_string(),)),
} }

Loading…
Cancel
Save