1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 09:37:44 +00:00

LibJS: Function.length respects default and rest parameters

"[Function.length is] the number of formal parameters. This number
excludes the rest parameter and only includes parameters before
the first one with a default value." - MDN
This commit is contained in:
Matthew Olsson 2020-05-05 20:02:14 -07:00 committed by Andreas Kling
parent 2c14714ee0
commit 838390171c
6 changed files with 43 additions and 15 deletions

View file

@ -70,14 +70,14 @@ Value ScopeNode::execute(Interpreter& interpreter) const
Value FunctionDeclaration::execute(Interpreter& interpreter) const Value FunctionDeclaration::execute(Interpreter& interpreter) const
{ {
auto* function = ScriptFunction::create(interpreter.global_object(), name(), body(), parameters(), interpreter.current_environment()); auto* function = ScriptFunction::create(interpreter.global_object(), name(), body(), parameters(), function_length(), interpreter.current_environment());
interpreter.set_variable(name(), function); interpreter.set_variable(name(), function);
return js_undefined(); return js_undefined();
} }
Value FunctionExpression::execute(Interpreter& interpreter) const Value FunctionExpression::execute(Interpreter& interpreter) const
{ {
return ScriptFunction::create(interpreter.global_object(), name(), body(), parameters(), interpreter.current_environment()); return ScriptFunction::create(interpreter.global_object(), name(), body(), parameters(), function_length(), interpreter.current_environment());
} }
Value ExpressionStatement::execute(Interpreter& interpreter) const Value ExpressionStatement::execute(Interpreter& interpreter) const

View file

@ -166,23 +166,26 @@ public:
const Vector<Parameter>& parameters() const { return m_parameters; }; const Vector<Parameter>& parameters() const { return m_parameters; };
protected: protected:
FunctionNode(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, NonnullRefPtrVector<VariableDeclaration> variables) FunctionNode(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables)
: m_name(name) : m_name(name)
, m_body(move(body)) , m_body(move(body))
, m_parameters(move(parameters)) , m_parameters(move(parameters))
, m_variables(move(variables)) , m_variables(move(variables))
, m_function_length(function_length)
{ {
} }
void dump(int indent, const char* class_name) const; void dump(int indent, const char* class_name) const;
const NonnullRefPtrVector<VariableDeclaration>& variables() const { return m_variables; } const NonnullRefPtrVector<VariableDeclaration>& variables() const { return m_variables; }
i32 function_length() const { return m_function_length; }
private: private:
FlyString m_name; FlyString m_name;
NonnullRefPtr<Statement> m_body; NonnullRefPtr<Statement> m_body;
const Vector<Parameter> m_parameters; const Vector<Parameter> m_parameters;
NonnullRefPtrVector<VariableDeclaration> m_variables; NonnullRefPtrVector<VariableDeclaration> m_variables;
const i32 m_function_length;
}; };
class FunctionDeclaration final class FunctionDeclaration final
@ -191,8 +194,8 @@ class FunctionDeclaration final
public: public:
static bool must_have_name() { return true; } static bool must_have_name() { return true; }
FunctionDeclaration(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, NonnullRefPtrVector<VariableDeclaration> variables) FunctionDeclaration(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables)
: FunctionNode(name, move(body), move(parameters), move(variables)) : FunctionNode(name, move(body), move(parameters), function_length, move(variables))
{ {
} }
@ -208,8 +211,8 @@ class FunctionExpression final : public Expression
public: public:
static bool must_have_name() { return false; } static bool must_have_name() { return false; }
FunctionExpression(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, NonnullRefPtrVector<VariableDeclaration> variables) FunctionExpression(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables)
: FunctionNode(name, move(body), move(parameters), move(variables)) : FunctionNode(name, move(body), move(parameters), function_length, move(variables))
{ {
} }

View file

