Commit 37520d3e authored by wingo's avatar wingo Committed by Commit bot

Revert "Factor formal argument parsing into ParserBase"

Revert https://codereview.chromium.org/1078093002/ and follow-on parser
patches due to a perf regression.

This reverts commit 53ddccfc.
This reverts commit 71d3213a.
This reverts commit 0f432ebb.
This reverts commit 1dbc4327.

R=marja@chromium.org

Review URL: https://codereview.chromium.org/1094653002

Cr-Commit-Position: refs/heads/master@{#27912}
parent c153a843
......@@ -195,11 +195,7 @@ var kMessages = {
array_not_subclassable: ["Subclassing Arrays is not currently supported."],
for_in_loop_initializer: ["for-in loop variable declaration may not have an initializer."],
for_of_loop_initializer: ["for-of loop variable declaration may not have an initializer."],
for_inof_loop_multi_bindings: ["Invalid left-hand side in ", "%0", " loop: Must have a single binding."],
bad_getter_arity: ["Getter must not have any formal parameters."],
bad_setter_arity: ["Setter must have exactly one formal parameter."],
this_formal_parameter: ["'this' is not a valid formal parameter name"],
duplicate_arrow_function_formal_parameter: ["Arrow function may not have duplicate parameter names"]
for_inof_loop_multi_bindings: ["Invalid left-hand side in ", "%0", " loop: Must have a single binding."]
};
......
......@@ -787,7 +787,16 @@ Expression* ParserTraits::ExpressionFromIdentifier(const AstRawString* name,
Scope* scope,
AstNodeFactory* factory) {
if (parser_->fni_ != NULL) parser_->fni_->PushVariableName(name);
return scope->NewUnresolved(factory, name, start_position, end_position);
// Arrow function parameters are parsed as an expression. When
// parsing lazily, it is enough to create a VariableProxy in order
// for Traits::DeclareArrowParametersFromExpression() to be able to
// pick the names of the parameters.
return parser_->parsing_lazy_arrow_parameters_
? factory->NewVariableProxy(name, Variable::NORMAL, start_position,
end_position)
: scope->NewUnresolved(factory, name, start_position,
end_position);
}
......@@ -852,6 +861,7 @@ Parser::Parser(ParseInfo* info)
target_stack_(NULL),
compile_options_(info->compile_options()),
cached_parse_data_(NULL),
parsing_lazy_arrow_parameters_(false),
total_preparse_skipped_(0),
pre_parse_timer_(NULL),
parsing_on_main_thread_(true) {
......@@ -1132,40 +1142,27 @@ FunctionLiteral* Parser::ParseLazy(Isolate* isolate, ParseInfo* info,
bool ok = true;
if (shared_info->is_arrow()) {
FormalParameterErrorLocations error_locs;
bool has_rest = false;
ZoneList<const AstRawString*>* params;
if (Check(Token::LPAREN)) {
// '(' StrictFormalParameters ')'
params = ParseFormalParameterList(&error_locs, &has_rest, &ok);
if (ok) ok = Check(Token::RPAREN);
} else {
// BindingIdentifier
params = NewFormalParameterList(1, zone());
DuplicateFinder* null_duplicate_finder = nullptr;
const AstRawString* single_param =
ParseFormalParameter(null_duplicate_finder, &error_locs, &ok);
if (ok) params->Add(single_param, zone());
}
// The first expression being parsed is the parameter list of the arrow
// function. Setting this avoids prevents ExpressionFromIdentifier()
// from creating unresolved variables in already-resolved scopes.
parsing_lazy_arrow_parameters_ = true;
Expression* expression = ParseExpression(false, &ok);
if (ok) {
Expression* expression = ParseArrowFunctionLiteral(
shared_info->start_position(), params, error_locs, has_rest, &ok);
if (ok) {
// Scanning must end at the same position that was recorded
// previously. If not, parsing has been interrupted due to a stack
// overflow, at which point the partially parsed arrow function
// concise body happens to be a valid expression. This is a problem
// only for arrow functions with single expression bodies, since there
// is no end token such as "}" for normal functions.
if (scanner()->location().end_pos == shared_info->end_position()) {
// The pre-parser saw an arrow function here, so the full parser
// must produce a FunctionLiteral.
DCHECK(expression->IsFunctionLiteral());
result = expression->AsFunctionLiteral();
} else {
ok = false;
}
// Scanning must end at the same position that was recorded
// previously. If not, parsing has been interrupted due to a
// stack overflow, at which point the partially parsed arrow
// function concise body happens to be a valid expression. This
// is a problem only for arrow functions with single statement
// bodies, since there is no end token such as "}" for normal
// functions.
if (scanner()->location().end_pos == shared_info->end_position()) {
// The pre-parser saw an arrow function here, so the full parser
// must produce a FunctionLiteral.
DCHECK(expression->IsFunctionLiteral());
result = expression->AsFunctionLiteral();
} else {
result = NULL;
ok = false;
}
}
} else if (shared_info->is_default_constructor()) {
......@@ -3737,141 +3734,77 @@ Handle<FixedArray> CompileTimeValue::GetElements(Handle<FixedArray> value) {
}
void ParserTraits::RecordArrowFunctionParameter(
ZoneList<const AstRawString*>* params, VariableProxy* proxy,
FormalParameterErrorLocations* error_locs, bool* ok) {
const AstRawString* raw_name = proxy->raw_name();
Scanner::Location param_location(proxy->position(),
proxy->position() + raw_name->length());
if (proxy->is_this()) {
ReportMessageAt(param_location, "this_formal_parameter");
*ok = false;
return;
}
if (!error_locs->eval_or_arguments.IsValid() && IsEvalOrArguments(raw_name))
error_locs->eval_or_arguments = param_location;
if (!error_locs->reserved.IsValid() && IsFutureStrictReserved(raw_name))
error_locs->reserved = param_location;
if (!error_locs->undefined.IsValid() && IsUndefined(raw_name))
error_locs->undefined = param_location;
// TODO(wingo): Fix quadratic check. (Scope::IsDeclaredParameter has the same
// issue.)
for (int i = 0; i < params->length(); i++) {
// Eagerly report the error here; duplicate formal parameter names are never
// allowed in arrow functions.
if (raw_name == params->at(i)) {
ReportMessageAt(param_location,
"duplicate_arrow_function_formal_parameter");
*ok = false;
return;
}
}
// When the formal parameter was originally seen, it was parsed as a
// VariableProxy and recorded as unresolved in the scope. Here we undo that
// parse-time side-effect.
parser_->scope_->RemoveUnresolved(proxy);
params->Add(raw_name, parser_->zone());
}
// Arrow function parameter lists are parsed as StrictFormalParameters, which
// means that they cannot have duplicates. Note that this is a subset of the
// restrictions placed on parameters to functions whose body is strict.
ZoneList<const AstRawString*>*
ParserTraits::ParseArrowFunctionFormalParameterList(
Expression* params, const Scanner::Location& params_loc,
FormalParameterErrorLocations* error_locs, bool* is_rest, bool* ok) {
ZoneList<const AstRawString*>* result =
NewFormalParameterList(4, parser_->zone());
DCHECK_NOT_NULL(params);
bool CheckAndDeclareArrowParameter(ParserTraits* traits, Expression* expression,
Scope* scope, int* num_params,
Scanner::Location* undefined_loc,
Scanner::Location* dupe_loc) {
// Case for empty parameter lists:
// () => ...
if (expression == NULL) return true;
// Too many parentheses around expression:
// (( ... )) => ...
if (params->is_multi_parenthesized()) {
// TODO(wingo): Make a better message.
ReportMessageAt(params_loc, "malformed_arrow_function_parameter_list");
*ok = false;
return NULL;
}
if (expression->is_multi_parenthesized()) return false;
// ArrowFunctionFormals ::
// VariableProxy
// Binary(Token::COMMA, ArrowFunctionFormals, VariableProxy)
//
// To stay iterative we'll process arguments in right-to-left order, then
// reverse the list in place.
//
// Sadly, for the various malformed_arrow_function_parameter_list errors, we
// can't be more specific on the error message or on the location because we
// need to match the pre-parser's behavior.
while (params->IsBinaryOperation()) {
BinaryOperation* binop = params->AsBinaryOperation();
Expression* left = binop->left();
Expression* right = binop->right();
if (binop->op() != Token::COMMA) {
ReportMessageAt(params_loc, "malformed_arrow_function_parameter_list");
*ok = false;
return NULL;
}
// RHS of comma expression should be an unparenthesized variable proxy.
if (right->is_parenthesized() || !right->IsVariableProxy()) {
ReportMessageAt(params_loc, "malformed_arrow_function_parameter_list");
*ok = false;
return NULL;
// Case for a single parameter:
// (foo) => ...
// foo => ...
if (expression->IsVariableProxy()) {
if (expression->AsVariableProxy()->is_this()) return false;
const AstRawString* raw_name = expression->AsVariableProxy()->raw_name();
if (traits->IsEvalOrArguments(raw_name) ||
traits->IsFutureStrictReserved(raw_name))
return false;
if (traits->IsUndefined(raw_name) && !undefined_loc->IsValid()) {
*undefined_loc = Scanner::Location(
expression->position(), expression->position() + raw_name->length());
}
RecordArrowFunctionParameter(result, right->AsVariableProxy(), error_locs,
CHECK_OK);
// LHS of comma expression should be unparenthesized.
params = left;
if (params->is_parenthesized()) {
ReportMessageAt(params_loc, "malformed_arrow_function_parameter_list");
*ok = false;
return NULL;
if (scope->IsDeclared(raw_name)) {
*dupe_loc = Scanner::Location(
expression->position(), expression->position() + raw_name->length());
return false;
}
if (result->length() > Code::kMaxArguments) {
ReportMessageAt(params_loc, "malformed_arrow_function_parameter_list");
*ok = false;
return NULL;
}
}
// When the variable was seen, it was recorded as unresolved in the outer
// scope. But it's really not unresolved.
scope->outer_scope()->RemoveUnresolved(expression->AsVariableProxy());
if (params->IsVariableProxy()) {
RecordArrowFunctionParameter(result, params->AsVariableProxy(), error_locs,
CHECK_OK);
} else {
ReportMessageAt(params_loc, "malformed_arrow_function_parameter_list");
*ok = false;
return NULL;
scope->DeclareParameter(raw_name, VAR);
++(*num_params);
return true;
}
// Reverse in place.
std::reverse(result->begin(), result->end());
// Case for more than one parameter:
// (foo, bar [, ...]) => ...
if (expression->IsBinaryOperation()) {
BinaryOperation* binop = expression->AsBinaryOperation();
if (binop->op() != Token::COMMA || binop->left()->is_parenthesized() ||
binop->right()->is_parenthesized())
return false;
return result;
return CheckAndDeclareArrowParameter(traits, binop->left(), scope,
num_params, undefined_loc, dupe_loc) &&
CheckAndDeclareArrowParameter(traits, binop->right(), scope,
num_params, undefined_loc, dupe_loc);
}
// Any other kind of expression is not a valid parameter list.
return false;
}
int ParserTraits::DeclareFormalParameters(ZoneList<const AstRawString*>* params,
Scope* scope, bool has_rest) {
for (int i = 0; i < params->length(); i++) {
const AstRawString* param_name = params->at(i);
int is_rest = has_rest && i == params->length() - 1;
Variable* var = scope->DeclareParameter(param_name, VAR, is_rest);
if (is_sloppy(scope->language_mode())) {
// TODO(sigurds) Mark every parameter as maybe assigned. This is a
// conservative approximation necessary to account for parameters
// that are assigned via the arguments array.
var->set_maybe_assigned();
}
}
return params->length();
int ParserTraits::DeclareArrowParametersFromExpression(
Expression* expression, Scope* scope, Scanner::Location* undefined_loc,
Scanner::Location* dupe_loc, bool* ok) {
int num_params = 0;
// Always reset the flag: It only needs to be set for the first expression
// parsed as arrow function parameter list, because only top-level functions
// are parsed lazily.
parser_->parsing_lazy_arrow_parameters_ = false;
*ok = CheckAndDeclareArrowParameter(this, expression, scope, &num_params,
undefined_loc, dupe_loc);
return num_params;
}
......@@ -3970,25 +3903,77 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
function_state.set_generator_object_variable(temp);
}
FormalParameterErrorLocations error_locs;
bool has_rest = false;
// FormalParameterList ::
// '(' (Identifier)*[','] ')'
Expect(Token::LPAREN, CHECK_OK);
int start_position = scanner()->location().beg_pos;
ZoneList<const AstRawString*>* params =
ParseFormalParameterList(&error_locs, &has_rest, CHECK_OK);
Expect(Token::RPAREN, CHECK_OK);
int formals_end_position = scanner()->location().end_pos;
scope->set_start_position(scanner()->location().beg_pos);
// We don't yet know if the function will be strict, so we cannot yet
// produce errors for parameter names or duplicates. However, we remember
// the locations of these errors if they occur and produce the errors later.
Scanner::Location eval_args_loc = Scanner::Location::invalid();
Scanner::Location dupe_loc = Scanner::Location::invalid();
Scanner::Location reserved_loc = Scanner::Location::invalid();
// Similarly for strong mode.
Scanner::Location undefined_loc = Scanner::Location::invalid();
bool is_rest = false;
bool done = arity_restriction == FunctionLiteral::GETTER_ARITY ||
(peek() == Token::RPAREN &&
arity_restriction != FunctionLiteral::SETTER_ARITY);
while (!done) {
bool is_strict_reserved = false;
is_rest = peek() == Token::ELLIPSIS && allow_harmony_rest_params();
if (is_rest) {
Consume(Token::ELLIPSIS);
}
CheckArityRestrictions(params->length(), arity_restriction, start_position,
formals_end_position, CHECK_OK);
const AstRawString* param_name =
ParseIdentifierOrStrictReservedWord(&is_strict_reserved, CHECK_OK);
scope->set_start_position(start_position);
// Store locations for possible future error reports.
if (!eval_args_loc.IsValid() && IsEvalOrArguments(param_name)) {
eval_args_loc = scanner()->location();
}
if (!undefined_loc.IsValid() && IsUndefined(param_name)) {
undefined_loc = scanner()->location();
}
if (!reserved_loc.IsValid() && is_strict_reserved) {
reserved_loc = scanner()->location();
}
if (!dupe_loc.IsValid() &&
scope_->IsDeclaredParameter(param_name)) {
duplicate_parameters = FunctionLiteral::kHasDuplicateParameters;
dupe_loc = scanner()->location();
}
num_parameters = DeclareFormalParameters(params, scope_, has_rest);
if (error_locs.duplicate.IsValid()) {
duplicate_parameters = FunctionLiteral::kHasDuplicateParameters;
}
Variable* var = scope_->DeclareParameter(param_name, VAR, is_rest);
if (is_sloppy(scope->language_mode())) {
// TODO(sigurds) Mark every parameter as maybe assigned. This is a
// conservative approximation necessary to account for parameters
// that are assigned via the arguments array.
var->set_maybe_assigned();
}
num_parameters++;
if (num_parameters > Code::kMaxArguments) {
ReportMessage("too_many_parameters");
*ok = false;
return NULL;
}
if (arity_restriction == FunctionLiteral::SETTER_ARITY) break;
done = (peek() == Token::RPAREN);
if (!done) {
if (is_rest) {
ReportMessageAt(scanner()->peek_location(), "param_after_rest");
*ok = false;
return NULL;
}
Expect(Token::COMMA, CHECK_OK);
}
}
Expect(Token::RPAREN, CHECK_OK);
Expect(Token::LBRACE, CHECK_OK);
......@@ -4070,9 +4055,10 @@ FunctionLiteral* Parser::ParseFunctionLiteral(
CheckFunctionName(language_mode(), kind, function_name,
name_is_strict_reserved, function_name_location,
CHECK_OK);
const bool use_strict_params = has_rest || IsConciseMethod(kind);
CheckFunctionParameterNames(language_mode(), use_strict_params, error_locs,
CHECK_OK);
const bool use_strict_params = is_rest || IsConciseMethod(kind);
CheckFunctionParameterNames(language_mode(), use_strict_params,
eval_args_loc, undefined_loc, dupe_loc,
reserved_loc, CHECK_OK);
if (is_strict(language_mode())) {
CheckStrictOctalLiteral(scope->start_position(), scope->end_position(),
......
......@@ -557,8 +557,6 @@ class ParserTraits {
typedef ObjectLiteral::Property* ObjectLiteralProperty;
typedef ZoneList<v8::internal::Expression*>* ExpressionList;
typedef ZoneList<ObjectLiteral::Property*>* PropertyList;
typedef const v8::internal::AstRawString* FormalParameter;
typedef ZoneList<const v8::internal::AstRawString*>* FormalParameterList;
typedef ZoneList<v8::internal::Statement*>* StatementList;
// For constructing objects returned by the traversing functions.
......@@ -696,6 +694,7 @@ class ParserTraits {
static Expression* EmptyExpression() {
return NULL;
}
static Expression* EmptyArrowParamList() { return NULL; }
static Literal* EmptyLiteral() {
return NULL;
}
......@@ -706,10 +705,6 @@ class ParserTraits {
static ZoneList<Expression*>* NullExpressionList() {
return NULL;
}
static const AstRawString* EmptyFormalParameter() { return NULL; }
static ZoneList<const AstRawString*>* NullFormalParameterList() {
return NULL;
}
// Non-NULL empty string.
V8_INLINE const AstRawString* EmptyIdentifierString();
......@@ -745,20 +740,14 @@ class ParserTraits {
ZoneList<v8::internal::Statement*>* NewStatementList(int size, Zone* zone) {
return new(zone) ZoneList<v8::internal::Statement*>(size, zone);
}
ZoneList<const v8::internal::AstRawString*>* NewFormalParameterList(
int size, Zone* zone) {
return new (zone) ZoneList<const v8::internal::AstRawString*>(size, zone);
}
V8_INLINE Scope* NewScope(Scope* parent_scope, ScopeType scope_type,
FunctionKind kind = kNormalFunction);
void RecordArrowFunctionParameter(ZoneList<const AstRawString*>* params,
VariableProxy* proxy,
FormalParameterErrorLocations* error_locs,
bool* ok);
ZoneList<const AstRawString*>* ParseArrowFunctionFormalParameterList(
Expression* params, const Scanner::Location& params_loc,
FormalParameterErrorLocations* error_locs, bool* is_rest, bool* ok);
// Utility functions
int DeclareArrowParametersFromExpression(Expression* expression, Scope* scope,
Scanner::Location* undefined_loc,
Scanner::Location* dupe_loc,
bool* ok);
// Temporary glue; these functions will move to ParserBase.
Expression* ParseV8Intrinsic(bool* ok);
......@@ -779,9 +768,6 @@ class ParserTraits {
bool name_is_strict_reserved, int pos,
bool* ok);
int DeclareFormalParameters(ZoneList<const AstRawString*>* params,
Scope* scope, bool has_rest);
V8_INLINE void CheckConflictingVarDeclarations(v8::internal::Scope* scope,
bool* ok);
......@@ -1059,6 +1045,8 @@ class Parser : public ParserBase<ParserTraits> {
ScriptCompiler::CompileOptions compile_options_;
ParseData* cached_parse_data_;
bool parsing_lazy_arrow_parameters_; // for lazily parsed arrow functions.
PendingCompilationErrorHandler pending_error_handler_;
// Other information which will be stored in Parser and moved to Isolate after
......
......@@ -926,19 +926,62 @@ PreParser::Expression PreParser::ParseFunctionLiteral(
PreParserFactory factory(NULL);
FunctionState function_state(&function_state_, &scope_, function_scope, kind,
&factory);
FormalParameterErrorLocations error_locs;
// FormalParameterList ::
// '(' (Identifier)*[','] ')'
Expect(Token::LPAREN, CHECK_OK);
int start_position = position();
DuplicateFinder duplicate_finder(scanner()->unicode_cache());
// We don't yet know if the function will be strict, so we cannot yet produce
// errors for parameter names or duplicates. However, we remember the
// locations of these errors if they occur and produce the errors later.
Scanner::Location eval_args_loc = Scanner::Location::invalid();
Scanner::Location dupe_loc = Scanner::Location::invalid();
Scanner::Location reserved_loc = Scanner::Location::invalid();
// Similarly for strong mode.
Scanner::Location undefined_loc = Scanner::Location::invalid();
bool is_rest = false;
Expect(Token::LPAREN, CHECK_OK);
int start_position = scanner()->location().beg_pos;
PreParserFormalParameterList params =
ParseFormalParameterList(&error_locs, &is_rest, CHECK_OK);
Expect(Token::RPAREN, CHECK_OK);
int formals_end_position = scanner()->location().end_pos;
bool done = arity_restriction == FunctionLiteral::GETTER_ARITY ||
(peek() == Token::RPAREN &&
arity_restriction != FunctionLiteral::SETTER_ARITY);
while (!done) {
bool is_strict_reserved = false;
is_rest = peek() == Token::ELLIPSIS && allow_harmony_rest_params();
if (is_rest) {
Consume(Token::ELLIPSIS);
}
Identifier param_name =
ParseIdentifierOrStrictReservedWord(&is_strict_reserved, CHECK_OK);
if (!eval_args_loc.IsValid() && param_name.IsEvalOrArguments()) {
eval_args_loc = scanner()->location();
}
if (!undefined_loc.IsValid() && param_name.IsUndefined()) {
undefined_loc = scanner()->location();
}
if (!reserved_loc.IsValid() && is_strict_reserved) {
reserved_loc = scanner()->location();
}
CheckArityRestrictions(params->length(), arity_restriction, start_position,
formals_end_position, ok);
if (!*ok) return Expression::Default();
int prev_value = scanner()->FindSymbol(&duplicate_finder, 1);
if (!dupe_loc.IsValid() && prev_value != 0) {
dupe_loc = scanner()->location();
}
if (arity_restriction == FunctionLiteral::SETTER_ARITY) break;
done = (peek() == Token::RPAREN);
if (!done) {
if (is_rest) {
ReportMessageAt(scanner()->peek_location(), "param_after_rest");
*ok = false;
return Expression::Default();
}
Expect(Token::COMMA, CHECK_OK);
}
}
Expect(Token::RPAREN, CHECK_OK);
// See Parser::ParseFunctionLiteral for more information about lazy parsing
// and lazy compilation.
......@@ -959,8 +1002,8 @@ PreParser::Expression PreParser::ParseFunctionLiteral(
CheckFunctionName(language_mode(), kind, function_name,
name_is_strict_reserved, function_name_location, CHECK_OK);
const bool use_strict_params = is_rest || IsConciseMethod(kind);
CheckFunctionParameterNames(language_mode(), use_strict_params, error_locs,
CHECK_OK);
CheckFunctionParameterNames(language_mode(), use_strict_params, eval_args_loc,
undefined_loc, dupe_loc, reserved_loc, CHECK_OK);
if (is_strict(language_mode())) {
int end_position = scanner()->location().end_pos;
......
......@@ -17,26 +17,6 @@
namespace v8 {
namespace internal {
// When parsing the formal parameters of a function, we usually don't yet know
// if the function will be strict, so we cannot yet produce errors for
// parameter names or duplicates. Instead, we remember the locations of these
// errors if they occur and produce the errors later.
class FormalParameterErrorLocations BASE_EMBEDDED {
public:
FormalParameterErrorLocations()
: eval_or_arguments(Scanner::Location::invalid()),
undefined(Scanner::Location::invalid()),
duplicate(Scanner::Location::invalid()),
reserved(Scanner::Location::invalid()) {}
Scanner::Location eval_or_arguments;
Scanner::Location undefined;
Scanner::Location duplicate;
Scanner::Location reserved;
};
// Common base class shared between parser and pre-parser. Traits encapsulate
// the differences between Parser and PreParser:
......@@ -71,8 +51,6 @@ class FormalParameterErrorLocations BASE_EMBEDDED {
// typedef Literal;
// typedef ExpressionList;
// typedef PropertyList;
// typedef FormalParameter;
// typedef FormalParameterList;
// // For constructing objects returned by the traversing functions.
// typedef Factory;
// };
......@@ -85,8 +63,6 @@ class ParserBase : public Traits {
// Shorten type names defined by Traits.
typedef typename Traits::Type::Expression ExpressionT;
typedef typename Traits::Type::Identifier IdentifierT;
typedef typename Traits::Type::FormalParameter FormalParameterT;
typedef typename Traits::Type::FormalParameterList FormalParameterListT;
typedef typename Traits::Type::FunctionLiteral FunctionLiteralT;
typedef typename Traits::Type::Literal LiteralT;
typedef typename Traits::Type::ObjectLiteralProperty ObjectLiteralPropertyT;
......@@ -519,28 +495,31 @@ class ParserBase : public Traits {
// after parsing the function, since the function can declare itself strict.
void CheckFunctionParameterNames(LanguageMode language_mode,
bool strict_params,
const FormalParameterErrorLocations& locs,
const Scanner::Location& eval_args_loc,
const Scanner::Location& undefined_loc,
const Scanner::Location& dupe_loc,
const Scanner::Location& reserved_loc,
bool* ok) {
if (is_sloppy(language_mode) && !strict_params) return;
if (is_strict(language_mode) && locs.eval_or_arguments.IsValid()) {
Traits::ReportMessageAt(locs.eval_or_arguments, "strict_eval_arguments");
if (is_strict(language_mode) && eval_args_loc.IsValid()) {
Traits::ReportMessageAt(eval_args_loc, "strict_eval_arguments");
*ok = false;
return;
}
if (is_strict(language_mode) && locs.reserved.IsValid()) {
Traits::ReportMessageAt(locs.reserved, "unexpected_strict_reserved");
if (is_strong(language_mode) && undefined_loc.IsValid()) {
Traits::ReportMessageAt(undefined_loc, "strong_undefined");
*ok = false;
return;
}
if (is_strong(language_mode) && locs.undefined.IsValid()) {
Traits::ReportMessageAt(locs.undefined, "strong_undefined");
// TODO(arv): When we add support for destructuring in setters we also need
// to check for duplicate names.
if (dupe_loc.IsValid()) {
Traits::ReportMessageAt(dupe_loc, "strict_param_dupe");
*ok = false;
return;
}
// TODO(arv): When we add support for destructuring in setters we also need
// to check for duplicate names.
if (locs.duplicate.IsValid()) {
Traits::ReportMessageAt(locs.duplicate, "strict_param_dupe");
if (reserved_loc.IsValid()) {
Traits::ReportMessageAt(reserved_loc, "unexpected_strict_reserved");
*ok = false;
return;
}
......@@ -621,22 +600,12 @@ class ParserBase : public Traits {
ExpressionT ParseMemberExpression(bool* ok);
ExpressionT ParseMemberExpressionContinuation(ExpressionT expression,
bool* ok);
ExpressionT ParseArrowFunctionLiteral(
int start_pos, FormalParameterListT params,
const FormalParameterErrorLocations& error_locs, bool has_rest, bool* ok);
ExpressionT ParseArrowFunctionLiteral(int start_pos, ExpressionT params_ast,
bool* ok);
ExpressionT ParseTemplateLiteral(ExpressionT tag, int start, bool* ok);
void AddTemplateExpression(ExpressionT);
ExpressionT ParseSuperExpression(bool is_new, bool* ok);
FormalParameterT ParseFormalParameter(DuplicateFinder* duplicate_finder,
FormalParameterErrorLocations* locs,
bool* ok);
FormalParameterListT ParseFormalParameterList(
FormalParameterErrorLocations* locs, bool* is_rest, bool* ok);
void CheckArityRestrictions(
int param_count, FunctionLiteral::ArityRestriction arity_restriction,
int formals_start_pos, int formals_end_pos, bool* ok);
// Checks if the expression is a valid reference expression (e.g., on the
// left-hand side of assignments). Although ruled out by ECMA as early errors,
// we allow calls for web compatibility and rewrite them to a runtime throw.
......@@ -846,6 +815,12 @@ class PreParserExpression {
IsValidArrowParamListField::encode(valid_arrow_param_list));
}
static PreParserExpression EmptyArrowParamList() {
// Any expression for which IsValidArrowParamList() returns true
// will work here.
return FromIdentifier(PreParserIdentifier::Default());
}
static PreParserExpression StringLiteral() {
return PreParserExpression(TypeField::encode(kStringLiteralExpression));
}
......@@ -936,27 +911,20 @@ class PreParserExpression {
return IsIdentifier() || IsProperty();
}
bool IsValidArrowParamList(FormalParameterErrorLocations* locs,
const Scanner::Location& params_loc) const {
bool IsValidArrowParamList(Scanner::Location* undefined_loc) const {
ValidArrowParam valid = ValidateArrowParams();
if (ParenthesizationField::decode(code_) == kMultiParenthesizedExpression) {
return false;
}
switch (valid) {
case kInvalidArrowParam:
return false;
case kInvalidStrongArrowParam:
locs->undefined = params_loc;
return true;
case kInvalidStrictReservedArrowParam:
locs->reserved = params_loc;
return true;
case kInvalidStrictEvalArgumentsArrowParam:
locs->eval_or_arguments = params_loc;
return true;
default:
DCHECK_EQ(valid, kValidArrowParam);
return true;
if (valid == kValidArrowParam) {
return true;
} else if (valid == kInvalidStrongArrowParam) {
// Return true for now regardless of strong mode for compatibility with
// parser.
*undefined_loc = Scanner::Location();
return true;
} else {
return false;
}
}
......@@ -1023,12 +991,8 @@ class PreParserExpression {
kNoTemplateTagExpression
};
// These validity constraints are ordered such that a value of N implies lack
// of errors M < N.
enum ValidArrowParam {
kInvalidArrowParam,
kInvalidStrictEvalArgumentsArrowParam,
kInvalidStrictReservedArrowParam,
kInvalidStrongArrowParam,
kValidArrowParam
};
......@@ -1044,16 +1008,13 @@ class PreParserExpression {
return kInvalidArrowParam;
}
PreParserIdentifier ident = AsIdentifier();
// In strict mode, eval and arguments are not valid formal parameter names.
if (ident.IsEval() || ident.IsArguments()) {
return kInvalidStrictEvalArgumentsArrowParam;
}
// In strict mode, future reserved words are not valid either, and as they
// produce different errors we allot them their own error code.
if (ident.IsFutureStrictReserved()) {
return kInvalidStrictReservedArrowParam;
// A valid identifier can be an arrow function parameter
// except for eval, arguments, yield, and reserved keywords.
if (ident.IsEval() || ident.IsArguments() ||
ident.IsFutureStrictReserved()) {
return kInvalidArrowParam;
}
// In strong mode, 'undefined' isn't a valid formal parameter name either.
// In strong mode, 'undefined' is similarly restricted.
if (ident.IsUndefined()) {
return kInvalidStrongArrowParam;
}
......@@ -1070,7 +1031,7 @@ class PreParserExpression {
ExpressionTypeField;
typedef BitField<bool, ParenthesizationField::kNext, 1> IsUseStrictField;
typedef BitField<bool, IsUseStrictField::kNext, 1> IsUseStrongField;
typedef BitField<ValidArrowParam, ParenthesizationField::kNext, 3>
typedef BitField<ValidArrowParam, ParenthesizationField::kNext, 2>
IsValidArrowParamListField;
typedef BitField<PreParserIdentifier::Type, ParenthesizationField::kNext, 10>
IdentifierTypeField;
......@@ -1079,25 +1040,20 @@ class PreParserExpression {
};
// The pre-parser doesn't need to build lists of expressions, identifiers, or
// the like.
template <typename T>
class PreParserList {
// PreParserExpressionList doesn't actually store the expressions because
// PreParser doesn't need to.
class PreParserExpressionList {
public:
// These functions make list->Add(some_expression) work (and do nothing).
PreParserList() : length_(0) {}
PreParserList* operator->() { return this; }
void Add(T, void*) { ++length_; }
PreParserExpressionList() : length_(0) {}
PreParserExpressionList* operator->() { return this; }
void Add(PreParserExpression, void*) { ++length_; }
int length() const { return length_; }
private:
int length_;
};
typedef PreParserList<PreParserExpression> PreParserExpressionList;
typedef PreParserList<PreParserIdentifier> PreParserFormalParameterList;
class PreParserStatement {
public:
static PreParserStatement Default() {
......@@ -1162,7 +1118,16 @@ class PreParserStatement {
};
typedef PreParserList<PreParserStatement> PreParserStatementList;
// PreParserStatementList doesn't actually store the statements because
// the PreParser does not need them.
class PreParserStatementList {
public:
// These functions make list->Add(some_expression) work as no-ops.
PreParserStatementList() {}
PreParserStatementList* operator->() { return this; }
void Add(PreParserStatement, void*) {}
};
class PreParserFactory {
......@@ -1327,8 +1292,6 @@ class PreParserTraits {
typedef PreParserExpression Literal;
typedef PreParserExpressionList ExpressionList;
typedef PreParserExpressionList PropertyList;
typedef PreParserIdentifier FormalParameter;
typedef PreParserFormalParameterList FormalParameterList;
typedef PreParserStatementList StatementList;
// For constructing objects returned by the traversing functions.
......@@ -1472,6 +1435,9 @@ class PreParserTraits {
static PreParserExpression EmptyExpression() {
return PreParserExpression::Default();
}
static PreParserExpression EmptyArrowParamList() {
return PreParserExpression::EmptyArrowParamList();
}
static PreParserExpression EmptyLiteral() {
return PreParserExpression::Default();
}
......@@ -1484,12 +1450,6 @@ class PreParserTraits {
static PreParserExpressionList NullExpressionList() {
return PreParserExpressionList();
}
static PreParserIdentifier EmptyFormalParameter() {
return PreParserIdentifier::Default();
}
static PreParserFormalParameterList NullFormalParameterList() {
return PreParserFormalParameterList();
}
// Odd-ball literal creators.
static PreParserExpression GetLiteralTheHole(int position,
......@@ -1554,11 +1514,6 @@ class PreParserTraits {
return PreParserExpressionList();
}
static PreParserFormalParameterList NewFormalParameterList(int size,
Zone* zone) {
return PreParserFormalParameterList();
}
V8_INLINE void SkipLazyFunctionBody(PreParserIdentifier function_name,
int* materialized_literal_count,
int* expected_property_count, bool* ok) {
......@@ -1570,9 +1525,16 @@ class PreParserTraits {
Variable* fvar, Token::Value fvar_init_op,
FunctionKind kind, bool* ok);
V8_INLINE PreParserFormalParameterList ParseArrowFunctionFormalParameterList(
PreParserExpression expression, const Scanner::Location& params_loc,
FormalParameterErrorLocations* error_locs, bool* is_rest, bool* ok);
// Utility functions
int DeclareArrowParametersFromExpression(PreParserExpression expression,
Scope* scope,
Scanner::Location* undefined_loc,
Scanner::Location* dupe_loc,
bool* ok) {
// TODO(aperez): Detect duplicated identifiers in paramlists.
*ok = expression.IsValidArrowParamList(undefined_loc);
return 0;
}
struct TemplateLiteralState {};
......@@ -1598,11 +1560,6 @@ class PreParserTraits {
return !tag.IsNoTemplateTag();
}
int DeclareFormalParameters(PreParserFormalParameterList params, Scope* scope,
bool has_rest) {
return params->length();
}
void CheckConflictingVarDeclarations(Scope* scope, bool* ok) {}
// Temporary glue; these functions will move to ParserBase.
......@@ -1791,22 +1748,6 @@ PreParserExpression PreParserTraits::SpreadCallNew(PreParserExpression function,
}
PreParserFormalParameterList
PreParserTraits::ParseArrowFunctionFormalParameterList(
PreParserExpression params, const Scanner::Location& params_loc,
FormalParameterErrorLocations* error_locs, bool* is_rest, bool* ok) {
// TODO(wingo): Detect duplicated identifiers in paramlists. Detect parameter
// lists that are too long.
if (!params.IsValidArrowParamList(error_locs, params_loc)) {
*ok = false;
ReportMessageAt(params_loc, "malformed_arrow_function_parameter_list");
return this->NullFormalParameterList();
}
return PreParserFormalParameterList();
}
PreParserStatementList PreParser::ParseEagerFunctionBody(
PreParserIdentifier function_name, int pos, Variable* fvar,
Token::Value fvar_init_op, FunctionKind kind, bool* ok) {
......@@ -2102,13 +2043,12 @@ ParserBase<Traits>::ParsePrimaryExpression(bool* ok) {
case Token::LPAREN:
Consume(Token::LPAREN);
if (allow_harmony_arrow_functions() && Check(Token::RPAREN)) {
// As a primary expression, the only thing that can follow "()" is "=>".
FormalParameterListT params = this->NewFormalParameterList(0, zone());
FormalParameterErrorLocations error_locs;
bool has_rest = false;
result = this->ParseArrowFunctionLiteral(beg_pos, params, error_locs,
has_rest, CHECK_OK);
if (allow_harmony_arrow_functions() && peek() == Token::RPAREN) {
// Arrow functions are the only expression type constructions
// for which an empty parameter list "()" is valid input.
Consume(Token::RPAREN);
result = this->ParseArrowFunctionLiteral(
beg_pos, this->EmptyArrowParamList(), CHECK_OK);
} else {
// Heuristically try to detect immediately called functions before
// seeing the call parentheses.
......@@ -2566,13 +2506,8 @@ ParserBase<Traits>::ParseAssignmentExpression(bool accept_IN, bool* ok) {
if (allow_harmony_arrow_functions() && peek() == Token::ARROW) {
checkpoint.Restore();
FormalParameterErrorLocations error_locs;
Scanner::Location loc(lhs_location.beg_pos, scanner()->location().end_pos);
bool has_rest = false;
FormalParameterListT params = this->ParseArrowFunctionFormalParameterList(
expression, loc, &error_locs, &has_rest, CHECK_OK);
expression = this->ParseArrowFunctionLiteral(
lhs_location.beg_pos, params, error_locs, has_rest, CHECK_OK);
expression = this->ParseArrowFunctionLiteral(lhs_location.beg_pos,
expression, CHECK_OK);
return expression;
}
......@@ -3105,113 +3040,11 @@ ParserBase<Traits>::ParseMemberExpressionContinuation(ExpressionT expression,
}
template <class Traits>
typename ParserBase<Traits>::FormalParameterT
ParserBase<Traits>::ParseFormalParameter(DuplicateFinder* duplicate_finder,
FormalParameterErrorLocations* locs,
bool* ok) {
// FormalParameter[Yield,GeneratorParameter] :
// BindingElement[?Yield, ?GeneratorParameter]
bool is_strict_reserved;
IdentifierT name =
ParseIdentifierOrStrictReservedWord(&is_strict_reserved, ok);
if (!*ok) return this->EmptyFormalParameter();
// Store locations for possible future error reports.
if (!locs->eval_or_arguments.IsValid() && this->IsEvalOrArguments(name)) {
locs->eval_or_arguments = scanner()->location();
}
if (!locs->undefined.IsValid() && this->IsUndefined(name)) {
locs->undefined = scanner()->location();
}
if (!locs->reserved.IsValid() && is_strict_reserved) {
locs->reserved = scanner()->location();
}
if (!locs->duplicate.IsValid() && duplicate_finder != nullptr) {
int prev_value = scanner()->FindSymbol(duplicate_finder, 1);
if (prev_value != 0) locs->duplicate = scanner()->location();
}
return name;
}
template <class Traits>
typename ParserBase<Traits>::FormalParameterListT
ParserBase<Traits>::ParseFormalParameterList(
FormalParameterErrorLocations* locs, bool* is_rest, bool* ok) {
// FormalParameters[Yield,GeneratorParameter] :
// [empty]
// FormalParameterList[?Yield, ?GeneratorParameter]
//
// FormalParameterList[Yield,GeneratorParameter] :
// FunctionRestParameter[?Yield]
// FormalsList[?Yield, ?GeneratorParameter]
// FormalsList[?Yield, ?GeneratorParameter] , FunctionRestParameter[?Yield]
//
// FormalsList[Yield,GeneratorParameter] :
// FormalParameter[?Yield, ?GeneratorParameter]
// FormalsList[?Yield, ?GeneratorParameter] ,
// FormalParameter[?Yield,?GeneratorParameter]
FormalParameterListT result = this->NewFormalParameterList(4, zone_);
DuplicateFinder duplicate_finder(scanner()->unicode_cache());
if (peek() != Token::RPAREN) {
do {
*is_rest = allow_harmony_rest_params() && Check(Token::ELLIPSIS);
FormalParameterT param =
ParseFormalParameter(&duplicate_finder, locs, ok);
if (!*ok) return this->NullFormalParameterList();
result->Add(param, zone());
if (result->length() > Code::kMaxArguments) {
ReportMessage("too_many_parameters");
*ok = false;
return this->NullFormalParameterList();
}
} while (!*is_rest && Check(Token::COMMA));
}
if (is_rest && peek() == Token::COMMA) {
ReportMessageAt(scanner()->peek_location(), "param_after_rest");
*ok = false;
return this->NullFormalParameterList();
}
return result;
}
template <class Traits>
void ParserBase<Traits>::CheckArityRestrictions(
int param_count, FunctionLiteral::ArityRestriction arity_restriction,
int formals_start_pos, int formals_end_pos, bool* ok) {
switch (arity_restriction) {
case FunctionLiteral::GETTER_ARITY:
if (param_count != 0) {
ReportMessageAt(Scanner::Location(formals_start_pos, formals_end_pos),
"bad_getter_arity");
*ok = false;
}
break;
case FunctionLiteral::SETTER_ARITY:
if (param_count != 1) {
ReportMessageAt(Scanner::Location(formals_start_pos, formals_end_pos),
"bad_setter_arity");
*ok = false;
}
break;
default:
break;
}
}
template <class Traits>
typename ParserBase<Traits>::ExpressionT
ParserBase<Traits>::ParseArrowFunctionLiteral(
int start_pos, FormalParameterListT params,
const FormalParameterErrorLocations& error_locs, bool has_rest, bool* ok) {
ParserBase<Traits>::ParseArrowFunctionLiteral(int start_pos,
ExpressionT params_ast,
bool* ok) {
if (peek() == Token::ARROW && scanner_->HasAnyLineTerminatorBeforeNext()) {
// ASI inserts `;` after arrow parameters if a line terminator is found.
// `=> ...` is never a valid expression, so report as syntax error.
......@@ -3233,7 +3066,29 @@ ParserBase<Traits>::ParseArrowFunctionLiteral(
typename Traits::Type::Factory function_factory(ast_value_factory());
FunctionState function_state(&function_state_, &scope_, scope,
kArrowFunction, &function_factory);
num_parameters = this->DeclareFormalParameters(params, scope_, has_rest);
Scanner::Location undefined_loc = Scanner::Location::invalid();
Scanner::Location dupe_loc = Scanner::Location::invalid();
// TODO(arv): Pass in eval_args_loc and reserved_loc here.
num_parameters = Traits::DeclareArrowParametersFromExpression(
params_ast, scope_, &undefined_loc, &dupe_loc, ok);
if (!*ok) {
ReportMessageAt(
Scanner::Location(start_pos, scanner()->location().beg_pos),
"malformed_arrow_function_parameter_list");
return this->EmptyExpression();
}
if (undefined_loc.IsValid()) {
// Workaround for preparser not keeping track of positions.
undefined_loc = Scanner::Location(start_pos,
scanner()->location().end_pos);
}
if (num_parameters > Code::kMaxArguments) {
ReportMessageAt(Scanner::Location(params_ast->position(), position()),
"too_many_parameters");
*ok = false;
return this->EmptyExpression();
}
Expect(Token::ARROW, CHECK_OK);
......@@ -3272,13 +3127,15 @@ ParserBase<Traits>::ParseArrowFunctionLiteral(
scope->set_start_position(start_pos);
scope->set_end_position(scanner()->location().end_pos);
// Arrow function formal parameters are parsed as StrictFormalParameterList,
// which is not the same as "parameters of a strict function"; it only means
// that duplicates are not allowed. Of course, the arrow function may
// itself be strict as well.
// Arrow function *parameter lists* are always checked as in strict mode.
// TODO(arv): eval_args_loc and reserved_loc needs to be set by
// DeclareArrowParametersFromExpression.
Scanner::Location eval_args_loc = Scanner::Location::invalid();
Scanner::Location reserved_loc = Scanner::Location::invalid();
const bool use_strict_params = true;
this->CheckFunctionParameterNames(language_mode(), use_strict_params,
error_locs, CHECK_OK);
eval_args_loc, undefined_loc, dupe_loc,
reserved_loc, CHECK_OK);
// Validate strict mode.
if (is_strict(language_mode())) {
......
......@@ -3502,23 +3502,20 @@ TEST(ErrorsArrowFunctions) {
"(x, (y, z)) => 0",
"((x, y), z) => 0",
// Arrow function formal parameters are parsed as StrictFormalParameters,
// which confusingly only implies that there are no duplicates. Words
// reserved in strict mode, and eval or arguments, are indeed valid in
// sloppy mode.
"eval => { 'use strict'; 0 }",
"arguments => { 'use strict'; 0 }",
"yield => { 'use strict'; 0 }",
"interface => { 'use strict'; 0 }",
"(eval) => { 'use strict'; 0 }",
"(arguments) => { 'use strict'; 0 }",
"(yield) => { 'use strict'; 0 }",
"(interface) => { 'use strict'; 0 }",
"(eval, bar) => { 'use strict'; 0 }",
"(bar, eval) => { 'use strict'; 0 }",
"(bar, arguments) => { 'use strict'; 0 }",
"(bar, yield) => { 'use strict'; 0 }",
"(bar, interface) => { 'use strict'; 0 }",
// Parameter lists are always validated as strict, so those are errors.
"eval => {}",
"arguments => {}",
"yield => {}",
"interface => {}",
"(eval) => {}",
"(arguments) => {}",
"(yield) => {}",
"(interface) => {}",
"(eval, bar) => {}",
"(bar, eval) => {}",
"(bar, arguments) => {}",
"(bar, yield) => {}",
"(bar, interface) => {}",
// TODO(aperez): Detecting duplicates does not work in PreParser.
// "(bar, bar) => {}",
......@@ -3625,66 +3622,6 @@ TEST(NoErrorsArrowFunctions) {
}
TEST(ArrowFunctionsSloppyParameterNames) {
const char* strong_context_data[][2] = {
{"'use strong'; ", ";"},
{"'use strong'; bar ? (", ") : baz;"},
{"'use strong'; bar ? baz : (", ");"},
{"'use strong'; bar, ", ";"},
{"'use strong'; ", ", bar;"},
{NULL, NULL}
};
const char* strict_context_data[][2] = {
{"'use strict'; ", ";"},
{"'use strict'; bar ? (", ") : baz;"},
{"'use strict'; bar ? baz : (", ");"},
{"'use strict'; bar, ", ";"},
{"'use strict'; ", ", bar;"},
{NULL, NULL}
};
const char* sloppy_context_data[][2] = {
{"", ";"},
{"bar ? (", ") : baz;"},
{"bar ? baz : (", ");"},
{"bar, ", ";"},
{"", ", bar;"},
{NULL, NULL}
};
const char* statement_data[] = {
"eval => {}",
"arguments => {}",
"yield => {}",
"interface => {}",
"(eval) => {}",
"(arguments) => {}",
"(yield) => {}",
"(interface) => {}",
"(eval, bar) => {}",
"(bar, eval) => {}",
"(bar, arguments) => {}",
"(bar, yield) => {}",
"(bar, interface) => {}",
"(interface, eval) => {}",
"(interface, arguments) => {}",
"(eval, interface) => {}",
"(arguments, interface) => {}",
NULL
};
static const ParserFlag always_flags[] = { kAllowHarmonyArrowFunctions,
kAllowStrongMode};
RunParserSyncTest(strong_context_data, statement_data, kError, NULL, 0,
always_flags, arraysize(always_flags));
RunParserSyncTest(strict_context_data, statement_data, kError, NULL, 0,
always_flags, arraysize(always_flags));
RunParserSyncTest(sloppy_context_data, statement_data, kSuccess, NULL, 0,
always_flags, arraysize(always_flags));
}
TEST(SuperNoErrors) {
// Tests that parser and preparser accept 'super' keyword in right places.
const char* context_data[][2] = {
......
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Flags: --harmony-rest-parameters
function foo(...b, a) { return a }
*%(basename)s:7: SyntaxError: Rest parameter must be last formal parameter
function foo(...b, a) { return a }
^
SyntaxError: Rest parameter must be last formal parameter
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function foo(b, eval) { "use strict"; return b }
*%(basename)s:5: SyntaxError: Unexpected eval or arguments in strict mode
function foo(b, eval) { "use strict"; return b }
^^^^
SyntaxError: Unexpected eval or arguments in strict mode
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function foo(b, a, a,) { return a }
*%(basename)s:5: SyntaxError: Unexpected token )
function foo(b, a, a,) { return a }
^
SyntaxError: Unexpected token )
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
function foo(b, a, a, d) { return a }
*%(basename)s:6: SyntaxError: Strict mode function may not have duplicate parameter names
function foo(b, a, a, d) { return a }
^
SyntaxError: Strict mode function may not have duplicate parameter names
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment