From 892c85485881f8be2f17bd83238980f858126576 Mon Sep 17 00:00:00 2001 From: caitpotter88 Date: Mon, 1 Jun 2015 10:10:39 -0700 Subject: [PATCH] [es6] implement default parameters via desugaring Stage 1 implementation: - Parameters can't be referenced before initialized (from left-to-right) - SingleNameBindings only, no support for BindingPatterns Known issues: - Incorrect scoping (parameter expressions may reference variables declared in function body) - Function arity is untouched - Hole-checking needs work - Rest parameters are broken when mixed with optional arguments BUG=v8:2160 LOG=N R=arv@chromium.org, rossberg@chromium.org Review URL: https://codereview.chromium.org/1127063003 Cr-Commit-Position: refs/heads/master@{#28739} --- src/bootstrapper.cc | 3 + src/flag-definitions.h | 3 +- src/messages.h | 2 + src/parser.cc | 158 +++++++++++++++++- src/parser.h | 10 +- src/preparser.cc | 7 +- src/preparser.h | 55 +++++- src/scopes.cc | 42 +++-- src/scopes.h | 16 +- src/variables.h | 4 + .../harmony/default-parameters-debug.js | 65 +++++++ .../harmony/default-parameters-lazy.js | 22 +++ test/mjsunit/harmony/default-parameters.js | 143 ++++++++++++++++ 13 files changed, 501 insertions(+), 29 deletions(-) create mode 100644 test/mjsunit/harmony/default-parameters-debug.js create mode 100644 test/mjsunit/harmony/default-parameters-lazy.js create mode 100644 test/mjsunit/harmony/default-parameters.js diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 3a26e40b4..566188e3c 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1766,6 +1766,7 @@ EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_destructuring) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_object) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_spread_arrays) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_sharedarraybuffer) +EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_default_parameters) void Genesis::InstallNativeFunctions_harmony_proxies() { @@ -1797,6 +1798,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_spreadcalls) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_destructuring) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_spread_arrays) +EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_default_parameters) void Genesis::InitializeGlobal_harmony_regexps() { Handle builtins(native_context()->builtins()); @@ -2441,6 +2443,7 @@ bool Genesis::InstallExperimentalNatives() { static const char* harmony_spread_arrays_natives[] = {nullptr}; static const char* harmony_sharedarraybuffer_natives[] = { "native harmony-sharedarraybuffer.js", NULL}; + static const char* harmony_default_parameters_natives[] = {nullptr}; for (int i = ExperimentalNatives::GetDebuggerCount(); i < ExperimentalNatives::GetBuiltinsCount(); i++) { diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 03d33071f..290aec75d 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -194,7 +194,8 @@ DEFINE_IMPLICATION(es_staging, harmony) V(harmony_reflect, "harmony Reflect API") \ V(harmony_destructuring, "harmony destructuring") \ V(harmony_spread_arrays, "harmony spread in array literals") \ - V(harmony_sharedarraybuffer, "harmony sharedarraybuffer") + V(harmony_sharedarraybuffer, "harmony sharedarraybuffer") \ + V(harmony_default_parameters, "harmony default parameters") // Features that are complete (but still behind --harmony/es-staging flag). #define HARMONY_STAGED(V) \ diff --git a/src/messages.h b/src/messages.h index 50cb2c60e..7e45d1f81 100644 --- a/src/messages.h +++ b/src/messages.h @@ -331,6 +331,8 @@ class CallSite { T(ParamAfterRest, "Rest parameter must be last formal parameter") \ T(BadSetterRestParameter, \ "Setter function argument must not be a rest parameter") \ + T(BadRestParameterInitializer, \ + "Rest parameters must not have an initializer") \ T(ParenthesisInArgString, "Function arg string contains parenthesis") \ T(SingleFunctionLiteral, "Single function literal required") \ T(SloppyLexical, \ diff --git a/src/parser.cc b/src/parser.cc index 196853c02..8d90fb6bf 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -884,6 +884,7 @@ Parser::Parser(ParseInfo* info) set_allow_harmony_spreadcalls(FLAG_harmony_spreadcalls); set_allow_harmony_destructuring(FLAG_harmony_destructuring); set_allow_harmony_spread_arrays(FLAG_harmony_spread_arrays); + set_allow_harmony_default_parameters(FLAG_harmony_default_parameters); set_allow_strong_mode(FLAG_strong_mode); for (int feature = 0; feature < v8::Isolate::kUseCounterFeatureCount; ++feature) { @@ -1148,9 +1149,16 @@ FunctionLiteral* Parser::ParseLazy(Isolate* isolate, ParseInfo* info, scope->set_start_position(shared_info->start_position()); ExpressionClassifier formals_classifier; bool has_rest = false; + bool has_parameter_expressions = false; + + // TODO(caitp): make default parameters work in arrow functions + ZoneList* initializers = + new (zone()) ZoneList(0, zone()); if (Check(Token::LPAREN)) { // '(' StrictFormalParameters ')' - ParseFormalParameterList(scope, &has_rest, &formals_classifier, &ok); + ParseFormalParameterList(scope, initializers, + &has_parameter_expressions, &has_rest, + &formals_classifier, &ok); if (ok) ok = Check(Token::RPAREN); } else { // BindingIdentifier @@ -3404,6 +3412,114 @@ Statement* Parser::DesugarLexicalBindingsInForStatement( } +ZoneList* Parser::DesugarInitializeParameters( + Scope* scope, bool has_parameter_expressions, + ZoneList* initializers) { + DCHECK(scope->is_function_scope()); + + if (has_parameter_expressions) { + // If has_parameter_expressions for the function is true, each parameter is + // desugared as follows: + // + // SingleNameBinding : + // let = %_Arguments(); + // SingleNameBinding Initializer + // let = IS_UNDEFINED(%_Arguments()) ? + // : %_Arguments(); + // + // TODO(caitp, dslomov): support BindingPatterns & rest parameters + // + scope->UndeclareParametersForExpressions(); + ZoneList* body = + new (zone()) ZoneList(initializers->length(), zone()); + for (int i = 0; i < initializers->length(); ++i) { + Expression* initializer = initializers->at(i); + + // Position of parameter VariableProxy, for hole-checking + int pos = scope->parameter_position(i); + + static const int kCapacity = 1; + static const bool kIsInitializerBlock = true; + Block* param_block = + factory()->NewBlock(nullptr, kCapacity, kIsInitializerBlock, pos); + + VariableProxy* proxy = + NewUnresolved(scope->parameter(i)->raw_name(), LET); + VariableDeclaration* declaration = factory()->NewVariableDeclaration( + proxy, LET, scope, RelocInfo::kNoPosition); + + bool ok = true; + // All formal parameters have been removed from the scope VariableMap, + // and so Declare() should not be able to fail. + proxy = factory()->NewVariableProxy(Declare(declaration, true, &ok), pos); + DCHECK(ok); + + const AstRawString* fn_name = ast_value_factory()->empty_string(); + const Runtime::Function* arguments = + Runtime::FunctionForId(Runtime::kInlineArguments); + ZoneList* arguments_i0 = + new (zone()) ZoneList(1, zone()); + arguments_i0->Add(factory()->NewSmiLiteral(i, RelocInfo::kNoPosition), + zone()); + + if (initializer == nullptr) { + // let = %_Arguments(i) + Expression* assign = factory()->NewAssignment( + Token::INIT_LET, proxy, + factory()->NewCallRuntime(fn_name, arguments, arguments_i0, + RelocInfo::kNoPosition), + RelocInfo::kNoPosition); + param_block->AddStatement( + factory()->NewExpressionStatement(assign, RelocInfo::kNoPosition), + zone()); + proxy->var()->set_initializer_position(pos); + } else { + // IS_UNDEFINED(%_Arguments(i)) ? : %_Arguments(i); + ZoneList* arguments_i1 = + new (zone()) ZoneList(1, zone()); + arguments_i1->Add(factory()->NewSmiLiteral(i, RelocInfo::kNoPosition), + zone()); + + Expression* arg_or_default = factory()->NewConditional( + // condition: + factory()->NewCompareOperation( + Token::EQ_STRICT, + factory()->NewCallRuntime(fn_name, arguments, arguments_i0, + RelocInfo::kNoPosition), + factory()->NewUndefinedLiteral(RelocInfo::kNoPosition), + RelocInfo::kNoPosition), + // if true: + initializer, + // if false: + factory()->NewCallRuntime(fn_name, arguments, arguments_i1, + RelocInfo::kNoPosition), + RelocInfo::kNoPosition); + + Expression* assign = factory()->NewAssignment( + Token::INIT_LET, proxy, arg_or_default, RelocInfo::kNoPosition); + + param_block->AddStatement( + factory()->NewExpressionStatement(assign, RelocInfo::kNoPosition), + zone()); + proxy->var()->set_initializer_position(initializer->position()); + } + body->Add(param_block, zone()); + } + return body; + } else { + // If has_parameter_expressions is false, remove the unnecessary parameter + // block scopes. + ZoneList* scopes = scope->inner_scopes(); + for (int i = 0; i < scopes->length(); ++i) { + Scope* scope = scopes->at(i); + DCHECK(scope->is_block_scope()); + scope->FinalizeBlockScope(); + } + return nullptr; + } +} + + Statement* Parser::ParseForStatement(ZoneList* labels, bool* ok) { // ForStatement :: @@ -3774,7 +3890,8 @@ void ParserTraits::DeclareArrowFunctionParameters( parser_->scope_->RemoveUnresolved(expr->AsVariableProxy()); bool is_rest = false; - bool is_duplicate = DeclareFormalParameter(scope, raw_name, is_rest); + int pos = expr->position(); + bool is_duplicate = DeclareFormalParameter(scope, raw_name, is_rest, pos); if (is_duplicate && !duplicate_loc->IsValid()) { *duplicate_loc = param_location; @@ -3885,11 +4002,15 @@ FunctionLiteral* Parser::ParseFunctionLiteral( } bool has_rest = false; + bool has_parameter_expressions = false; Expect(Token::LPAREN, CHECK_OK); int start_position = scanner()->location().beg_pos; scope_->set_start_position(start_position); - num_parameters = ParseFormalParameterList(scope, &has_rest, - &formals_classifier, CHECK_OK); + ZoneList* initializers = + new (zone()) ZoneList(0, zone()); + num_parameters = ParseFormalParameterList( + scope, initializers, &has_parameter_expressions, &has_rest, + &formals_classifier, CHECK_OK); Expect(Token::RPAREN, CHECK_OK); int formals_end_position = scanner()->location().end_pos; @@ -3990,8 +4111,31 @@ FunctionLiteral* Parser::ParseFunctionLiteral( } } if (!is_lazily_parsed) { - body = ParseEagerFunctionBody(function_name, pos, fvar, fvar_init_op, - kind, CHECK_OK); + body = DesugarInitializeParameters(scope, has_parameter_expressions, + initializers); + if (has_parameter_expressions) { + // TODO(caitp): Function body scope must be a declaration scope + Scope* function_body_scope = NewScope(scope, BLOCK_SCOPE); + function_body_scope->set_start_position(scope->start_position()); + function_body_scope->SetScopeName(function_name); + BlockState function_body_state(&scope_, function_body_scope); + ZoneList* inner_body = ParseEagerFunctionBody( + function_name, pos, fvar, fvar_init_op, kind, CHECK_OK); + + // Declare Block node + Block* block = + factory()->NewBlock(nullptr, inner_body->length(), false, pos); + block->set_scope(function_body_scope); + for (int i = 0; i < inner_body->length(); ++i) { + block->AddStatement(inner_body->at(i), zone()); + } + + scope->set_end_position(function_body_scope->end_position()); + body->Add(block, zone()); + } else { + body = ParseEagerFunctionBody(function_name, pos, fvar, fvar_init_op, + kind, CHECK_OK); + } materialized_literal_count = function_state.materialized_literal_count(); expected_property_count = function_state.expected_property_count(); handler_count = function_state.handler_count(); @@ -4275,6 +4419,8 @@ PreParser::PreParseResult Parser::ParseLazyFunctionBodyWithPreParser( allow_harmony_destructuring()); reusable_preparser_->set_allow_harmony_spread_arrays( allow_harmony_spread_arrays()); + reusable_preparser_->set_allow_harmony_default_parameters( + allow_harmony_default_parameters()); reusable_preparser_->set_allow_strong_mode(allow_strong_mode()); } PreParser::PreParseResult result = reusable_preparser_->PreParseLazyFunction( diff --git a/src/parser.h b/src/parser.h index eba677c94..584d69b4f 100644 --- a/src/parser.h +++ b/src/parser.h @@ -748,9 +748,10 @@ class ParserTraits { FunctionKind kind = kNormalFunction); bool DeclareFormalParameter(Scope* scope, const AstRawString* name, - bool is_rest) { + bool is_rest, int pos) { bool is_duplicate = false; - Variable* var = scope->DeclareParameter(name, VAR, is_rest, &is_duplicate); + Variable* var = + scope->DeclareParameter(name, VAR, is_rest, &is_duplicate, pos); if (is_sloppy(scope->language_mode())) { // TODO(sigurds) Mark every parameter as maybe assigned. This is a // conservative approximation necessary to account for parameters @@ -1025,7 +1026,6 @@ class Parser : public ParserBase { bool* ok_; }; - void ParseVariableDeclarations(VariableDeclarationContext var_context, DeclarationParsingResult* parsing_result, bool* ok); @@ -1071,6 +1071,10 @@ class Parser : public ParserBase { ForStatement* loop, Statement* init, Expression* cond, Statement* next, Statement* body, bool* ok); + ZoneList* DesugarInitializeParameters( + Scope* scope, bool has_parameter_expressions, + ZoneList* initializers); + FunctionLiteral* ParseFunctionLiteral( const AstRawString* name, Scanner::Location function_name_location, bool name_is_strict_reserved, FunctionKind kind, diff --git a/src/preparser.cc b/src/preparser.cc index 2644b4374..8390f70bd 100644 --- a/src/preparser.cc +++ b/src/preparser.cc @@ -1035,14 +1035,17 @@ PreParser::Expression PreParser::ParseFunctionLiteral( ExpressionClassifier formals_classifier; bool has_rest = false; + bool has_parameter_expressions = false; + PreParserExpressionList initializers = NewExpressionList(0, zone()); Expect(Token::LPAREN, CHECK_OK); int start_position = scanner()->location().beg_pos; function_scope->set_start_position(start_position); int num_parameters; { DuplicateFinder duplicate_finder(scanner()->unicode_cache()); - num_parameters = ParseFormalParameterList(&duplicate_finder, &has_rest, - &formals_classifier, CHECK_OK); + num_parameters = ParseFormalParameterList( + &duplicate_finder, initializers, &has_parameter_expressions, &has_rest, + &formals_classifier, CHECK_OK); } Expect(Token::RPAREN, CHECK_OK); int formals_end_position = scanner()->location().end_pos; diff --git a/src/preparser.h b/src/preparser.h index 2fe5db1d0..6c12be95a 100644 --- a/src/preparser.h +++ b/src/preparser.h @@ -66,6 +66,7 @@ class ParserBase : public Traits { public: // Shorten type names defined by Traits. typedef typename Traits::Type::Expression ExpressionT; + typedef typename Traits::Type::ExpressionList ExpressionListT; typedef typename Traits::Type::Identifier IdentifierT; typedef typename Traits::Type::FormalParameter FormalParameterT; typedef typename Traits::Type::FormalParameterScope FormalParameterScopeT; @@ -97,6 +98,7 @@ class ParserBase : public Traits { allow_harmony_computed_property_names_(false), allow_harmony_rest_params_(false), allow_harmony_spreadcalls_(false), + allow_harmony_default_parameters_(false), allow_strong_mode_(false) {} // Getters that indicate whether certain syntactical constructs are @@ -126,6 +128,9 @@ class ParserBase : public Traits { bool allow_harmony_spread_arrays() const { return allow_harmony_spread_arrays_; } + bool allow_harmony_default_parameters() const { + return allow_harmony_default_parameters_; + } bool allow_strong_mode() const { return allow_strong_mode_; } @@ -167,6 +172,10 @@ class ParserBase : public Traits { void set_allow_harmony_spread_arrays(bool allow) { allow_harmony_spread_arrays_ = allow; } + void set_allow_harmony_default_parameters(bool allow) { + allow_harmony_default_parameters_ = allow; + } + protected: enum AllowRestrictedIdentifiers { @@ -919,7 +928,9 @@ class ParserBase : public Traits { void ParseFormalParameter(FormalParameterScopeT* scope, bool is_rest, ExpressionClassifier* classifier, bool* ok); - int ParseFormalParameterList(FormalParameterScopeT* scope, bool* has_rest, + int ParseFormalParameterList(FormalParameterScopeT* scope, + ExpressionListT initializers, + bool* has_parameter_expressions, bool* has_rest, ExpressionClassifier* classifier, bool* ok); void CheckArityRestrictions( int param_count, FunctionLiteral::ArityRestriction arity_restriction, @@ -1022,6 +1033,7 @@ class ParserBase : public Traits { bool allow_harmony_spreadcalls_; bool allow_harmony_destructuring_; bool allow_harmony_spread_arrays_; + bool allow_harmony_default_parameters_; bool allow_strong_mode_; }; @@ -1799,8 +1811,8 @@ class PreParserTraits { } V8_INLINE bool DeclareFormalParameter(DuplicateFinder* scope, - PreParserIdentifier param, - bool is_rest); + PreParserIdentifier param, bool is_rest, + int pos); void CheckConflictingVarDeclarations(Scope* scope, bool* ok) {} @@ -1996,7 +2008,7 @@ PreParserExpression PreParserTraits::SpreadCallNew(PreParserExpression function, bool PreParserTraits::DeclareFormalParameter( DuplicateFinder* duplicate_finder, PreParserIdentifier current_identifier, - bool is_rest) { + bool is_rest, int pos) { return pre_parser_->scanner()->FindSymbol(duplicate_finder, 1) != 0; } @@ -3673,10 +3685,11 @@ void ParserBase::ParseFormalParameter(FormalParameterScopeT* scope, bool* ok) { // FormalParameter[Yield,GeneratorParameter] : // BindingElement[?Yield, ?GeneratorParameter] + int pos = peek_position(); IdentifierT name = ParseAndClassifyIdentifier(classifier, ok); if (!*ok) return; - bool was_declared = Traits::DeclareFormalParameter(scope, name, is_rest); + bool was_declared = Traits::DeclareFormalParameter(scope, name, is_rest, pos); if (was_declared) { classifier->RecordDuplicateFormalParameterError(scanner()->location()); } @@ -3685,7 +3698,8 @@ void ParserBase::ParseFormalParameter(FormalParameterScopeT* scope, template int ParserBase::ParseFormalParameterList( - FormalParameterScopeT* scope, bool* is_rest, + FormalParameterScopeT* scope, ExpressionListT initializers, + bool* has_parameter_expressions, bool* is_rest, ExpressionClassifier* classifier, bool* ok) { // FormalParameters[Yield,GeneratorParameter] : // [empty] @@ -3705,14 +3719,43 @@ int ParserBase::ParseFormalParameterList( if (peek() != Token::RPAREN) { do { + Scope* param_scope = NewScope(scope_, BLOCK_SCOPE); + BlockState param_state(&scope_, param_scope); + param_scope->set_start_position(peek_position()); if (++parameter_count > Code::kMaxArguments) { ReportMessage(MessageTemplate::kTooManyParameters); *ok = false; return -1; } + + int start_pos = peek_position(); *is_rest = allow_harmony_rest_params() && Check(Token::ELLIPSIS); ParseFormalParameter(scope, *is_rest, classifier, ok); if (!*ok) return -1; + + // TODO(caitp, dslomov): set *has_parameter_expressions to true if + // formal parameter is an ObjectBindingPattern containing computed + // property keys + + ExpressionT initializer = this->EmptyExpression(); + if (allow_harmony_default_parameters() && Check(Token::ASSIGN)) { + // Default parameter initializer + static const bool accept_IN = true; + ExpressionClassifier classifier; + initializer = ParseAssignmentExpression(accept_IN, &classifier, ok); + if (!*ok) return -1; + *has_parameter_expressions = true; + + // A rest parameter cannot be initialized. + if (*is_rest) { + Scanner::Location loc(start_pos, scanner()->location().end_pos); + ReportMessageAt(loc, MessageTemplate::kBadRestParameterInitializer); + *ok = false; + return -1; + } + } + param_scope->set_end_position(scanner()->location().end_pos); + initializers->Add(initializer, zone()); } while (!*is_rest && Check(Token::COMMA)); if (*is_rest && peek() == Token::COMMA) { diff --git a/src/scopes.cc b/src/scopes.cc index 21a8805c5..98c689b4c 100644 --- a/src/scopes.cc +++ b/src/scopes.cc @@ -77,6 +77,7 @@ Scope::Scope(Zone* zone, Scope* outer_scope, ScopeType scope_type, internals_(4, zone), temps_(4, zone), params_(4, zone), + param_positions_(4, zone), unresolved_(16, zone), decls_(4, zone), module_descriptor_( @@ -100,6 +101,7 @@ Scope::Scope(Zone* zone, Scope* inner_scope, ScopeType scope_type, internals_(4, zone), temps_(4, zone), params_(4, zone), + param_positions_(4, zone), unresolved_(16, zone), decls_(4, zone), module_descriptor_(NULL), @@ -126,6 +128,7 @@ Scope::Scope(Zone* zone, Scope* inner_scope, internals_(0, zone), temps_(0, zone), params_(0, zone), + param_positions_(0, zone), unresolved_(0, zone), decls_(0, zone), module_descriptor_(NULL), @@ -183,6 +186,7 @@ void Scope::SetDefaults(ScopeType scope_type, Scope* outer_scope, module_var_ = NULL, rest_parameter_ = NULL; rest_index_ = -1; + has_parameter_expressions_ = false; scope_info_ = scope_info; start_position_ = RelocInfo::kNoPosition; end_position_ = RelocInfo::kNoPosition; @@ -470,7 +474,7 @@ Variable* Scope::Lookup(const AstRawString* name) { Variable* Scope::DeclareParameter(const AstRawString* name, VariableMode mode, - bool is_rest, bool* is_duplicate) { + bool is_rest, bool* is_duplicate, int pos) { DCHECK(!already_resolved()); DCHECK(is_function_scope()); Variable* var = variables_.Declare(this, name, mode, Variable::NORMAL, @@ -483,6 +487,7 @@ Variable* Scope::DeclareParameter(const AstRawString* name, VariableMode mode, // TODO(wingo): Avoid O(n^2) check. *is_duplicate = IsDeclaredParameter(name); params_.Add(var, zone()); + param_positions_.Add(pos, zone()); return var; } @@ -553,6 +558,18 @@ void Scope::AddDeclaration(Declaration* declaration) { } +void Scope::UndeclareParametersForExpressions() { + DCHECK(is_function_scope()); + DCHECK(!has_parameter_expressions_); + has_parameter_expressions_ = true; + for (int i = 0; i < num_parameters(); ++i) { + Variable* p = parameter(i); + const AstRawString* name = p->raw_name(); + variables_.Remove(const_cast(name), name->hash()); + } +} + + void Scope::SetIllegalRedeclaration(Expression* expression) { // Record only the first illegal redeclaration. if (!HasIllegalRedeclaration()) { @@ -1419,16 +1436,21 @@ void Scope::AllocateParameterLocals(Isolate* isolate) { // If it does, and if it is not copied into the context object, it must // receive the highest parameter index for that parameter; thus iteration // order is relevant! - for (int i = params_.length() - 1; i >= 0; --i) { - Variable* var = params_[i]; - if (var == rest_parameter_) continue; - - DCHECK(var->scope() == this); - if (uses_sloppy_arguments || has_forced_context_allocation()) { - // Force context allocation of the parameter. - var->ForceContextAllocation(); + // + // If hasParameterExpressions is true, parameters are redeclared during + // desugaring, and must not be allocated here. + if (!has_parameter_expressions_) { + for (int i = params_.length() - 1; i >= 0; --i) { + Variable* var = params_[i]; + if (var == rest_parameter_) continue; + + DCHECK(var->scope() == this); + if (uses_sloppy_arguments || has_forced_context_allocation()) { + // Force context allocation of the parameter. + var->ForceContextAllocation(); + } + AllocateParameter(var, i); } - AllocateParameter(var, i); } } diff --git a/src/scopes.h b/src/scopes.h index d56018532..85ab77340 100644 --- a/src/scopes.h +++ b/src/scopes.h @@ -126,7 +126,7 @@ class Scope: public ZoneObject { // parameters the rightmost one 'wins'. However, the implementation // expects all parameters to be declared and from left to right. Variable* DeclareParameter(const AstRawString* name, VariableMode mode, - bool is_rest, bool* is_duplicate); + bool is_rest, bool* is_duplicate, int pos); // Declare a local variable in this scope. If the variable has been // declared before, the previously declared variable is returned. @@ -182,6 +182,10 @@ class Scope: public ZoneObject { // the scope; see codegen.cc:ProcessDeclarations. void AddDeclaration(Declaration* declaration); + // Formal parameters may be re-declared as lexical declarations in order to + // support TDZ semantics specified in ECMAScript 6. + void UndeclareParametersForExpressions(); + // --------------------------------------------------------------------------- // Illegal redeclaration support. @@ -373,6 +377,13 @@ class Scope: public ZoneObject { return params_[index]; } + // TODO(caitp): This probably won't work when BindingPatterns are supported + // in function parameters. Need a better way. + int parameter_position(int index) const { + DCHECK(is_function_scope()); + return param_positions_[index]; + } + // Returns the default function arity --- does not include rest parameters. int default_function_length() const { int count = params_.length(); @@ -563,6 +574,7 @@ class Scope: public ZoneObject { ZoneList temps_; // Parameter list in source order. ZoneList params_; + ZoneList param_positions_; // Variables that must be looked up dynamically. DynamicScopePart* dynamics_; // Unresolved variables referred to from this scope. @@ -637,6 +649,8 @@ class Scope: public ZoneObject { Variable* rest_parameter_; int rest_index_; + bool has_parameter_expressions_; + // Serialized scope info support. Handle scope_info_; bool already_resolved() { return already_resolved_; } diff --git a/src/variables.h b/src/variables.h index 384a88595..72680514c 100644 --- a/src/variables.h +++ b/src/variables.h @@ -67,6 +67,10 @@ class Variable: public ZoneObject { Handle name() const { return name_->string(); } const AstRawString* raw_name() const { return name_; } VariableMode mode() const { return mode_; } + void set_mode(VariableMode mode) { + // Don't use this unless you have a very good reason + mode_ = mode; + } bool has_forced_context_allocation() const { return force_context_allocation_; } diff --git a/test/mjsunit/harmony/default-parameters-debug.js b/test/mjsunit/harmony/default-parameters-debug.js new file mode 100644 index 000000000..0170d2943 --- /dev/null +++ b/test/mjsunit/harmony/default-parameters-debug.js @@ -0,0 +1,65 @@ +// 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: --expose-debug-as debug --harmony-default-parameters + +// Get the Debug object exposed from the debug context global object. +Debug = debug.Debug + +listenerComplete = false; +breakPointCount = 0; + +function listener(event, exec_state, event_data, data) { + if (event == Debug.DebugEvent.Break) { + breakPointCount++; + if (breakPointCount == 1) { + // Break point in initializer for parameter `a`, invoked by + // initializer for parameter `b` + assertEquals('default', exec_state.frame(1).evaluate('mode').value()); + + // initializer for `b` can't refer to `b` + assertThrows(function() { + return exec_state.frame(1).evaluate('b').value(); + }, ReferenceError); + + assertThrows(function() { + return exec_state.frame(1).evaluate('c'); + }, ReferenceError); + } else if (breakPointCount == 2) { + // Break point in IIFE initializer for parameter `c` + assertEquals('modeFn', exec_state.frame(1).evaluate('a.name').value()); + assertEquals('default', exec_state.frame(1).evaluate('b').value()); + assertThrows(function() { + return exec_state.frame(1).evaluate('c'); + }, ReferenceError); + } else if (breakPointCount == 3) { + // Break point in function body --- `c` parameter is shadowed + assertEquals('modeFn', exec_state.frame(0).evaluate('a.name').value()); + assertEquals('default', exec_state.frame(0).evaluate('b').value()); + // TODO(caitp): fix scoping so that parameter `c` can be shadowed by vars + //assertEquals(true, exec_state.frame(0).evaluate('c').value()); + } + } +}; + +// Add the debug event listener. +Debug.setListener(listener); + +function f(a = function modeFn(mode) { + debugger; + return mode; + }, + b = a("default"), + c = (function() { + debugger; + })()) { + // TODO(caitp): fix scoping so that parameter `c` can be shadowed by vars + //var c = true; + debugger; +}; + +f(); + +// Make sure that the debug event listener vas invoked. +assertEquals(3, breakPointCount); diff --git a/test/mjsunit/harmony/default-parameters-lazy.js b/test/mjsunit/harmony/default-parameters-lazy.js new file mode 100644 index 000000000..3637a0f1d --- /dev/null +++ b/test/mjsunit/harmony/default-parameters-lazy.js @@ -0,0 +1,22 @@ +// 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-default-parameters --min-preparse-length=0 + +var i = 0; +function f(handler = function(b) { return b + "#" + (++i); }, b = "red") { + return handler(b); +} + +assertEquals([ + "blue#1", + "red#2", + "red", + "yellow#3" +], [ + f(undefined, "blue"), + f(), + f(function(b) { return b; }), + f(undefined, "yellow") +]); diff --git a/test/mjsunit/harmony/default-parameters.js b/test/mjsunit/harmony/default-parameters.js new file mode 100644 index 000000000..e54b5a882 --- /dev/null +++ b/test/mjsunit/harmony/default-parameters.js @@ -0,0 +1,143 @@ +// 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-default-parameters --harmony-arrow-functions + +function return_specified() { return "specified"; } + +var method_returns_specified = { + method() { return "specified"; } +}; + + +(function testDefaultFunctions() { + function optional_function(handler = function() { }) { + assertEquals("function", typeof handler); + + // TODO(caitp): infer function name correctly + // (https://code.google.com/p/v8/issues/detail?id=3699) + // assertEquals("handler", handler.name); + + return handler(); + } + assertEquals(undefined, optional_function()); + assertEquals(undefined, optional_function(undefined)); + assertEquals("specified", optional_function(return_specified)); +})(); + + +(function testDefaultFunctionReferencesParameters() { + function fn1(handler = function() { return value; }, value) { + return handler(); + } + assertEquals(undefined, fn1()); + assertEquals(undefined, fn1(undefined, undefined)); + assertEquals(1, fn1(undefined, 1)); + + function fn2(value, handler = function() { return value; }) { + return handler(); + } + assertEquals(undefined, fn2()); + assertEquals(undefined, fn2(undefined)); + assertEquals(1, fn2(1)); +})(); + + +(function testDefaultObjects() { + function optional_object(object = { method() { return "method"; } }) { + assertEquals("object", typeof object); + + assertEquals("function", typeof object.method); + return object.method(); + } + + assertEquals("method", optional_object()); + assertEquals("method", optional_object(undefined)); + assertEquals("specified", optional_object(method_returns_specified)); + + + assertEquals(4, (function(x = { a: 4 }) { return x.a; })()); + assertEquals(5, (function(x, y = { a: x }) { return y.a; })(5)); + assertEquals(6, (function(x, y = { a: eval("x") }) { return y.a; })(6)); +})(); + + +// TDZ + +(function testReferencesUninitializedParameter() { + assertThrows(function(a = b, b) {}, ReferenceError); +})(); + + +(function testEvalReferencesUninitializedParameter() { + assertThrows( function(x = { a: y }, y) { return x.a; }, ReferenceError); + assertThrows(function(a = eval("b"), b = 0) { return a; }, ReferenceError); + assertThrows( + function(x = { a: eval("y") }, y) { return x.a; }, ReferenceError); +})(); + + +(function testReferencesInitializedParameter() { + assertEquals(1, (function(a = 1, b = a) { return b; })()); +})(); + + +// Scoping +// +// TODO(caitp): fix scoping --- var declarations in function body can't be +// resolved in formal parameters +// assertThrows(function referencesVariableBodyDeclaration(a = body_var) { +// var body_var = true; +// return a; +// }, ReferenceError); + + +// TODO(caitp): default function length does not include any parameters +// following the first optional parameter +// assertEquals(0, (function(a = 1) {}).length); +// assertEquals(1, (function(a, b = 1) {}).length); +// assertEquals(2, (function(a, b, c = 1) {}).length); +// assertEquals(3, (function(a, b, c, d = 1) {}).length); +// assertEquals(1, (function(a, b = 1, c, d = 1) {}).length); + + +(function testInitializerReferencesThis() { + var O = {}; + function fn(x = this) { return x; } + assertEquals(O, fn.call(O)); + + function fn2(x = () => this) { return x(); } + assertEquals(O, fn2.call(O)); +})(); + + +(function testInitializerReferencesSelf() { + function fn(x, y = fn) { return x ? y(false) + 1 : 0 } + assertEquals(1, fn(true)); +})(); + + +(function testInitializerEvalParameter() { + assertEquals(7, (function(x, y = eval("x")) { return y; })(7)); + assertEquals(9, (function(x = () => eval("y"), y = 9) { return x(); })()); +})(); + + +(function testContextAllocatedUsedInBody() { + assertEquals("Monkey!Monkey!Monkey!", (function(x, y = eval("x")) { + return "Mon" + x + "Mon" + eval("y") + "Mon" + y; + })("key!")); + assertEquals("Monkey!", (function(x = "Mon", y = "key!") { + return eval("x") + eval("y"); + })()); +})(); + + +(function testContextAllocatedEscapesFunction() { + assertEquals("Monkey!", (function(x = "Monkey!") { + return function() { + return x; + }; + })()()); +})(); -- 2.34.1