@ -285,6 +285,7 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
Vector<FunctionNode::Parameter> parameters; Vector<FunctionNode::Parameter> parameters;
bool parse_failed = false; bool parse_failed = false;
bool has_rest_parameter = false; bool has_rest_parameter = false;
i32 function_length = -1;
while (true) { while (true) {
if (match(TokenType::Comma)) { if (match(TokenType::Comma)) {
if (has_rest_parameter) { if (has_rest_parameter) {
@ -297,6 +298,7 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
RefPtr<Expression> default_value; RefPtr<Expression> default_value;
if (expect_parens && match(TokenType::Equals)) { if (expect_parens && match(TokenType::Equals)) {
consume(TokenType::Equals); consume(TokenType::Equals);
function_length = parameters.size();
default_value = parse_expression(0); default_value = parse_expression(0);
} }
parameters.append({ parameter_name, default_value }); parameters.append({ parameter_name, default_value });
@ -307,6 +309,7 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
break; break;
} }
has_rest_parameter = true; has_rest_parameter = true;
function_length = parameters.size();
auto parameter_name = consume(TokenType::Identifier).value(); auto parameter_name = consume(TokenType::Identifier).value();
parameters.append({ parameter_name, nullptr, true }); parameters.append({ parameter_name, nullptr, true });
} else if (match(TokenType::ParenClose)) { } else if (match(TokenType::ParenClose)) {
@ -337,6 +340,9 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
if (parse_failed) if (parse_failed)
return nullptr; return nullptr;
if (function_length == -1)
function_length = parameters.size();
auto function_body_result = [this]() -> RefPtr<BlockStatement> { auto function_body_result = [this]() -> RefPtr<BlockStatement> {
if (match(TokenType::CurlyOpen)) { if (match(TokenType::CurlyOpen)) {
// Parse a function body with statements // Parse a function body with statements
@ -361,7 +367,7 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
if (!function_body_result.is_null()) { if (!function_body_result.is_null()) {
state_rollback_guard.disarm(); state_rollback_guard.disarm();
auto body = function_body_result.release_nonnull(); auto body = function_body_result.release_nonnull();
return create_ast_node<FunctionExpression>("", move(body), move(parameters), m_parser_state.m_var_scopes.take_last()); return create_ast_node<FunctionExpression>("", move(body), move(parameters), function_length, m_parser_state.m_var_scopes.take_last());
} }
return nullptr; return nullptr;
@ -863,10 +869,12 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool needs_function_
} }
consume(TokenType::ParenOpen); consume(TokenType::ParenOpen);
Vector<FunctionNode::Parameter> parameters; Vector<FunctionNode::Parameter> parameters;
i32 function_length = -1;
while (match(TokenType::Identifier) || match(TokenType::TripleDot)) { while (match(TokenType::Identifier) || match(TokenType::TripleDot)) {
if (match(TokenType::TripleDot)) { if (match(TokenType::TripleDot)) {
consume(); consume();
auto parameter_name = consume(TokenType::Identifier).value(); auto parameter_name = consume(TokenType::Identifier).value();
function_length = parameters.size();
parameters.append({ parameter_name, nullptr, true }); parameters.append({ parameter_name, nullptr, true });
break; break;
} }
@ -874,6 +882,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool needs_function_
RefPtr<Expression> default_value; RefPtr<Expression> default_value;
if (match(TokenType::Equals)) { if (match(TokenType::Equals)) {
consume(TokenType::Equals); consume(TokenType::Equals);
function_length = parameters.size();
default_value = parse_expression(0); default_value = parse_expression(0);
} }
parameters.append({ parameter_name, default_value }); parameters.append({ parameter_name, default_value });
@ -882,9 +891,13 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool needs_function_
consume(TokenType::Comma); consume(TokenType::Comma);
} }
consume(TokenType::ParenClose); consume(TokenType::ParenClose);
if (function_length == -1)
function_length = parameters.size();
auto body = parse_block_statement(); auto body = parse_block_statement();
body->add_variables(m_parser_state.m_var_scopes.last()); body->add_variables(m_parser_state.m_var_scopes.last());
return create_ast_node<FunctionNodeType>(name, move(body), move(parameters), NonnullRefPtrVector<VariableDeclaration>()); return create_ast_node<FunctionNodeType>(name, move(body), move(parameters), function_length, NonnullRefPtrVector<VariableDeclaration>());
} }
NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration() NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration()

View file

@ -47,17 +47,18 @@ static ScriptFunction* script_function_from(Interpreter& interpreter)
return static_cast<ScriptFunction*>(this_object); return static_cast<ScriptFunction*>(this_object);
} }
ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, LexicalEnvironment* parent_environment) ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, LexicalEnvironment* parent_environment)
{ {
return global_object.heap().allocate<ScriptFunction>(name, body, move(parameters), parent_environment, *global_object.function_prototype()); return global_object.heap().allocate<ScriptFunction>(name, body, move(parameters), m_function_length, parent_environment, *global_object.function_prototype());
} }
ScriptFunction::ScriptFunction(const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, LexicalEnvironment* parent_environment, Object& prototype) ScriptFunction::ScriptFunction(const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, LexicalEnvironment* parent_environment, Object& prototype)
: Function(prototype) : Function(prototype)
, m_name(name) , m_name(name)
, m_body(body) , m_body(body)
, m_parameters(move(parameters)) , m_parameters(move(parameters))
, m_parent_environment(parent_environment) , m_parent_environment(parent_environment)
, m_function_length(m_function_length)
{ {
put("prototype", Object::create_empty(interpreter(), interpreter().global_object()), 0); put("prototype", Object::create_empty(interpreter(), interpreter().global_object()), 0);
put_native_property("length", length_getter, nullptr, Attribute::Configurable); put_native_property("length", length_getter, nullptr, Attribute::Configurable);
@ -130,7 +131,7 @@ Value ScriptFunction::length_getter(Interpreter& interpreter)
auto* function = script_function_from(interpreter); auto* function = script_function_from(interpreter);
if (!function) if (!function)
return {}; return {};
return Value(static_cast<i32>(function->parameters().size())); return Value(static_cast<i32>(function->m_function_length));
} }
Value ScriptFunction::name_getter(Interpreter& interpreter) Value ScriptFunction::name_getter(Interpreter& interpreter)

