Skip to content

Commit

Permalink
Merge c72dd00 into 62c99f5
Browse files Browse the repository at this point in the history
  • Loading branch information
0x7D2B authored May 23, 2021
2 parents 62c99f5 + c72dd00 commit e02d2f7
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 136 deletions.
2 changes: 1 addition & 1 deletion boa/src/builtins/reflect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl Reflect {
};

let args = args_list.create_list_from_array_like(&[], context)?;
target.construct(&args, new_target, context)
target.construct(&args, &new_target, context)
}

/// Defines a property on an object.
Expand Down
274 changes: 140 additions & 134 deletions boa/src/object/gcobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,40 @@ impl GcObject {
std::ptr::eq(lhs.as_ref(), rhs.as_ref())
}

/// Call this object.
/// Internal implementation of [`call`](#method.call) and [`construct`](#method.construct).
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
///
/// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
/// <https://tc39.es/ecma262/#sec-ordinarycallbindthis>
/// <https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody>
/// <https://tc39.es/ecma262/#sec-ordinarycallevaluatebody>
#[track_caller]
pub fn call(&self, this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
fn call_construct(
&self,
this_target: &Value,
args: &[Value],
context: &mut Context,
construct: bool,
) -> Result<Value> {
let this_function_object = self.clone();
let f_body = if let Some(function) = self.borrow().as_function() {
if function.is_callable() {
let mut has_parameter_expressions = false;

let body = if let Some(function) = self.borrow().as_function() {
if construct && !function.is_constructable() {
let name = self
.get(&"name".into(), self.clone().into(), context)?
.display()
.to_string();
return context.throw_type_error(format!("{} is not a constructor", name));
} else if !construct && !function.is_callable() {
return context.throw_type_error("function object is not callable");
} else {
match function {
Function::BuiltIn(BuiltInFunction(function), flags) => {
if flags.is_constructable() {
if flags.is_constructable() || construct {
FunctionBody::BuiltInConstructor(*function)
} else {
FunctionBody::BuiltInFunction(*function)
Expand All @@ -131,14 +150,38 @@ impl GcObject {
environment,
flags,
} => {
let this = if construct {
// 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 = this_target.as_object().unwrap().get(
&PROTOTYPE.into(),
this_target.clone(),
context,
)?;
let proto = if proto.is_object() {
proto
} else {
context
.standard_objects()
.object_object()
.prototype()
.into()
};
Value::from(Object::create(proto))
} else {
this_target.clone()
};

// Create a new Function environment whose parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object,
if flags.is_lexical_this_mode() {
None
} else {
this_function_object.clone(),
if construct || !flags.is_lexical_this_mode() {
Some(this.clone())
} else {
None
},
Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
Expand All @@ -150,19 +193,6 @@ impl GcObject {
Value::undefined(),
);

// Add argument bindings to the function environment
for (i, param) in params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
function.add_rest_param(param, i, args, context, &local_env);
break;
}

let value = args.get(i).cloned().unwrap_or_else(Value::undefined);
function
.add_arguments_to_environment(param, value, &local_env, context);
}

// Add arguments object
let arguments_obj = create_unmapped_arguments_object(args);
local_env.borrow_mut().create_mutable_binding(
Expand All @@ -176,146 +206,122 @@ impl GcObject {
arguments_obj,
context,
)?;
// push the environment first so that it will be used by default parameters
context.push_environment(local_env.clone());

// Add argument bindings to the function environment
for (i, param) in params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
function.add_rest_param(param, i, args, context, &local_env);
break;
}

has_parameter_expressions =
has_parameter_expressions || param.init().is_some();

context.push_environment(local_env);
let value = match args.get(i).cloned() {
None | Some(Value::Undefined) => param
.init()
.map(|init| init.run(context).ok())
.flatten()
.unwrap_or_default(),
Some(value) => value,
};

function
.add_arguments_to_environment(param, value, &local_env, context);
}

if has_parameter_expressions {
// Create a second environment when default parameter expressions are used
// This prevents variables declared in the function body from being
// used in default parameter initializers.
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
let second_env = FunctionEnvironmentRecord::new(
this_function_object,
if construct || !flags.is_lexical_this_mode() {
Some(this)
} else {
None
},
Some(local_env),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if flags.is_lexical_this_mode() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
Value::undefined(),
);
context.push_environment(second_env);
}

FunctionBody::Ordinary(body.clone())
}
}
} else {
return context.throw_type_error("function object is not callable");
}
} else {
return context.throw_type_error("not a function");
};

match f_body {
FunctionBody::BuiltInFunction(func) => func(this, args, context),
FunctionBody::BuiltInConstructor(func) => func(&Value::undefined(), args, context),
match body {
FunctionBody::BuiltInConstructor(function) if construct => {
function(&this_target, args, context)
}
FunctionBody::BuiltInFunction(_) if construct => {
unreachable!("Cannot have a function in construct")
}
FunctionBody::BuiltInConstructor(function) => {
function(&Value::undefined(), args, context)
}
FunctionBody::BuiltInFunction(function) => function(this_target, args, context),
FunctionBody::Ordinary(body) => {
let result = body.run(context);
let this = context.get_this_binding();

if has_parameter_expressions {
context.pop_environment();
}
context.pop_environment();

result
if construct {
this
} else {
result
}
}
}
}

/// Call this object.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
#[track_caller]
#[inline]
pub fn call(&self, this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
self.call_construct(this, args, context, false)
}

/// Construct an instance of this object with the specified arguments.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[track_caller]
#[inline]
pub fn construct(
&self,
args: &[Value],
new_target: Value,
new_target: &Value,
context: &mut Context,
) -> Result<Value> {
let this_function_object = self.clone();
let body = if let Some(function) = self.borrow().as_function() {
if function.is_constructable() {
match function {
Function::BuiltIn(BuiltInFunction(function), _) => {
FunctionBody::BuiltInConstructor(*function)
}
Function::Ordinary {
body,
params,
environment,
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)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object,
Some(this),
Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if flags.is_lexical_this_mode() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
new_target.clone(),
);

// Add argument bindings to the function environment
for (i, param) in params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
function.add_rest_param(param, i, args, context, &local_env);
break;
}

let value = args.get(i).cloned().unwrap_or_else(Value::undefined);
function
.add_arguments_to_environment(param, value, &local_env, context);
}

// Add arguments object
let arguments_obj = create_unmapped_arguments_object(args);
local_env.borrow_mut().create_mutable_binding(
"arguments".to_string(),
false,
true,
context,
)?;
local_env.borrow_mut().initialize_binding(
"arguments",
arguments_obj,
context,
)?;
context.push_environment(local_env);

FunctionBody::Ordinary(body.clone())
}
}
} else {
let name = self
.get(&"name".into(), self.clone().into(), context)?
.display()
.to_string();
return context.throw_type_error(format!("{} is not a constructor", name));
}
} else {
return context.throw_type_error("not a function");
};

match body {
FunctionBody::BuiltInConstructor(function) => function(&new_target, args, context),
FunctionBody::Ordinary(body) => {
let _ = body.run(context);

// local_env gets dropped here, its no longer needed
let result = context.get_this_binding();
context.pop_environment();
result
}
FunctionBody::BuiltInFunction(_) => unreachable!("Cannot have a function in construct"),
}
self.call_construct(new_target, args, context, true)
}

/// Converts an object to a primitive.
Expand Down
2 changes: 1 addition & 1 deletion boa/src/syntax/ast/node/new/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl Executable for New {
}

match func_object {
Value::Object(ref object) => object.construct(&v_args, object.clone().into(), context),
Value::Object(ref object) => object.construct(&v_args, &object.clone().into(), context),
_ => context
.throw_type_error(format!("{} is not a constructor", self.expr().to_string(),)),
}
Expand Down

0 comments on commit e02d2f7

Please sign in to comment.