From 3acef7b7d541eb260de978252a59cbff8c6bd44c Mon Sep 17 00:00:00 2001 From: 0x7D2B <72297086+0x7D2B@users.noreply.github.com> Date: Thu, 27 May 2021 09:14:27 +0000 Subject: [PATCH] Add default parameter support (#1273) * Add default parameter support * Update second environment * Use map instead of if let, remove empty line * Inline call and construct methods Co-authored-by: Iban Eguia * Handle cases where arguments object is not needed * Reword arguments object comment Co-authored-by: Iban Eguia --- boa/src/builtins/reflect/mod.rs | 2 +- boa/src/object/gcobject.rs | 294 ++++++++++-------- boa/src/syntax/ast/node/new/mod.rs | 2 +- boa/src/syntax/ast/node/statement_list/mod.rs | 10 + 4 files changed, 170 insertions(+), 138 deletions(-) diff --git a/boa/src/builtins/reflect/mod.rs b/boa/src/builtins/reflect/mod.rs index d7caf02488..a1cbcb8fdb 100644 --- a/boa/src/builtins/reflect/mod.rs +++ b/boa/src/builtins/reflect/mod.rs @@ -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. diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 9973fa030b..e7c194caaa 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -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. - // - // + /// + /// + /// + /// + /// #[track_caller] - pub fn call(&self, this: &Value, args: &[Value], context: &mut Context) -> Result { + fn call_construct( + &self, + this_target: &Value, + args: &[Value], + context: &mut Context, + construct: bool, + ) -> Result { 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) @@ -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 + // see + 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) // 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 @@ -150,6 +193,44 @@ impl GcObject { Value::undefined(), ); + let mut arguments_in_parameter_names = false; + + for param in params.iter() { + has_parameter_expressions = + has_parameter_expressions || param.init().is_some(); + arguments_in_parameter_names = + arguments_in_parameter_names || param.name() == "arguments"; + } + + // An arguments object is added when all of the following conditions are met + // - If not in an arrow function (10.2.11.16) + // - If the parameter list does not contain `arguments` (10.2.11.17) + // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) + // + // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation + if !flags.is_lexical_this_mode() + && !arguments_in_parameter_names + && (has_parameter_expressions + || (!body.lexically_declared_names().contains("arguments") + && !body.function_declared_names().contains("arguments"))) + { + // 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, + )?; + } + // 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 @@ -158,49 +239,90 @@ impl GcObject { break; } - let value = args.get(i).cloned().unwrap_or_else(Value::undefined); + 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); } - // 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); + 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::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. + // + // + #[track_caller] + #[inline] + pub fn call(&self, this: &Value, args: &[Value], context: &mut Context) -> Result { + self.call_construct(this, args, context, false) + } + /// Construct an instance of this object with the specified arguments. /// /// # Panics @@ -208,114 +330,14 @@ impl GcObject { /// Panics if the object is currently mutably borrowed. // #[track_caller] + #[inline] pub fn construct( &self, args: &[Value], - new_target: Value, + new_target: &Value, context: &mut Context, ) -> Result { - 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 - // see - 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) - // - 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. diff --git a/boa/src/syntax/ast/node/new/mod.rs b/boa/src/syntax/ast/node/new/mod.rs index 85be684e41..37a0100551 100644 --- a/boa/src/syntax/ast/node/new/mod.rs +++ b/boa/src/syntax/ast/node/new/mod.rs @@ -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(),)), } diff --git a/boa/src/syntax/ast/node/statement_list/mod.rs b/boa/src/syntax/ast/node/statement_list/mod.rs index 89c5e36101..871c29e316 100644 --- a/boa/src/syntax/ast/node/statement_list/mod.rs +++ b/boa/src/syntax/ast/node/statement_list/mod.rs @@ -72,6 +72,16 @@ impl StatementList { set } + pub fn function_declared_names(&self) -> HashSet<&str> { + let mut set = HashSet::new(); + for stmt in self.items() { + if let Node::FunctionDecl(decl) = stmt { + set.insert(decl.name()); + } + } + set + } + pub fn var_declared_names(&self) -> HashSet<&str> { let mut set = HashSet::new(); for stmt in self.items() {