View file

@ -33,9 +33,9 @@ namespace JS {
class ScriptFunction final : public Function { class ScriptFunction final : public Function {
public: public:
static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, LexicalEnvironment* parent_environment); static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, LexicalEnvironment* parent_environment);
ScriptFunction(const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, LexicalEnvironment* parent_environment, Object& prototype); ScriptFunction(const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, LexicalEnvironment* parent_environment, Object& prototype);
virtual ~ScriptFunction(); virtual ~ScriptFunction();
const Statement& body() const { return m_body; } const Statement& body() const { return m_body; }
@ -60,6 +60,7 @@ private:
NonnullRefPtr<Statement> m_body; NonnullRefPtr<Statement> m_body;
const Vector<FunctionNode::Parameter> m_parameters; const Vector<FunctionNode::Parameter> m_parameters;
LexicalEnvironment* m_parent_environment { nullptr }; LexicalEnvironment* m_parent_environment { nullptr };
i32 m_function_length;
}; };
} }

View file

@ -11,6 +11,16 @@ try {
assert((bar.length = 5) === 5); assert((bar.length = 5) === 5);
assert(bar.length === 3); assert(bar.length === 3);
function baz(a, b = 1, c) {}
assert(baz.length === 1);
assert((baz.length = 5) === 5);
assert(baz.length === 1);
function qux(a, b, ...c) {}
assert(qux.length === 2);
assert((qux.length = 5) === 5);
assert(qux.length === 2);
console.log("PASS"); console.log("PASS");
} catch (e) { } catch (e) {
console.log("FAIL: " + e); console.log("FAIL: " + e);