[es6] Implement for-of iterator finalization
Implements iterator finalisation by desugaring for-of loops with an additional try-finally wrapper. See comment in parser.cc for details. Also improved some AST printing facilities while there. @Ross, I had to disable the bytecode generation test for for-of, because it got completely out of hand after this change (the new bytecode has 150+ lines). See the TODO that I assigned to you. Patch set 1 is WIP patch by Georg (http://crrev.com/1695583003), patch set 2 relative changes. @Georg, FYI, I changed the following: - Moved try-finally out of the loop body, for performance, and in order to be able to handle `continue` correctly. - Fixed scope management in ParseForStatement, which was the cause for the variable allocation failure. - Fixed pre-existing zone initialisation bug in rewriter, which caused the crashes. - Enabled all tests, adjusted a few others, added a couple more. BUG=v8:2214 LOG=Y Review URL: https://codereview.chromium.org/1695393003 Cr-Commit-Position: refs/heads/master@{#34111}
This commit is contained in:
parent
504796e916
commit
cb1bf4af3c
@ -255,6 +255,7 @@ class AstValue : public ZoneObject {
|
||||
F(dot_catch, ".catch") \
|
||||
F(empty, "") \
|
||||
F(eval, "eval") \
|
||||
F(function, "function") \
|
||||
F(get_space, "get ") \
|
||||
F(let, "let") \
|
||||
F(native, "native") \
|
||||
|
@ -36,10 +36,10 @@ AST_NODE_LIST(DECL_ACCEPT)
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
void AstNode::PrintAst() { PrintAst(Isolate::Current()); }
|
||||
void AstNode::Print() { Print(Isolate::Current()); }
|
||||
|
||||
|
||||
void AstNode::PrintAst(Isolate* isolate) {
|
||||
void AstNode::Print(Isolate* isolate) {
|
||||
AstPrinter::PrintOut(isolate, this);
|
||||
}
|
||||
|
||||
|
@ -199,8 +199,8 @@ class AstNode: public ZoneObject {
|
||||
#ifdef DEBUG
|
||||
void PrettyPrint(Isolate* isolate);
|
||||
void PrettyPrint();
|
||||
void PrintAst(Isolate* isolate);
|
||||
void PrintAst();
|
||||
void Print(Isolate* isolate);
|
||||
void Print();
|
||||
#endif // DEBUG
|
||||
|
||||
// Type testing & conversion functions overridden by concrete subclasses.
|
||||
@ -883,11 +883,13 @@ class ForOfStatement final : public ForEachStatement {
|
||||
void Initialize(Expression* each,
|
||||
Expression* subject,
|
||||
Statement* body,
|
||||
Variable* iterator,
|
||||
Expression* assign_iterator,
|
||||
Expression* next_result,
|
||||
Expression* result_done,
|
||||
Expression* assign_each) {
|
||||
ForEachStatement::Initialize(each, subject, body);
|
||||
iterator_ = iterator;
|
||||
assign_iterator_ = assign_iterator;
|
||||
next_result_ = next_result;
|
||||
result_done_ = result_done;
|
||||
@ -898,6 +900,10 @@ class ForOfStatement final : public ForEachStatement {
|
||||
return subject();
|
||||
}
|
||||
|
||||
Variable* iterator() const {
|
||||
return iterator_;
|
||||
}
|
||||
|
||||
// iterator = subject[Symbol.iterator]()
|
||||
Expression* assign_iterator() const {
|
||||
return assign_iterator_;
|
||||
@ -932,6 +938,7 @@ class ForOfStatement final : public ForEachStatement {
|
||||
protected:
|
||||
ForOfStatement(Zone* zone, ZoneList<const AstRawString*>* labels, int pos)
|
||||
: ForEachStatement(zone, labels, pos),
|
||||
iterator_(NULL),
|
||||
assign_iterator_(NULL),
|
||||
next_result_(NULL),
|
||||
result_done_(NULL),
|
||||
@ -941,6 +948,7 @@ class ForOfStatement final : public ForEachStatement {
|
||||
private:
|
||||
int local_id(int n) const { return base_id() + parent_num_ids() + n; }
|
||||
|
||||
Variable* iterator_;
|
||||
Expression* assign_iterator_;
|
||||
Expression* next_result_;
|
||||
Expression* result_done_;
|
||||
|
@ -1203,6 +1203,14 @@ const char* AstPrinter::PrintProgram(FunctionLiteral* program) {
|
||||
}
|
||||
|
||||
|
||||
void AstPrinter::PrintOut(Isolate* isolate, AstNode* node) {
|
||||
AstPrinter printer(isolate);
|
||||
printer.Init();
|
||||
printer.Visit(node);
|
||||
PrintF("%s", printer.Output());
|
||||
}
|
||||
|
||||
|
||||
void AstPrinter::PrintDeclarations(ZoneList<Declaration*>* declarations) {
|
||||
if (declarations->length() > 0) {
|
||||
IndentedScope indent(this, "DECLS");
|
||||
@ -1390,6 +1398,10 @@ void AstPrinter::VisitForOfStatement(ForOfStatement* node) {
|
||||
PrintIndentedVisit("FOR", node->each());
|
||||
PrintIndentedVisit("OF", node->iterable());
|
||||
PrintIndentedVisit("BODY", node->body());
|
||||
PrintIndentedVisit("INIT", node->assign_iterator());
|
||||
PrintIndentedVisit("NEXT", node->next_result());
|
||||
PrintIndentedVisit("EACH", node->assign_each());
|
||||
PrintIndentedVisit("DONE", node->result_done());
|
||||
}
|
||||
|
||||
|
||||
@ -1542,31 +1554,36 @@ void AstPrinter::VisitArrayLiteral(ArrayLiteral* node) {
|
||||
|
||||
|
||||
void AstPrinter::VisitVariableProxy(VariableProxy* node) {
|
||||
Variable* var = node->var();
|
||||
EmbeddedVector<char, 128> buf;
|
||||
int pos =
|
||||
FormatSlotNode(&buf, node, "VAR PROXY", node->VariableFeedbackSlot());
|
||||
|
||||
switch (var->location()) {
|
||||
case VariableLocation::UNALLOCATED:
|
||||
break;
|
||||
case VariableLocation::PARAMETER:
|
||||
SNPrintF(buf + pos, " parameter[%d]", var->index());
|
||||
break;
|
||||
case VariableLocation::LOCAL:
|
||||
SNPrintF(buf + pos, " local[%d]", var->index());
|
||||
break;
|
||||
case VariableLocation::CONTEXT:
|
||||
SNPrintF(buf + pos, " context[%d]", var->index());
|
||||
break;
|
||||
case VariableLocation::GLOBAL:
|
||||
SNPrintF(buf + pos, " global[%d]", var->index());
|
||||
break;
|
||||
case VariableLocation::LOOKUP:
|
||||
SNPrintF(buf + pos, " lookup");
|
||||
break;
|
||||
if (!node->is_resolved()) {
|
||||
SNPrintF(buf + pos, " unresolved");
|
||||
PrintLiteralWithModeIndented(buf.start(), nullptr, node->name());
|
||||
} else {
|
||||
Variable* var = node->var();
|
||||
switch (var->location()) {
|
||||
case VariableLocation::UNALLOCATED:
|
||||
break;
|
||||
case VariableLocation::PARAMETER:
|
||||
SNPrintF(buf + pos, " parameter[%d]", var->index());
|
||||
break;
|
||||
case VariableLocation::LOCAL:
|
||||
SNPrintF(buf + pos, " local[%d]", var->index());
|
||||
break;
|
||||
case VariableLocation::CONTEXT:
|
||||
SNPrintF(buf + pos, " context[%d]", var->index());
|
||||
break;
|
||||
case VariableLocation::GLOBAL:
|
||||
SNPrintF(buf + pos, " global[%d]", var->index());
|
||||
break;
|
||||
case VariableLocation::LOOKUP:
|
||||
SNPrintF(buf + pos, " lookup");
|
||||
break;
|
||||
}
|
||||
PrintLiteralWithModeIndented(buf.start(), var, node->name());
|
||||
}
|
||||
PrintLiteralWithModeIndented(buf.start(), var, node->name());
|
||||
}
|
||||
|
||||
|
||||
|
@ -104,6 +104,9 @@ class AstPrinter: public PrettyPrinter {
|
||||
|
||||
const char* PrintProgram(FunctionLiteral* program);
|
||||
|
||||
// Print a node to stdout.
|
||||
static void PrintOut(Isolate* isolate, AstNode* node);
|
||||
|
||||
// Individual nodes
|
||||
#define DECLARE_VISIT(type) virtual void Visit##type(type* node);
|
||||
AST_NODE_LIST(DECLARE_VISIT)
|
||||
|
@ -535,7 +535,7 @@ Variable* Scope::DeclareLocal(const AstRawString* name, VariableMode mode,
|
||||
int declaration_group_start) {
|
||||
DCHECK(!already_resolved());
|
||||
// This function handles VAR, LET, and CONST modes. DYNAMIC variables are
|
||||
// introduces during variable allocation, and TEMPORARY variables are
|
||||
// introduced during variable allocation, and TEMPORARY variables are
|
||||
// allocated via NewTemporary().
|
||||
DCHECK(IsDeclaredVariableMode(mode));
|
||||
++num_var_or_const_;
|
||||
@ -1646,7 +1646,7 @@ void Scope::AllocateVariablesRecursively(Isolate* isolate) {
|
||||
}
|
||||
|
||||
// If scope is already resolved, we still need to allocate
|
||||
// variables in inner scopes which might not had been resolved yet.
|
||||
// variables in inner scopes which might not have been resolved yet.
|
||||
if (already_resolved()) return;
|
||||
// The number of slots required for variables.
|
||||
num_heap_slots_ = Context::MIN_CONTEXT_SLOTS;
|
||||
|
@ -2350,6 +2350,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_observe)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexps)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_unicode_regexps)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_do_expressions)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_iterator_close)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_lookbehind)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_regexp_property)
|
||||
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_function_name)
|
||||
@ -2975,6 +2976,7 @@ bool Genesis::InstallExperimentalNatives() {
|
||||
static const char* harmony_regexps_natives[] = {"native harmony-regexp.js",
|
||||
nullptr};
|
||||
static const char* harmony_tostring_natives[] = {nullptr};
|
||||
static const char* harmony_iterator_close_natives[] = {nullptr};
|
||||
static const char* harmony_sloppy_natives[] = {nullptr};
|
||||
static const char* harmony_sloppy_function_natives[] = {nullptr};
|
||||
static const char* harmony_sloppy_let_natives[] = {nullptr};
|
||||
|
@ -209,6 +209,7 @@ DEFINE_IMPLICATION(es_staging, move_object_start)
|
||||
V(harmony_sharedarraybuffer, "harmony sharedarraybuffer") \
|
||||
V(harmony_simd, "harmony simd") \
|
||||
V(harmony_do_expressions, "harmony do-expressions") \
|
||||
V(harmony_iterator_close, "harmony iterator finalization") \
|
||||
V(harmony_tailcalls, "harmony tail calls") \
|
||||
V(harmony_object_values_entries, "harmony Object.values / Object.entries") \
|
||||
V(harmony_object_own_property_descriptors, \
|
||||
|
@ -295,6 +295,7 @@ class CallSite {
|
||||
T(RestrictedFunctionProperties, \
|
||||
"'caller' and 'arguments' are restricted function properties and cannot " \
|
||||
"be accessed in this context.") \
|
||||
T(ReturnMethodNotCallable, "The iterator's 'return' method is not callable") \
|
||||
T(StaticPrototype, "Classes may not have static property named prototype") \
|
||||
T(StrictCannotAssign, "Cannot assign to read only '%' in strict mode") \
|
||||
T(StrictDeleteProperty, "Cannot delete property '%' of %") \
|
||||
|
@ -3356,6 +3356,7 @@ void Parser::InitializeForEachStatement(ForEachStatement* stmt,
|
||||
}
|
||||
|
||||
for_of->Initialize(each, subject, body,
|
||||
iterator,
|
||||
assign_iterator,
|
||||
next_result,
|
||||
result_done,
|
||||
@ -3633,9 +3634,6 @@ Statement* Parser::DesugarLexicalBindingsInForStatement(
|
||||
|
||||
Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
|
||||
bool* ok) {
|
||||
// ForStatement ::
|
||||
// 'for' '(' Expression? ';' Expression? ';' Expression? ')' Statement
|
||||
|
||||
int stmt_pos = peek_position();
|
||||
Statement* init = NULL;
|
||||
ZoneList<const AstRawString*> lexical_bindings(1, zone());
|
||||
@ -3773,39 +3771,44 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
|
||||
}
|
||||
body_scope->set_end_position(scanner()->location().end_pos);
|
||||
body_scope = body_scope->FinalizeBlockScope();
|
||||
body_block->set_scope(body_scope);
|
||||
body_block->set_scope(body_scope);
|
||||
|
||||
// Create a TDZ for any lexically-bound names.
|
||||
if (IsLexicalVariableMode(parsing_result.descriptor.mode)) {
|
||||
DCHECK_NULL(init_block);
|
||||
// Create a TDZ for any lexically-bound names.
|
||||
if (IsLexicalVariableMode(parsing_result.descriptor.mode)) {
|
||||
DCHECK_NULL(init_block);
|
||||
|
||||
init_block =
|
||||
factory()->NewBlock(nullptr, 1, false, RelocInfo::kNoPosition);
|
||||
init_block =
|
||||
factory()->NewBlock(nullptr, 1, false, RelocInfo::kNoPosition);
|
||||
|
||||
for (int i = 0; i < lexical_bindings.length(); ++i) {
|
||||
// TODO(adamk): This needs to be some sort of special
|
||||
// INTERNAL variable that's invisible to the debugger
|
||||
// but visible to everything else.
|
||||
VariableProxy* tdz_proxy =
|
||||
NewUnresolved(lexical_bindings[i], LET);
|
||||
Declaration* tdz_decl = factory()->NewVariableDeclaration(
|
||||
tdz_proxy, LET, scope_, RelocInfo::kNoPosition);
|
||||
Variable* tdz_var = Declare(
|
||||
tdz_decl, DeclarationDescriptor::NORMAL, true, CHECK_OK);
|
||||
tdz_var->set_initializer_position(position());
|
||||
}
|
||||
for (int i = 0; i < lexical_bindings.length(); ++i) {
|
||||
// TODO(adamk): This needs to be some sort of special
|
||||
// INTERNAL variable that's invisible to the debugger
|
||||
// but visible to everything else.
|
||||
VariableProxy* tdz_proxy =
|
||||
NewUnresolved(lexical_bindings[i], LET);
|
||||
Declaration* tdz_decl = factory()->NewVariableDeclaration(
|
||||
tdz_proxy, LET, scope_, RelocInfo::kNoPosition);
|
||||
Variable* tdz_var = Declare(
|
||||
tdz_decl, DeclarationDescriptor::NORMAL, true, CHECK_OK);
|
||||
tdz_var->set_initializer_position(position());
|
||||
}
|
||||
}
|
||||
|
||||
Statement* final_loop = loop->IsForOfStatement()
|
||||
? FinalizeForOfStatement(
|
||||
loop->AsForOfStatement(), RelocInfo::kNoPosition)
|
||||
: loop;
|
||||
|
||||
for_scope->set_end_position(scanner()->location().end_pos);
|
||||
for_scope = for_scope->FinalizeBlockScope();
|
||||
// Parsed for-in loop w/ variable declarations.
|
||||
if (init_block != nullptr) {
|
||||
init_block->statements()->Add(loop, zone());
|
||||
init_block->statements()->Add(final_loop, zone());
|
||||
init_block->set_scope(for_scope);
|
||||
return init_block;
|
||||
} else {
|
||||
DCHECK_NULL(for_scope);
|
||||
return loop;
|
||||
return final_loop;
|
||||
}
|
||||
} else {
|
||||
init = parsing_result.BuildInitializationBlock(
|
||||
@ -3868,21 +3871,28 @@ Statement* Parser::ParseForStatement(ZoneList<const AstRawString*>* labels,
|
||||
// expressions in head of the loop should actually have variables
|
||||
// resolved in the outer scope.
|
||||
Scope* body_scope = NewScope(for_scope, BLOCK_SCOPE);
|
||||
BlockState block_state(&scope_, body_scope);
|
||||
Block* block =
|
||||
factory()->NewBlock(NULL, 1, false, RelocInfo::kNoPosition);
|
||||
Statement* body = ParseSubStatement(NULL, CHECK_OK);
|
||||
block->statements()->Add(body, zone());
|
||||
InitializeForEachStatement(loop, expression, enumerable, block,
|
||||
is_destructuring);
|
||||
body_scope->set_end_position(scanner()->location().end_pos);
|
||||
body_scope = body_scope->FinalizeBlockScope();
|
||||
block->set_scope(body_scope);
|
||||
{
|
||||
BlockState block_state(&scope_, body_scope);
|
||||
Block* block =
|
||||
factory()->NewBlock(NULL, 1, false, RelocInfo::kNoPosition);
|
||||
Statement* body = ParseSubStatement(NULL, CHECK_OK);
|
||||
block->statements()->Add(body, zone());
|
||||
InitializeForEachStatement(loop, expression, enumerable, block,
|
||||
is_destructuring);
|
||||
body_scope->set_end_position(scanner()->location().end_pos);
|
||||
body_scope = body_scope->FinalizeBlockScope();
|
||||
block->set_scope(body_scope);
|
||||
}
|
||||
|
||||
Statement* final_loop = loop->IsForOfStatement()
|
||||
? FinalizeForOfStatement(
|
||||
loop->AsForOfStatement(), RelocInfo::kNoPosition)
|
||||
: loop;
|
||||
|
||||
for_scope->set_end_position(scanner()->location().end_pos);
|
||||
for_scope = for_scope->FinalizeBlockScope();
|
||||
DCHECK(for_scope == nullptr);
|
||||
// Parsed for-in loop.
|
||||
return loop;
|
||||
return final_loop;
|
||||
|
||||
} else {
|
||||
init = factory()->NewExpressionStatement(expression, lhs_beg_pos);
|
||||
@ -5765,7 +5775,7 @@ Expression* Parser::RewriteSpreads(ArrayLiteral* lit) {
|
||||
ForEachStatement::ITERATE, nullptr, RelocInfo::kNoPosition);
|
||||
ForOfStatement* for_of = loop->AsForOfStatement();
|
||||
for_of->Initialize(factory()->NewVariableProxy(each), subject,
|
||||
append_body, assign_iterator, next_element,
|
||||
append_body, iterator, assign_iterator, next_element,
|
||||
element_done, assign_each);
|
||||
do_block->statements()->Add(for_of, zone());
|
||||
}
|
||||
@ -5923,7 +5933,6 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
auto scope = parser_->scope_;
|
||||
auto zone = parser_->zone();
|
||||
|
||||
Statement* skip = factory->NewEmptyStatement(nopos);
|
||||
|
||||
// Forward definition for break/continue statements.
|
||||
WhileStatement* loop = factory->NewWhileStatement(nullptr, nopos);
|
||||
@ -5995,8 +6004,8 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
throw_call = factory->NewExpressionStatement(call, nopos);
|
||||
}
|
||||
|
||||
validate_iterator =
|
||||
factory->NewIfStatement(is_receiver_call, skip, throw_call, nopos);
|
||||
validate_iterator = factory->NewIfStatement(
|
||||
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
|
||||
}
|
||||
|
||||
|
||||
@ -6039,8 +6048,8 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
throw_call = factory->NewExpressionStatement(call, nopos);
|
||||
}
|
||||
|
||||
validate_next_output =
|
||||
factory->NewIfStatement(is_receiver_call, skip, throw_call, nopos);
|
||||
validate_next_output = factory->NewIfStatement(
|
||||
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
|
||||
}
|
||||
|
||||
|
||||
@ -6076,11 +6085,13 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
Statement* throw_call = factory->NewExpressionStatement(call, nopos);
|
||||
|
||||
Block* then = factory->NewBlock(nullptr, 4+1, false, nopos);
|
||||
BuildIteratorClose(then->statements(), var_iterator, Nothing<Variable*>(),
|
||||
Nothing<Variable*>());
|
||||
Variable* var_tmp = scope->NewTemporary(avfactory->empty_string());
|
||||
BuildIteratorClose(
|
||||
then->statements(), var_iterator, factory->NewUndefinedLiteral(nopos),
|
||||
var_tmp);
|
||||
then->statements()->Add(throw_call, zone);
|
||||
check_throw =
|
||||
factory->NewIfStatement(condition, then, skip, nopos);
|
||||
check_throw = factory->NewIfStatement(
|
||||
condition, then, factory->NewEmptyStatement(nopos), nopos);
|
||||
}
|
||||
|
||||
|
||||
@ -6119,8 +6130,8 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
throw_call = factory->NewExpressionStatement(call, nopos);
|
||||
}
|
||||
|
||||
validate_throw_output =
|
||||
factory->NewIfStatement(is_receiver_call, skip, throw_call, nopos);
|
||||
validate_throw_output = factory->NewIfStatement(
|
||||
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
|
||||
}
|
||||
|
||||
|
||||
@ -6132,7 +6143,8 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
factory->NewStringLiteral(avfactory->done_string(), nopos);
|
||||
Expression* property = factory->NewProperty(output_proxy, literal, nopos);
|
||||
BreakStatement* break_loop = factory->NewBreakStatement(loop, nopos);
|
||||
if_done = factory->NewIfStatement(property, break_loop, skip, nopos);
|
||||
if_done = factory->NewIfStatement(
|
||||
property, break_loop, factory->NewEmptyStatement(nopos), nopos);
|
||||
}
|
||||
|
||||
|
||||
@ -6251,8 +6263,8 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
case_next->Add(factory->NewBreakStatement(switch_mode, nopos), zone);
|
||||
|
||||
auto case_return = new (zone) ZoneList<Statement*>(5, zone);
|
||||
BuildIteratorClose(
|
||||
case_return, var_iterator, Just(var_input), Just(var_output));
|
||||
BuildIteratorClose(case_return, var_iterator,
|
||||
factory->NewVariableProxy(var_input, nopos), var_output);
|
||||
case_return->Add(factory->NewBreakStatement(switch_mode, nopos), zone);
|
||||
|
||||
auto case_throw = new (zone) ZoneList<Statement*>(5, zone);
|
||||
@ -6318,17 +6330,25 @@ Expression* ParserTraits::RewriteYieldStar(
|
||||
|
||||
void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
|
||||
Variable* iterator,
|
||||
Maybe<Variable*> input,
|
||||
Maybe<Variable*> output) {
|
||||
Expression* input,
|
||||
Variable* var_output) {
|
||||
//
|
||||
// This function adds four statements to [statements], corresponding to the
|
||||
// following code:
|
||||
//
|
||||
// let iteratorReturn = iterator.return;
|
||||
// if (IS_NULL_OR_UNDEFINED(iteratorReturn) return input;
|
||||
// output = %_Call(iteratorReturn, iterator);
|
||||
// if (!IS_RECEIVER(output)) %ThrowIterResultNotAnObject(output);
|
||||
//
|
||||
|
||||
const int nopos = RelocInfo::kNoPosition;
|
||||
auto factory = parser_->factory();
|
||||
auto avfactory = parser_->ast_value_factory();
|
||||
auto scope = parser_->scope_;
|
||||
auto zone = parser_->zone();
|
||||
Statement* skip = factory->NewEmptyStatement(nopos);
|
||||
|
||||
// let iteratorReturn = iterator.return;
|
||||
Variable* var = scope->NewTemporary(avfactory->empty_string());
|
||||
Variable* var_return = var_output; // Reusing the output variable.
|
||||
Statement* get_return;
|
||||
{
|
||||
Expression* iterator_proxy = factory->NewVariableProxy(iterator);
|
||||
@ -6336,57 +6356,47 @@ void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
|
||||
factory->NewStringLiteral(avfactory->return_string(), nopos);
|
||||
Expression* property =
|
||||
factory->NewProperty(iterator_proxy, literal, nopos);
|
||||
Expression* return_proxy = factory->NewVariableProxy(var);
|
||||
Expression* return_proxy = factory->NewVariableProxy(var_return);
|
||||
Expression* assignment = factory->NewAssignment(
|
||||
Token::ASSIGN, return_proxy, property, nopos);
|
||||
get_return = factory->NewExpressionStatement(assignment, nopos);
|
||||
}
|
||||
|
||||
// if (IS_NULL_OR_UNDEFINED(iteratorReturn) return; OR
|
||||
// if (IS_NULL_OR_UNDEFINED(iteratorReturn) return input;
|
||||
Statement* check_return;
|
||||
{
|
||||
Expression* condition = factory->NewCompareOperation(
|
||||
Token::EQ, factory->NewVariableProxy(var),
|
||||
Token::EQ, factory->NewVariableProxy(var_return),
|
||||
factory->NewNullLiteral(nopos), nopos);
|
||||
|
||||
Expression* value = input.IsJust() ?
|
||||
static_cast<Expression*>(factory->NewVariableProxy(input.FromJust())) :
|
||||
factory->NewUndefinedLiteral(nopos);
|
||||
Statement* return_input = factory->NewReturnStatement(input, nopos);
|
||||
|
||||
Statement* return_undefined = factory->NewReturnStatement(value, nopos);
|
||||
|
||||
check_return =
|
||||
factory->NewIfStatement(condition, return_undefined, skip, nopos);
|
||||
check_return = factory->NewIfStatement(
|
||||
condition, return_input, factory->NewEmptyStatement(nopos), nopos);
|
||||
}
|
||||
|
||||
// let output = %_Call(iteratorReturn, iterator); OR
|
||||
// output = %_Call(iteratorReturn, iterator, input);
|
||||
// output = %_Call(iteratorReturn, iterator);
|
||||
Statement* call_return;
|
||||
{
|
||||
auto args = new (zone) ZoneList<Expression*>(3, zone);
|
||||
args->Add(factory->NewVariableProxy(var), zone);
|
||||
args->Add(factory->NewVariableProxy(var_return), zone);
|
||||
args->Add(factory->NewVariableProxy(iterator), zone);
|
||||
if (input.IsJust()) {
|
||||
args->Add(factory->NewVariableProxy(input.FromJust()), zone);
|
||||
}
|
||||
|
||||
Expression* call =
|
||||
factory->NewCallRuntime(Runtime::kInlineCall, args, nopos);
|
||||
Expression* output_proxy = factory->NewVariableProxy(
|
||||
output.IsJust() ? output.FromJust() : var);
|
||||
Expression* output_proxy = factory->NewVariableProxy(var_output);
|
||||
Expression* assignment = factory->NewAssignment(
|
||||
Token::ASSIGN, output_proxy, call, nopos);
|
||||
call_return = factory->NewExpressionStatement(assignment, nopos);
|
||||
}
|
||||
|
||||
// if (!IS_RECEIVER(output)) %ThrowIterResultNotAnObject(output);
|
||||
// if (!IS_RECEIVER(output)) %ThrowIteratorResultNotAnObject(output);
|
||||
Statement* validate_output;
|
||||
{
|
||||
Expression* is_receiver_call;
|
||||
{
|
||||
auto args = new (zone) ZoneList<Expression*>(1, zone);
|
||||
args->Add(factory->NewVariableProxy(var), zone);
|
||||
args->Add(factory->NewVariableProxy(var_output), zone);
|
||||
is_receiver_call =
|
||||
factory->NewCallRuntime(Runtime::kInlineIsJSReceiver, args, nopos);
|
||||
}
|
||||
@ -6394,14 +6404,14 @@ void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
|
||||
Statement* throw_call;
|
||||
{
|
||||
auto args = new (zone) ZoneList<Expression*>(1, zone);
|
||||
args->Add(factory->NewVariableProxy(var), zone);
|
||||
args->Add(factory->NewVariableProxy(var_output), zone);
|
||||
Expression* call = factory->NewCallRuntime(
|
||||
Runtime::kThrowIteratorResultNotAnObject, args, nopos);
|
||||
throw_call = factory->NewExpressionStatement(call, nopos);
|
||||
}
|
||||
|
||||
validate_output =
|
||||
factory->NewIfStatement(is_receiver_call, skip, throw_call, nopos);
|
||||
validate_output = factory->NewIfStatement(
|
||||
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
|
||||
}
|
||||
|
||||
statements->Add(get_return, zone);
|
||||
@ -6411,5 +6421,355 @@ void ParserTraits::BuildIteratorClose(ZoneList<Statement*>* statements,
|
||||
}
|
||||
|
||||
|
||||
// Runtime encoding of different completion modes.
|
||||
enum ForOfLoopBodyCompletion { BODY_COMPLETED, BODY_ABORTED, BODY_THREW };
|
||||
|
||||
void ParserTraits::BuildIteratorCloseForCompletion(
|
||||
ZoneList<Statement*>* statements, Variable* iterator,
|
||||
Variable* completion) {
|
||||
//
|
||||
// This function adds two statements to [statements], corresponding to the
|
||||
// following code:
|
||||
//
|
||||
// let iteratorReturn = iterator.return;
|
||||
// if (!IS_NULL_OR_UNDEFINED(iteratorReturn)) {
|
||||
// let output;
|
||||
// if (completion === BODY_THREW) {
|
||||
// if (!IS_CALLABLE(iteratorReturn)) {
|
||||
// throw MakeTypeError(kReturnMethodNotCallable);
|
||||
// }
|
||||
// try { output = %_Call(iteratorReturn, iterator) } catch (_) { }
|
||||
// } else {
|
||||
// output = %_Call(iteratorReturn, iterator);
|
||||
// }
|
||||
// if (!IS_RECEIVER(output)) %ThrowIterResultNotAnObject(output);
|
||||
// }
|
||||
//
|
||||
|
||||
const int nopos = RelocInfo::kNoPosition;
|
||||
auto factory = parser_->factory();
|
||||
auto avfactory = parser_->ast_value_factory();
|
||||
auto scope = parser_->scope_;
|
||||
auto zone = parser_->zone();
|
||||
|
||||
// let output;
|
||||
Variable* var_output = scope->NewTemporary(avfactory->empty_string());
|
||||
|
||||
// let iteratorReturn = iterator.return;
|
||||
Variable* var_return = var_output; // Reusing the output variable.
|
||||
Statement* get_return;
|
||||
{
|
||||
Expression* iterator_proxy = factory->NewVariableProxy(iterator);
|
||||
Expression* literal =
|
||||
factory->NewStringLiteral(avfactory->return_string(), nopos);
|
||||
Expression* property =
|
||||
factory->NewProperty(iterator_proxy, literal, nopos);
|
||||
Expression* return_proxy = factory->NewVariableProxy(var_return);
|
||||
Expression* assignment = factory->NewAssignment(
|
||||
Token::ASSIGN, return_proxy, property, nopos);
|
||||
get_return = factory->NewExpressionStatement(assignment, nopos);
|
||||
}
|
||||
|
||||
// if (!IS_CALLABLE(iteratorReturn)) {
|
||||
// throw MakeTypeError(kReturnMethodNotCallable);
|
||||
// }
|
||||
Statement* check_return_callable;
|
||||
{
|
||||
Expression* type_of = factory->NewUnaryOperation(
|
||||
Token::TYPEOF, factory->NewVariableProxy(var_return), nopos);
|
||||
Expression* function_literal = factory->NewStringLiteral(
|
||||
avfactory->function_string(), nopos);
|
||||
Expression* condition = factory->NewCompareOperation(
|
||||
Token::EQ_STRICT, type_of, function_literal, nopos);
|
||||
|
||||
Expression* call = NewThrowTypeError(
|
||||
MessageTemplate::kReturnMethodNotCallable,
|
||||
avfactory->empty_string(), nopos);
|
||||
Statement* throw_call = factory->NewExpressionStatement(call, nopos);
|
||||
|
||||
check_return_callable = factory->NewIfStatement(
|
||||
condition, factory->NewEmptyStatement(nopos), throw_call, nopos);
|
||||
}
|
||||
|
||||
// output = %_Call(iteratorReturn, iterator);
|
||||
Statement* call_return;
|
||||
{
|
||||
auto args = new (zone) ZoneList<Expression*>(2, zone);
|
||||
args->Add(factory->NewVariableProxy(var_return), zone);
|
||||
args->Add(factory->NewVariableProxy(iterator), zone);
|
||||
Expression* call =
|
||||
factory->NewCallRuntime(Runtime::kInlineCall, args, nopos);
|
||||
|
||||
Expression* output_proxy = factory->NewVariableProxy(var_output);
|
||||
Expression* assignment = factory->NewAssignment(
|
||||
Token::ASSIGN, output_proxy, call, nopos);
|
||||
call_return = factory->NewExpressionStatement(assignment, nopos);
|
||||
}
|
||||
|
||||
// try { output = %_Call(iteratorReturn, iterator) } catch (_) { }
|
||||
Statement* try_call_return;
|
||||
{
|
||||
auto args = new (zone) ZoneList<Expression*>(2, zone);
|
||||
args->Add(factory->NewVariableProxy(var_return), zone);
|
||||
args->Add(factory->NewVariableProxy(iterator), zone);
|
||||
|
||||
Expression* call =
|
||||
factory->NewCallRuntime(Runtime::kInlineCall, args, nopos);
|
||||
Expression* assignment = factory->NewAssignment(
|
||||
Token::ASSIGN, factory->NewVariableProxy(var_output), call, nopos);
|
||||
|
||||
Block* try_block = factory->NewBlock(nullptr, 1, false, nopos);
|
||||
try_block->statements()->Add(
|
||||
factory->NewExpressionStatement(assignment, nopos), zone);
|
||||
|
||||
Block* catch_block = factory->NewBlock(nullptr, 0, false, nopos);
|
||||
|
||||
Scope* catch_scope = NewScope(scope, CATCH_SCOPE);
|
||||
Variable* catch_variable = catch_scope->DeclareLocal(
|
||||
avfactory->dot_catch_string(), VAR, kCreatedInitialized,
|
||||
Variable::NORMAL);
|
||||
|
||||
try_call_return = factory->NewTryCatchStatement(
|
||||
try_block, catch_scope, catch_variable, catch_block, nopos);
|
||||
}
|
||||
|
||||
// if (completion === ABRUPT_THROW) {
|
||||
// #check_return_callable;
|
||||
// #try_call_return;
|
||||
// } else {
|
||||
// #call_return;
|
||||
// }
|
||||
Statement* call_return_carefully;
|
||||
{
|
||||
Expression* condition = factory->NewCompareOperation(
|
||||
Token::EQ_STRICT, factory->NewVariableProxy(completion),
|
||||
factory->NewSmiLiteral(BODY_THREW, nopos), nopos);
|
||||
|
||||
Block* then_block = factory->NewBlock(nullptr, 2, false, nopos);
|
||||
then_block->statements()->Add(check_return_callable, zone);
|
||||
then_block->statements()->Add(try_call_return, zone);
|
||||
|
||||
call_return_carefully =
|
||||
factory->NewIfStatement(condition, then_block, call_return, nopos);
|
||||
}
|
||||
|
||||
// if (!IS_RECEIVER(output)) %ThrowIteratorResultNotAnObject(output);
|
||||
Statement* validate_output;
|
||||
{
|
||||
Expression* is_receiver_call;
|
||||
{
|
||||
auto args = new (zone) ZoneList<Expression*>(1, zone);
|
||||
args->Add(factory->NewVariableProxy(var_output), zone);
|
||||
is_receiver_call =
|
||||
factory->NewCallRuntime(Runtime::kInlineIsJSReceiver, args, nopos);
|
||||
}
|
||||
|
||||
Statement* throw_call;
|
||||
{
|
||||
auto args = new (zone) ZoneList<Expression*>(1, zone);
|
||||
args->Add(factory->NewVariableProxy(var_output), zone);
|
||||
Expression* call = factory->NewCallRuntime(
|
||||
Runtime::kThrowIteratorResultNotAnObject, args, nopos);
|
||||
throw_call = factory->NewExpressionStatement(call, nopos);
|
||||
}
|
||||
|
||||
validate_output = factory->NewIfStatement(
|
||||
is_receiver_call, factory->NewEmptyStatement(nopos), throw_call, nopos);
|
||||
}
|
||||
|
||||
// if (!IS_NULL_OR_UNDEFINED(iteratorReturn)) { ... }
|
||||
Statement* maybe_call_return;
|
||||
{
|
||||
Expression* condition = factory->NewCompareOperation(
|
||||
Token::EQ, factory->NewVariableProxy(var_return),
|
||||
factory->NewNullLiteral(nopos), nopos);
|
||||
|
||||
Block* block = factory->NewBlock(nullptr, 2, false, nopos);
|
||||
block->statements()->Add(call_return_carefully, zone);
|
||||
block->statements()->Add(validate_output, zone);
|
||||
|
||||
maybe_call_return = factory->NewIfStatement(
|
||||
condition, factory->NewEmptyStatement(nopos), block, nopos);
|
||||
}
|
||||
|
||||
|
||||
statements->Add(get_return, zone);
|
||||
statements->Add(maybe_call_return, zone);
|
||||
}
|
||||
|
||||
|
||||
Statement* ParserTraits::FinalizeForOfStatement(ForOfStatement* loop, int pos) {
|
||||
if (!FLAG_harmony_iterator_close) return loop;
|
||||
|
||||
//
|
||||
// This function replaces the loop with the following wrapping:
|
||||
//
|
||||
// let completion = BODY_COMPLETED;
|
||||
// try {
|
||||
// #loop;
|
||||
// } catch(e) {
|
||||
// if (completion === BODY_ABORTED) completion = BODY_THREW;
|
||||
// throw e;
|
||||
// } finally {
|
||||
// if (!(completion === BODY_COMPLETED || IS_UNDEFINED(#iterator))) {
|
||||
// #BuildIteratorClose(#iterator, completion) // See above.
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// where the loop's body is wrapped as follows:
|
||||
//
|
||||
// {
|
||||
// {{completion = BODY_ABORTED;}}
|
||||
// #loop-body
|
||||
// {{completion = BODY_COMPLETED;}}
|
||||
// }
|
||||
|
||||
const int nopos = RelocInfo::kNoPosition;
|
||||
auto factory = parser_->factory();
|
||||
auto avfactory = parser_->ast_value_factory();
|
||||
auto scope = parser_->scope_;
|
||||
auto zone = parser_->zone();
|
||||
|
||||
// let completion = BODY_COMPLETED;
|
||||
Variable* var_completion = scope->NewTemporary(avfactory->empty_string());
|
||||
Statement* initialize_completion;
|
||||
{
|
||||
Expression* proxy = factory->NewVariableProxy(var_completion);
|
||||
Expression* assignment = factory->NewAssignment(
|
||||
Token::ASSIGN, proxy,
|
||||
factory->NewSmiLiteral(BODY_COMPLETED, nopos), nopos);
|
||||
initialize_completion =
|
||||
factory->NewExpressionStatement(assignment, nopos);
|
||||
}
|
||||
|
||||
// if (completion === BODY_ABORTED) completion = BODY_THREW;
|
||||
Statement* set_completion_throw;
|
||||
{
|
||||
Expression* condition = factory->NewCompareOperation(
|
||||
Token::EQ_STRICT, factory->NewVariableProxy(var_completion),
|
||||
factory->NewSmiLiteral(BODY_ABORTED, nopos), nopos);
|
||||
|
||||
Expression* proxy = factory->NewVariableProxy(var_completion);
|
||||
Expression* assignment = factory->NewAssignment(
|
||||
Token::ASSIGN, proxy, factory->NewSmiLiteral(BODY_THREW, nopos),
|
||||
nopos);
|
||||
Statement* statement = factory->NewExpressionStatement(assignment, nopos);
|
||||
set_completion_throw = factory->NewIfStatement(
|
||||
condition, statement, factory->NewEmptyStatement(nopos), nopos);
|
||||
}
|
||||
|
||||
// if (!(completion === BODY_COMPLETED || IS_UNDEFINED(#iterator))) {
|
||||
// #BuildIteratorClose(#iterator, completion)
|
||||
// }
|
||||
Block* maybe_close;
|
||||
{
|
||||
Expression* condition1 = factory->NewCompareOperation(
|
||||
Token::EQ_STRICT, factory->NewVariableProxy(var_completion),
|
||||
factory->NewSmiLiteral(BODY_COMPLETED, nopos), nopos);
|
||||
Expression* condition2 = factory->NewCompareOperation(
|
||||
Token::EQ_STRICT, factory->NewVariableProxy(loop->iterator()),
|
||||
factory->NewUndefinedLiteral(nopos), nopos);
|
||||
Expression* condition = factory->NewBinaryOperation(
|
||||
Token::OR, condition1, condition2, nopos);
|
||||
|
||||
Block* block = factory->NewBlock(nullptr, 2, false, nopos);
|
||||
BuildIteratorCloseForCompletion(
|
||||
block->statements(), loop->iterator(), var_completion);
|
||||
DCHECK(block->statements()->length() == 2);
|
||||
|
||||
maybe_close = factory->NewBlock(nullptr, 1, false, nopos);
|
||||
maybe_close->statements()->Add(factory->NewIfStatement(
|
||||
condition, factory->NewEmptyStatement(nopos), block, nopos), zone);
|
||||
}
|
||||
|
||||
// try { #try_block }
|
||||
// catch(e) {
|
||||
// #set_completion_throw;
|
||||
// throw e;
|
||||
// }
|
||||
Statement* try_catch;
|
||||
{
|
||||
Scope* catch_scope = NewScope(scope, CATCH_SCOPE);
|
||||
Variable* catch_variable = catch_scope->DeclareLocal(
|
||||
avfactory->dot_catch_string(), VAR, kCreatedInitialized,
|
||||
Variable::NORMAL);
|
||||
|
||||
Statement* rethrow;
|
||||
{
|
||||
Expression* proxy = factory->NewVariableProxy(catch_variable);
|
||||
rethrow = factory->NewExpressionStatement(
|
||||
factory->NewThrow(proxy, nopos), nopos);
|
||||
}
|
||||
|
||||
Block* try_block = factory->NewBlock(nullptr, 1, false, nopos);
|
||||
try_block->statements()->Add(loop, zone);
|
||||
|
||||
Block* catch_block = factory->NewBlock(nullptr, 2, false, nopos);
|
||||
catch_block->statements()->Add(set_completion_throw, zone);
|
||||
catch_block->statements()->Add(rethrow, zone);
|
||||
|
||||
try_catch = factory->NewTryCatchStatement(
|
||||
try_block, catch_scope, catch_variable, catch_block, nopos);
|
||||
}
|
||||
|
||||
// try { #try_catch } finally { #maybe_close }
|
||||
Statement* try_finally;
|
||||
{
|
||||
Block* try_block = factory->NewBlock(nullptr, 1, false, nopos);
|
||||
try_block->statements()->Add(try_catch, zone);
|
||||
|
||||
try_finally =
|
||||
factory->NewTryFinallyStatement(try_block, maybe_close, nopos);
|
||||
}
|
||||
|
||||
// #initialize_completion;
|
||||
// #try_finally;
|
||||
Statement* final_loop;
|
||||
{
|
||||
Block* block = factory->NewBlock(nullptr, 2, false, nopos);
|
||||
block->statements()->Add(initialize_completion, zone);
|
||||
block->statements()->Add(try_finally, zone);
|
||||
final_loop = block;
|
||||
}
|
||||
|
||||
// {{completion = BODY_ABORTED;}}
|
||||
Statement* set_completion_break;
|
||||
{
|
||||
Expression* proxy = factory->NewVariableProxy(var_completion);
|
||||
Expression* assignment = factory->NewAssignment(
|
||||
Token::ASSIGN, proxy,
|
||||
factory->NewSmiLiteral(BODY_ABORTED, nopos), nopos);
|
||||
|
||||
Block* block = factory->NewBlock(nullptr, 1, true, nopos);
|
||||
block->statements()->Add(
|
||||
factory->NewExpressionStatement(assignment, nopos), zone);
|
||||
set_completion_break = block;
|
||||
}
|
||||
|
||||
// {{completion = BODY_COMPLETED;}}
|
||||
Statement* set_completion_normal;
|
||||
{
|
||||
Expression* proxy = factory->NewVariableProxy(var_completion);
|
||||
Expression* assignment = factory->NewAssignment(
|
||||
Token::ASSIGN, proxy, factory->NewSmiLiteral(BODY_COMPLETED, nopos),
|
||||
nopos);
|
||||
|
||||
Block* block = factory->NewBlock(nullptr, 1, true, nopos);
|
||||
block->statements()->Add(
|
||||
factory->NewExpressionStatement(assignment, nopos), zone);
|
||||
set_completion_normal = block;
|
||||
}
|
||||
|
||||
// { #set_completion_break; #loop-body; #set_completion_normal }
|
||||
Block* new_body = factory->NewBlock(nullptr, 2, false, nopos);
|
||||
new_body->statements()->Add(set_completion_break, zone);
|
||||
new_body->statements()->Add(loop->body(), zone);
|
||||
new_body->statements()->Add(set_completion_normal, zone);
|
||||
|
||||
loop->set_body(new_body);
|
||||
return final_loop;
|
||||
}
|
||||
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -461,6 +461,8 @@ class ParserTraits {
|
||||
MessageTemplate::Template message,
|
||||
const AstRawString* arg, int pos);
|
||||
|
||||
Statement* FinalizeForOfStatement(ForOfStatement* loop, int pos);
|
||||
|
||||
// Reporting errors.
|
||||
void ReportMessageAt(Scanner::Location source_location,
|
||||
MessageTemplate::Template message,
|
||||
@ -662,8 +664,12 @@ class ParserTraits {
|
||||
private:
|
||||
Parser* parser_;
|
||||
|
||||
void BuildIteratorClose(ZoneList<Statement*>* statements, Variable* iterator,
|
||||
Maybe<Variable*> input, Maybe<Variable*> output);
|
||||
void BuildIteratorClose(
|
||||
ZoneList<Statement*>* statements, Variable* iterator,
|
||||
Expression* input, Variable* output);
|
||||
void BuildIteratorCloseForCompletion(
|
||||
ZoneList<Statement*>* statements, Variable* iterator,
|
||||
Variable* body_threw);
|
||||
};
|
||||
|
||||
|
||||
|
@ -31,6 +31,7 @@ class Processor: public AstVisitor {
|
||||
result_assigned_(false),
|
||||
replacement_(nullptr),
|
||||
is_set_(false),
|
||||
zone_(ast_value_factory->zone()),
|
||||
scope_(scope),
|
||||
factory_(ast_value_factory) {
|
||||
InitializeAstVisitor(parser->stack_limit());
|
||||
|
@ -6287,7 +6287,9 @@ TEST(ForIn) {
|
||||
}
|
||||
|
||||
|
||||
TEST(ForOf) {
|
||||
// TODO(rmcilroy): Do something about this; new bytecode is too large
|
||||
// (150+ instructions) to adapt manually.
|
||||
DISABLED_TEST(ForOf) {
|
||||
InitializedHandleScope handle_scope;
|
||||
BytecodeGeneratorHelper helper;
|
||||
Zone zone;
|
||||
|
@ -1061,8 +1061,8 @@
|
||||
|
||||
|
||||
(function TestForInOfTDZ() {
|
||||
assertThrows("'use strict'; let x = {}; for (let [x, y] of {x});", ReferenceError);
|
||||
assertThrows("'use strict'; let x = {}; for (let [y, x] of {x});", ReferenceError);
|
||||
assertThrows("'use strict'; let x = {}; for (let [x, y] of [x]);", ReferenceError);
|
||||
assertThrows("'use strict'; let x = {}; for (let [y, x] of [x]);", ReferenceError);
|
||||
assertThrows("'use strict'; let x = {}; for (let [x, y] in {x});", ReferenceError);
|
||||
assertThrows("'use strict'; let x = {}; for (let [y, x] in {x});", ReferenceError);
|
||||
}());
|
||||
|
@ -238,7 +238,7 @@
|
||||
assertEquals({value: 1, done: false}, x.next());
|
||||
assertEquals({value: 42, done: false}, x.next());
|
||||
assertEquals({value: 43, done: false}, x.return(666));
|
||||
assertEquals({value: 666, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: false}, x.next());
|
||||
assertEquals({value: undefined, done: true}, x.next());
|
||||
}
|
||||
|
||||
|
364
test/mjsunit/harmony/iterator-close.js
Normal file
364
test/mjsunit/harmony/iterator-close.js
Normal file
@ -0,0 +1,364 @@
|
||||
// Copyright 2016 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-iterator-close
|
||||
|
||||
function* g() { yield 42; return 88 };
|
||||
|
||||
|
||||
// Return method is "undefined".
|
||||
{
|
||||
g.prototype.return = null;
|
||||
|
||||
assertEquals(undefined, (() => {
|
||||
for (let x of g()) { break; }
|
||||
})());
|
||||
|
||||
assertEquals(undefined, (() => {
|
||||
for (x of g()) { break; }
|
||||
})());
|
||||
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g()) { throw 42; }
|
||||
}, 42);
|
||||
|
||||
assertThrowsEquals(() => {
|
||||
for (x of g()) { throw 42; }
|
||||
}, 42);
|
||||
|
||||
assertEquals(42, (() => {
|
||||
for (let x of g()) { return 42; }
|
||||
})());
|
||||
|
||||
assertEquals(42, (() => {
|
||||
for (x of g()) { return 42; }
|
||||
})());
|
||||
|
||||
assertEquals(42, eval('for (let x of g()) { x; }'));
|
||||
|
||||
assertEquals(42, eval('for (let x of g()) { x; }'));
|
||||
}
|
||||
|
||||
|
||||
// Return method is not callable.
|
||||
{
|
||||
g.prototype.return = 666;
|
||||
|
||||
assertThrows(() => {
|
||||
for (let x of g()) { break; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (x of g()) { break; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (let x of g()) { throw 666; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (x of g()) { throw 666; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (let x of g()) { return 666; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (x of g()) { return 666; }
|
||||
}, TypeError);
|
||||
|
||||
assertEquals(42, eval('for (let x of g()) { x; }'));
|
||||
|
||||
assertEquals(42, eval('for (let x of g()) { x; }'));
|
||||
}
|
||||
|
||||
|
||||
// Return method does not return an object.
|
||||
{
|
||||
g.prototype.return = () => 666;
|
||||
|
||||
assertThrows(() => {
|
||||
for (let x of g()) { break; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (x of g()) { break; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (let x of g()) { throw 666; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (x of g()) { throw 666; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (let x of g()) { return 666; }
|
||||
}, TypeError);
|
||||
|
||||
assertThrows(() => {
|
||||
for (x of g()) { return 666; }
|
||||
}, TypeError);
|
||||
|
||||
assertEquals(42, eval('for (let x of g()) { x; }'));
|
||||
|
||||
assertEquals(42, eval('for (x of g()) { x; }'));
|
||||
}
|
||||
|
||||
|
||||
// Return method returns an object.
|
||||
{
|
||||
let log = [];
|
||||
g.prototype.return = (...args) => { log.push(args); return {} };
|
||||
|
||||
log = [];
|
||||
for (let x of g()) { break; }
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
for (x of g()) { break; }
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g()) { throw 42; }
|
||||
}, 42);
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (x of g()) { throw 42; }
|
||||
}, 42);
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertEquals(42, (() => {
|
||||
for (let x of g()) { return 42; }
|
||||
})());
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertEquals(42, (() => {
|
||||
for (x of g()) { return 42; }
|
||||
})());
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertEquals(42, eval('for (let x of g()) { x; }'));
|
||||
assertEquals([], log);
|
||||
|
||||
log = [];
|
||||
assertEquals(42, eval('for (x of g()) { x; }'));
|
||||
assertEquals([], log);
|
||||
}
|
||||
|
||||
|
||||
// Return method throws.
|
||||
{
|
||||
let log = [];
|
||||
g.prototype.return = (...args) => { log.push(args); throw 23 };
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g()) { break; }
|
||||
}, 23);
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (x of g()) { break; }
|
||||
}, 23);
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g()) { throw 42; }
|
||||
}, 42);
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (x of g()) { throw 42; }
|
||||
}, 42);
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g()) { return 42; }
|
||||
}, 23);
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (x of g()) { return 42; }
|
||||
}, 23);
|
||||
assertEquals([[]], log);
|
||||
|
||||
log = [];
|
||||
assertEquals(42, eval('for (let x of g()) { x; }'));
|
||||
assertEquals([], log);
|
||||
|
||||
log = [];
|
||||
assertEquals(42, eval('for (x of g()) { x; }'));
|
||||
assertEquals([], log);
|
||||
}
|
||||
|
||||
|
||||
// Next method throws.
|
||||
{
|
||||
g.prototype.next = () => { throw 666; };
|
||||
g.prototype.return = () => { assertUnreachable() };
|
||||
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g()) {}
|
||||
}, 666);
|
||||
|
||||
assertThrowsEquals(() => {
|
||||
for (x of g()) {}
|
||||
}, 666);
|
||||
}
|
||||
|
||||
|
||||
// Nested loops.
|
||||
{
|
||||
function* g1() { yield 1; yield 2; throw 3; }
|
||||
function* g2() { yield -1; yield -2; throw -3; }
|
||||
|
||||
assertDoesNotThrow(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
if (y == -2) break;
|
||||
}
|
||||
if (x == 2) break;
|
||||
}
|
||||
}, -3);
|
||||
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
}
|
||||
}
|
||||
}, -3);
|
||||
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
if (y == -2) break;
|
||||
}
|
||||
}
|
||||
}, 3);
|
||||
|
||||
assertDoesNotThrow(() => {
|
||||
l: for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
if (y == -2) break l;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
throw 4;
|
||||
}
|
||||
}
|
||||
}, 4);
|
||||
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
if (y == -2) throw 4;
|
||||
}
|
||||
}
|
||||
}, 4);
|
||||
|
||||
let log = [];
|
||||
g1.prototype.return = () => { log.push(1); throw 5 };
|
||||
g2.prototype.return = () => { log.push(2); throw -5 };
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
if (y == -2) break;
|
||||
}
|
||||
if (x == 2) break;
|
||||
}
|
||||
}, -5);
|
||||
assertEquals([2, 1], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
}
|
||||
}
|
||||
}, -3);
|
||||
assertEquals([1], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
if (y == -2) break;
|
||||
}
|
||||
}
|
||||
}, -5);
|
||||
assertEquals([2, 1], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
l: for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
if (y == -2) break l;
|
||||
}
|
||||
}
|
||||
}, -5);
|
||||
assertEquals([2, 1], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
throw 4;
|
||||
}
|
||||
}
|
||||
}, 4);
|
||||
assertEquals([2, 1], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
for (let y of g2()) {
|
||||
if (y == -2) throw 4;
|
||||
}
|
||||
}
|
||||
}, 4);
|
||||
assertEquals([2, 1], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
try {
|
||||
for (let y of g2()) {
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}, 3);
|
||||
assertEquals([], log);
|
||||
|
||||
log = [];
|
||||
assertThrowsEquals(() => {
|
||||
for (let x of g1()) {
|
||||
try {
|
||||
for (let y of g2()) {
|
||||
}
|
||||
} catch (_) {}
|
||||
if (x == 2) break;
|
||||
}
|
||||
}, 5);
|
||||
assertEquals([1], log);
|
||||
}
|
@ -829,6 +829,7 @@
|
||||
'harmony/regress/regress-4482': [FAIL],
|
||||
'harmony/reflect': [FAIL],
|
||||
'harmony/generators': [FAIL],
|
||||
'harmony/iterator-close': [FAIL],
|
||||
'regress/regress-572589': [FAIL],
|
||||
'harmony/reflect-construct': [FAIL],
|
||||
'es6/promises': [FAIL],
|
||||
|
@ -9,9 +9,3 @@
|
||||
assertThrows("'use strong'; for (let x in []) {}", SyntaxError);
|
||||
assertThrows("'use strong'; for (const x in []) {}", SyntaxError);
|
||||
})();
|
||||
|
||||
(function ForOfStatement() {
|
||||
assertTrue(eval("'use strong'; for (x of []) {} true"));
|
||||
assertTrue(eval("'use strong'; for (let x of []) {} true"));
|
||||
assertTrue(eval("'use strong'; for (const x of []) {} true"));
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user