Commit 39642fa2 authored by caitp's avatar caitp Committed by Commit bot

[async-await] (simpler) fix for Return in try/finally in async functions

Alternative approach to https://codereview.chromium.org/2667983004/, which
does not depend on implicit control flow changes from
https://codereview.chromium.org/2664083002

- Remove handling for `async function` from Parser::RewriteReturn(). This functionality
is moved to BytecodeGenerator::BuildAsyncReturn(). This ensures that promise resolution
is deferred until all finally blocks are evaluated fully.

- Add a new deferred command (CMD_ASYNC_RETURN), which instructs ControlScope to
generate return code using BuildAsyncReturn rather than BuildReturn.

- Parser has a new `NewReturnStatement()` helper which determines what type of return
statement to generate based on the type of function.

BUG=v8:5896, v8:4483
R=littledan@chromium.org, neis@chromium.org, rmcilroy@chromium.org, adamk@chromium.org, gsathya@chromium.org

Review-Url: https://codereview.chromium.org/2685683002
Cr-Commit-Position: refs/heads/master@{#43104}
parent a360134b
...@@ -234,6 +234,9 @@ void AstNumberingVisitor::VisitExpressionStatement(ExpressionStatement* node) { ...@@ -234,6 +234,9 @@ void AstNumberingVisitor::VisitExpressionStatement(ExpressionStatement* node) {
void AstNumberingVisitor::VisitReturnStatement(ReturnStatement* node) { void AstNumberingVisitor::VisitReturnStatement(ReturnStatement* node) {
IncrementNodeCount(); IncrementNodeCount();
Visit(node->expression()); Visit(node->expression());
DCHECK(!node->is_async_return() ||
properties_.flags() & AstProperties::kMustUseIgnitionTurbo);
} }
......
...@@ -897,17 +897,25 @@ class BreakStatement final : public JumpStatement { ...@@ -897,17 +897,25 @@ class BreakStatement final : public JumpStatement {
class ReturnStatement final : public JumpStatement { class ReturnStatement final : public JumpStatement {
public: public:
enum Type { kNormal, kAsyncReturn };
Expression* expression() const { return expression_; } Expression* expression() const { return expression_; }
void set_expression(Expression* e) { expression_ = e; } void set_expression(Expression* e) { expression_ = e; }
Type type() const { return TypeField::decode(bit_field_); }
bool is_async_return() const { return type() == kAsyncReturn; }
private: private:
friend class AstNodeFactory; friend class AstNodeFactory;
ReturnStatement(Expression* expression, int pos) ReturnStatement(Expression* expression, Type type, int pos)
: JumpStatement(pos, kReturnStatement), expression_(expression) {} : JumpStatement(pos, kReturnStatement), expression_(expression) {
bit_field_ |= TypeField::encode(type);
}
Expression* expression_; Expression* expression_;
class TypeField
: public BitField<Type, JumpStatement::kNextBitFieldIndex, 1> {};
}; };
...@@ -3210,7 +3218,13 @@ class AstNodeFactory final BASE_EMBEDDED { ...@@ -3210,7 +3218,13 @@ class AstNodeFactory final BASE_EMBEDDED {
} }
ReturnStatement* NewReturnStatement(Expression* expression, int pos) { ReturnStatement* NewReturnStatement(Expression* expression, int pos) {
return new (zone_) ReturnStatement(expression, pos); return new (zone_)
ReturnStatement(expression, ReturnStatement::kNormal, pos);
}
ReturnStatement* NewAsyncReturnStatement(Expression* expression, int pos) {
return new (zone_)
ReturnStatement(expression, ReturnStatement::kAsyncReturn, pos);
} }
WithStatement* NewWithStatement(Scope* scope, WithStatement* NewWithStatement(Scope* scope,
......
...@@ -297,7 +297,7 @@ void DeclarationScope::SetDefaults() { ...@@ -297,7 +297,7 @@ void DeclarationScope::SetDefaults() {
new_target_ = nullptr; new_target_ = nullptr;
function_ = nullptr; function_ = nullptr;
arguments_ = nullptr; arguments_ = nullptr;
this_function_ = nullptr; rare_data_ = nullptr;
should_eager_compile_ = false; should_eager_compile_ = false;
was_lazily_parsed_ = false; was_lazily_parsed_ = false;
#ifdef DEBUG #ifdef DEBUG
...@@ -682,7 +682,7 @@ void DeclarationScope::DeclareDefaultFunctionVariables( ...@@ -682,7 +682,7 @@ void DeclarationScope::DeclareDefaultFunctionVariables(
if (IsConciseMethod(function_kind_) || IsClassConstructor(function_kind_) || if (IsConciseMethod(function_kind_) || IsClassConstructor(function_kind_) ||
IsAccessorFunction(function_kind_)) { IsAccessorFunction(function_kind_)) {
this_function_ = EnsureRareData()->this_function =
Declare(zone(), ast_value_factory->this_function_string(), CONST); Declare(zone(), ast_value_factory->this_function_string(), CONST);
} }
} }
...@@ -703,6 +703,24 @@ Variable* DeclarationScope::DeclareFunctionVar(const AstRawString* name) { ...@@ -703,6 +703,24 @@ Variable* DeclarationScope::DeclareFunctionVar(const AstRawString* name) {
return function_; return function_;
} }
Variable* DeclarationScope::DeclareGeneratorObjectVar(
const AstRawString* name) {
DCHECK(is_function_scope() || is_module_scope());
DCHECK_NULL(generator_object_var());
Variable* result = EnsureRareData()->generator_object = NewTemporary(name);
result->set_is_used();
return result;
}
Variable* DeclarationScope::DeclarePromiseVar(const AstRawString* name) {
DCHECK(is_function_scope());
DCHECK_NULL(promise_var());
Variable* result = EnsureRareData()->promise = NewTemporary(name);
result->set_is_used();
return result;
}
bool Scope::HasBeenRemoved() const { bool Scope::HasBeenRemoved() const {
if (sibling() == this) { if (sibling() == this) {
DCHECK_NULL(inner_scope_); DCHECK_NULL(inner_scope_);
...@@ -2143,9 +2161,8 @@ void DeclarationScope::AllocateLocals() { ...@@ -2143,9 +2161,8 @@ void DeclarationScope::AllocateLocals() {
new_target_ = nullptr; new_target_ = nullptr;
} }
if (this_function_ != nullptr && !MustAllocate(this_function_)) { NullifyRareVariableIf(RareVariable::kThisFunction,
this_function_ = nullptr; [=](Variable* var) { return !MustAllocate(var); });
}
} }
void ModuleScope::AllocateModuleVariables() { void ModuleScope::AllocateModuleVariables() {
......
...@@ -680,6 +680,11 @@ class DeclarationScope : public Scope { ...@@ -680,6 +680,11 @@ class DeclarationScope : public Scope {
// calls sloppy eval. // calls sloppy eval.
Variable* DeclareFunctionVar(const AstRawString* name); Variable* DeclareFunctionVar(const AstRawString* name);
// Declare some special internal variables which must be accessible to
// Ignition without ScopeInfo.
Variable* DeclareGeneratorObjectVar(const AstRawString* name);
Variable* DeclarePromiseVar(const AstRawString* name);
// Declare a parameter in this scope. When there are duplicated // Declare a parameter in this scope. When there are duplicated
// parameters the rightmost one 'wins'. However, the implementation // parameters the rightmost one 'wins'. However, the implementation
// expects all parameters to be declared and from left to right. // expects all parameters to be declared and from left to right.
...@@ -723,6 +728,17 @@ class DeclarationScope : public Scope { ...@@ -723,6 +728,17 @@ class DeclarationScope : public Scope {
return function_; return function_;
} }
Variable* generator_object_var() const {
DCHECK(is_function_scope() || is_module_scope());
return GetRareVariable(RareVariable::kGeneratorObject);
}
Variable* promise_var() const {
DCHECK(is_function_scope());
DCHECK(IsAsyncFunction(function_kind_));
return GetRareVariable(RareVariable::kPromise);
}
// Parameters. The left-most parameter has index 0. // Parameters. The left-most parameter has index 0.
// Only valid for function and module scopes. // Only valid for function and module scopes.
Variable* parameter(int index) const { Variable* parameter(int index) const {
...@@ -763,12 +779,14 @@ class DeclarationScope : public Scope { ...@@ -763,12 +779,14 @@ class DeclarationScope : public Scope {
} }
Variable* this_function_var() const { Variable* this_function_var() const {
Variable* this_function = GetRareVariable(RareVariable::kThisFunction);
// This is only used in derived constructors atm. // This is only used in derived constructors atm.
DCHECK(this_function_ == nullptr || DCHECK(this_function == nullptr ||
(is_function_scope() && (IsClassConstructor(function_kind()) || (is_function_scope() && (IsClassConstructor(function_kind()) ||
IsConciseMethod(function_kind()) || IsConciseMethod(function_kind()) ||
IsAccessorFunction(function_kind())))); IsAccessorFunction(function_kind()))));
return this_function_; return this_function;
} }
// Adds a local variable in this scope's locals list. This is for adjusting // Adds a local variable in this scope's locals list. This is for adjusting
...@@ -877,8 +895,50 @@ class DeclarationScope : public Scope { ...@@ -877,8 +895,50 @@ class DeclarationScope : public Scope {
Variable* new_target_; Variable* new_target_;
// Convenience variable; function scopes only. // Convenience variable; function scopes only.
Variable* arguments_; Variable* arguments_;
// Convenience variable; Subclass constructor only
Variable* this_function_; struct RareData : public ZoneObject {
void* operator new(size_t size, Zone* zone) { return zone->New(size); }
// Convenience variable; Subclass constructor only
Variable* this_function = nullptr;
// Generator object, if any; generator function scopes and module scopes
// only.
Variable* generator_object = nullptr;
// Promise, if any; async function scopes only.
Variable* promise = nullptr;
};
enum class RareVariable {
kThisFunction = offsetof(RareData, this_function),
kGeneratorObject = offsetof(RareData, generator_object),
kPromise = offsetof(RareData, promise)
};
V8_INLINE RareData* EnsureRareData() {
if (rare_data_ == nullptr) {
rare_data_ = new (zone_) RareData;
}
return rare_data_;
}
V8_INLINE Variable* GetRareVariable(RareVariable id) const {
if (rare_data_ == nullptr) return nullptr;
return *reinterpret_cast<Variable**>(
reinterpret_cast<uint8_t*>(rare_data_) + static_cast<ptrdiff_t>(id));
}
// Set `var` to null if it's non-null and Predicate (Variable*) -> bool
// returns true.
template <typename Predicate>
V8_INLINE void NullifyRareVariableIf(RareVariable id, Predicate predicate) {
if (V8_LIKELY(rare_data_ == nullptr)) return;
Variable** var = reinterpret_cast<Variable**>(
reinterpret_cast<uint8_t*>(rare_data_) + static_cast<ptrdiff_t>(id));
if (*var && predicate(*var)) *var = nullptr;
}
RareData* rare_data_ = nullptr;
}; };
class ModuleScope final : public DeclarationScope { class ModuleScope final : public DeclarationScope {
......
...@@ -106,12 +106,19 @@ class BytecodeGenerator::ControlScope BASE_EMBEDDED { ...@@ -106,12 +106,19 @@ class BytecodeGenerator::ControlScope BASE_EMBEDDED {
void Break(Statement* stmt) { PerformCommand(CMD_BREAK, stmt); } void Break(Statement* stmt) { PerformCommand(CMD_BREAK, stmt); }
void Continue(Statement* stmt) { PerformCommand(CMD_CONTINUE, stmt); } void Continue(Statement* stmt) { PerformCommand(CMD_CONTINUE, stmt); }
void ReturnAccumulator() { PerformCommand(CMD_RETURN, nullptr); } void ReturnAccumulator() { PerformCommand(CMD_RETURN, nullptr); }
void AsyncReturnAccumulator() { PerformCommand(CMD_ASYNC_RETURN, nullptr); }
void ReThrowAccumulator() { PerformCommand(CMD_RETHROW, nullptr); } void ReThrowAccumulator() { PerformCommand(CMD_RETHROW, nullptr); }
class DeferredCommands; class DeferredCommands;
protected: protected:
enum Command { CMD_BREAK, CMD_CONTINUE, CMD_RETURN, CMD_RETHROW }; enum Command {
CMD_BREAK,
CMD_CONTINUE,
CMD_RETURN,
CMD_ASYNC_RETURN,
CMD_RETHROW
};
void PerformCommand(Command command, Statement* statement); void PerformCommand(Command command, Statement* statement);
virtual bool Execute(Command command, Statement* statement) = 0; virtual bool Execute(Command command, Statement* statement) = 0;
...@@ -221,6 +228,9 @@ class BytecodeGenerator::ControlScopeForTopLevel final ...@@ -221,6 +228,9 @@ class BytecodeGenerator::ControlScopeForTopLevel final
case CMD_RETURN: case CMD_RETURN:
generator()->BuildReturn(); generator()->BuildReturn();
return true; return true;
case CMD_ASYNC_RETURN:
generator()->BuildAsyncReturn();
return true;
case CMD_RETHROW: case CMD_RETHROW:
generator()->BuildReThrow(); generator()->BuildReThrow();
return true; return true;
...@@ -249,6 +259,7 @@ class BytecodeGenerator::ControlScopeForBreakable final ...@@ -249,6 +259,7 @@ class BytecodeGenerator::ControlScopeForBreakable final
return true; return true;
case CMD_CONTINUE: case CMD_CONTINUE:
case CMD_RETURN: case CMD_RETURN:
case CMD_ASYNC_RETURN:
case CMD_RETHROW: case CMD_RETHROW:
break; break;
} }
...@@ -286,6 +297,7 @@ class BytecodeGenerator::ControlScopeForIteration final ...@@ -286,6 +297,7 @@ class BytecodeGenerator::ControlScopeForIteration final
loop_builder_->Continue(); loop_builder_->Continue();
return true; return true;
case CMD_RETURN: case CMD_RETURN:
case CMD_ASYNC_RETURN:
case CMD_RETHROW: case CMD_RETHROW:
break; break;
} }
...@@ -311,6 +323,7 @@ class BytecodeGenerator::ControlScopeForTryCatch final ...@@ -311,6 +323,7 @@ class BytecodeGenerator::ControlScopeForTryCatch final
case CMD_BREAK: case CMD_BREAK:
case CMD_CONTINUE: case CMD_CONTINUE:
case CMD_RETURN: case CMD_RETURN:
case CMD_ASYNC_RETURN:
break; break;
case CMD_RETHROW: case CMD_RETHROW:
generator()->BuildReThrow(); generator()->BuildReThrow();
...@@ -337,6 +350,7 @@ class BytecodeGenerator::ControlScopeForTryFinally final ...@@ -337,6 +350,7 @@ class BytecodeGenerator::ControlScopeForTryFinally final
case CMD_BREAK: case CMD_BREAK:
case CMD_CONTINUE: case CMD_CONTINUE:
case CMD_RETURN: case CMD_RETURN:
case CMD_ASYNC_RETURN:
case CMD_RETHROW: case CMD_RETHROW:
commands_->RecordCommand(command, statement); commands_->RecordCommand(command, statement);
try_finally_builder_->LeaveTry(); try_finally_builder_->LeaveTry();
...@@ -1058,7 +1072,12 @@ void BytecodeGenerator::VisitBreakStatement(BreakStatement* stmt) { ...@@ -1058,7 +1072,12 @@ void BytecodeGenerator::VisitBreakStatement(BreakStatement* stmt) {
void BytecodeGenerator::VisitReturnStatement(ReturnStatement* stmt) { void BytecodeGenerator::VisitReturnStatement(ReturnStatement* stmt) {
builder()->SetStatementPosition(stmt); builder()->SetStatementPosition(stmt);
VisitForAccumulatorValue(stmt->expression()); VisitForAccumulatorValue(stmt->expression());
execution_control()->ReturnAccumulator();
if (stmt->is_async_return()) {
execution_control()->AsyncReturnAccumulator();
} else {
execution_control()->ReturnAccumulator();
}
} }
void BytecodeGenerator::VisitWithStatement(WithStatement* stmt) { void BytecodeGenerator::VisitWithStatement(WithStatement* stmt) {
...@@ -2001,6 +2020,28 @@ void BytecodeGenerator::BuildReturn() { ...@@ -2001,6 +2020,28 @@ void BytecodeGenerator::BuildReturn() {
builder()->Return(); builder()->Return();
} }
void BytecodeGenerator::BuildAsyncReturn() {
DCHECK(IsAsyncFunction(info()->literal()->kind()));
RegisterAllocationScope register_scope(this);
RegisterList args = register_allocator()->NewRegisterList(3);
Register receiver = args[0];
Register promise = args[1];
Register return_value = args[2];
builder()->StoreAccumulatorInRegister(return_value);
Variable* var_promise = scope()->promise_var();
DCHECK_NOT_NULL(var_promise);
BuildVariableLoad(var_promise, FeedbackSlot::Invalid(),
HoleCheckMode::kElided);
builder()
->StoreAccumulatorInRegister(promise)
.LoadUndefined()
.StoreAccumulatorInRegister(receiver)
.CallJSRuntime(Context::PROMISE_RESOLVE_INDEX, args)
.LoadAccumulatorWithRegister(promise);
BuildReturn();
}
void BytecodeGenerator::BuildReThrow() { builder()->ReThrow(); } void BytecodeGenerator::BuildReThrow() { builder()->ReThrow(); }
void BytecodeGenerator::BuildAbort(BailoutReason bailout_reason) { void BytecodeGenerator::BuildAbort(BailoutReason bailout_reason) {
......
...@@ -105,6 +105,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> { ...@@ -105,6 +105,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
HoleCheckMode hole_check_mode); HoleCheckMode hole_check_mode);
void BuildReturn(); void BuildReturn();
void BuildAsyncReturn();
void BuildReThrow(); void BuildReThrow();
void BuildAbort(BailoutReason bailout_reason); void BuildAbort(BailoutReason bailout_reason);
void BuildThrowIfHole(Handle<String> name); void BuildThrowIfHole(Handle<String> name);
......
...@@ -427,23 +427,12 @@ class ParserBase { ...@@ -427,23 +427,12 @@ class ParserBase {
FunctionKind kind() const { return scope()->function_kind(); } FunctionKind kind() const { return scope()->function_kind(); }
FunctionState* outer() const { return outer_function_state_; } FunctionState* outer() const { return outer_function_state_; }
void set_generator_object_variable(typename Types::Variable* variable) {
DCHECK_NOT_NULL(variable);
DCHECK(IsResumableFunction(kind()));
DCHECK(scope()->has_forced_context_allocation());
generator_object_variable_ = variable;
}
typename Types::Variable* generator_object_variable() const { typename Types::Variable* generator_object_variable() const {
return generator_object_variable_; return scope()->generator_object_var();
} }
void set_promise_variable(typename Types::Variable* variable) {
DCHECK(variable != NULL);
DCHECK(IsAsyncFunction(kind()));
promise_variable_ = variable;
}
typename Types::Variable* promise_variable() const { typename Types::Variable* promise_variable() const {
return promise_variable_; return scope()->promise_var();
} }
const ZoneList<DestructuringAssignment>& const ZoneList<DestructuringAssignment>&
...@@ -1383,6 +1372,15 @@ class ParserBase { ...@@ -1383,6 +1372,15 @@ class ParserBase {
return Call::NOT_EVAL; return Call::NOT_EVAL;
} }
// Convenience method which determines the type of return statement to emit
// depending on the current function type.
inline StatementT BuildReturnStatement(ExpressionT expr, int pos) {
if (V8_UNLIKELY(is_async_function())) {
return factory()->NewAsyncReturnStatement(expr, pos);
}
return factory()->NewReturnStatement(expr, pos);
}
// Validation per ES6 object literals. // Validation per ES6 object literals.
class ObjectLiteralChecker { class ObjectLiteralChecker {
public: public:
...@@ -4276,9 +4274,8 @@ ParserBase<Impl>::ParseArrowFunctionLiteral( ...@@ -4276,9 +4274,8 @@ ParserBase<Impl>::ParseArrowFunctionLiteral(
} else { } else {
ExpressionT expression = ParseAssignmentExpression(accept_IN, CHECK_OK); ExpressionT expression = ParseAssignmentExpression(accept_IN, CHECK_OK);
impl()->RewriteNonPattern(CHECK_OK); impl()->RewriteNonPattern(CHECK_OK);
body->Add( body->Add(BuildReturnStatement(expression, expression->position()),
factory()->NewReturnStatement(expression, expression->position()), zone());
zone());
if (allow_tailcalls() && !is_sloppy(language_mode())) { if (allow_tailcalls() && !is_sloppy(language_mode())) {
// ES6 14.6.1 Static Semantics: IsInTailPosition // ES6 14.6.1 Static Semantics: IsInTailPosition
impl()->MarkTailPosition(expression); impl()->MarkTailPosition(expression);
...@@ -5183,7 +5180,7 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseReturnStatement( ...@@ -5183,7 +5180,7 @@ typename ParserBase<Impl>::StatementT ParserBase<Impl>::ParseReturnStatement(
} }
ExpectSemicolon(CHECK_OK); ExpectSemicolon(CHECK_OK);
return_value = impl()->RewriteReturn(return_value, loc.beg_pos); return_value = impl()->RewriteReturn(return_value, loc.beg_pos);
return factory()->NewReturnStatement(return_value, loc.beg_pos); return BuildReturnStatement(return_value, loc.beg_pos);
} }
template <typename Impl> template <typename Impl>
......
...@@ -1613,8 +1613,6 @@ Expression* Parser::RewriteReturn(Expression* return_value, int pos) { ...@@ -1613,8 +1613,6 @@ Expression* Parser::RewriteReturn(Expression* return_value, int pos) {
} }
if (is_generator()) { if (is_generator()) {
return_value = BuildIteratorResult(return_value, true); return_value = BuildIteratorResult(return_value, true);
} else if (is_async_function()) {
return_value = BuildResolvePromise(return_value, return_value->position());
} }
return return_value; return return_value;
} }
...@@ -2477,9 +2475,8 @@ void Parser::PrepareGeneratorVariables() { ...@@ -2477,9 +2475,8 @@ void Parser::PrepareGeneratorVariables() {
// Calling a generator returns a generator object. That object is stored // Calling a generator returns a generator object. That object is stored
// in a temporary variable, a definition that is used by "yield" // in a temporary variable, a definition that is used by "yield"
// expressions. // expressions.
Variable* temp = function_state_->scope()->DeclareGeneratorObjectVar(
NewTemporary(ast_value_factory()->dot_generator_object_string()); ast_value_factory()->dot_generator_object_string());
function_state_->set_generator_object_variable(temp);
} }
FunctionLiteral* Parser::ParseFunctionLiteral( FunctionLiteral* Parser::ParseFunctionLiteral(
...@@ -3093,8 +3090,8 @@ Variable* Parser::PromiseVariable() { ...@@ -3093,8 +3090,8 @@ Variable* Parser::PromiseVariable() {
// comes first should create it and stash it in the FunctionState. // comes first should create it and stash it in the FunctionState.
Variable* promise = function_state_->promise_variable(); Variable* promise = function_state_->promise_variable();
if (function_state_->promise_variable() == nullptr) { if (function_state_->promise_variable() == nullptr) {
promise = scope()->NewTemporary(ast_value_factory()->empty_string()); promise = function_state_->scope()->DeclarePromiseVar(
function_state_->set_promise_variable(promise); ast_value_factory()->empty_string());
} }
return promise; return promise;
} }
......
...@@ -670,6 +670,10 @@ class PreParserFactory { ...@@ -670,6 +670,10 @@ class PreParserFactory {
int pos) { int pos) {
return PreParserStatement::Jump(); return PreParserStatement::Jump();
} }
PreParserStatement NewAsyncReturnStatement(PreParserExpression expression,
int pos) {
return PreParserStatement::Jump();
}
PreParserExpression NewFunctionLiteral( PreParserExpression NewFunctionLiteral(
PreParserIdentifier name, Scope* scope, PreParserStatementList body, PreParserIdentifier name, Scope* scope, PreParserStatementList body,
int materialized_literal_count, int expected_property_count, int materialized_literal_count, int expected_property_count,
......
// Copyright 2017 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: --allow-natives-syntax
function assertThrowsAsync(run, errorType, message) {
var actual;
var hadValue = false;
var hadError = false;
var promise = run();
if (typeof promise !== "object" || typeof promise.then !== "function") {
throw new MjsUnitAssertionError(
"Expected " + run.toString() +
" to return a Promise, but it returned " + PrettyPrint(promise));
}
promise.then(function(value) { hadValue = true; actual = value; },
function(error) { hadError = true; actual = error; });
assertFalse(hadValue || hadError);
%RunMicrotasks();
if (!hadError) {
throw new MjsUnitAssertionError(
"Expected " + run + "() to throw " + errorType.name +
", but did not throw.");
}
if (!(actual instanceof errorType))
throw new MjsUnitAssertionError(
"Expected " + run + "() to throw " + errorType.name +
", but threw '" + actual + "'");
if (message !== void 0 && actual.message !== message)
throw new MjsUnitAssertionError(
"Expected " + run + "() to throw '" + message + "', but threw '" +
actual.message + "'");
};
function assertEqualsAsync(expected, run, msg) {
var actual;
var hadValue = false;
var hadError = false;
var promise = run();
if (typeof promise !== "object" || typeof promise.then !== "function") {
throw new MjsUnitAssertionError(
"Expected " + run.toString() +
" to return a Promise, but it returned " + PrettyPrint(promise));
}
promise.then(function(value) { hadValue = true; actual = value; },
function(error) { hadError = true; actual = error; });
assertFalse(hadValue || hadError);
%RunMicrotasks();
if (hadError) throw actual;
assertTrue(
hadValue, "Expected '" + run.toString() + "' to produce a value");
assertEquals(expected, actual, msg);
};
function resolveLater(value) {
return new Promise(function(resolve) {
resolve(value);
});
}
function rejectLater(reason) {
return new Promise(function(resolve, reject) {
reject(reason);
});
}
class MyError extends Error {};
var AsyncFunction = async function() {}.constructor;
assertEqualsAsync("finally-return (func-expr)", async function() {
try {
return "early-return (func-expr)";
} finally {
return "finally-return (func-expr)";
}
});
assertEqualsAsync("finally-return (arrow)", async() => {
try {
return "early-return (arrow)";
} finally {
return "finally-return (arrow)";
}
});
assertEqualsAsync("finally-return (eval)", AsyncFunction(`
try {
return "early-return (eval)";
} finally {
return "finally-return (eval)";
}
`));
assertEqualsAsync("promise-finally-return (func-expr)", async function() {
try {
return new Promise(function() {});
} finally {
return "promise-finally-return (func-expr)";
}
});
assertEqualsAsync("promise-finally-return (arrow)", async() => {
try {
return new Promise(function() {});
} finally {
return "promise-finally-return (arrow)";
}
});
assertEqualsAsync("promise-finally-return (eval)", AsyncFunction(`
try {
return new Promise(function() {});
} finally {
return "promise-finally-return (eval)";
}
`));
assertEqualsAsync("await-finally-return (func-expr)", async function() {
try {
return "early-return (func-expr)";
} finally {
return await resolveLater("await-finally-return (func-expr)");
}
});
assertEqualsAsync("await-finally-return (arrow)", async() => {
try {
return "early-return (arrow)";
} finally {
return await resolveLater("await-finally-return (arrow)");
}
});
assertEqualsAsync("await-finally-return (eval)", AsyncFunction(`
try {
return "early-return (eval)";
} finally {
return await resolveLater("await-finally-return (eval)");
}
`));
assertThrowsAsync(async function() {
try {
return "early-return (func-expr)";
} finally {
throw new MyError("finally-throw (func-expr)");
}
}, MyError, "finally-throw (func-expr)");
assertThrowsAsync(async() => {
try {
return "early-return (arrow)";
} finally {
throw new MyError("finally-throw (arrow)");
}
}, MyError, "finally-throw (arrow)");
assertThrowsAsync(AsyncFunction(`
try {
return "early-return (eval)";
} finally {
throw new MyError("finally-throw (eval)");
}
`), MyError, "finally-throw (eval)");
assertThrowsAsync(async function() {
try {
return "early-return (func-expr)";
} finally {
await rejectLater(new MyError("await-finally-throw (func-expr)"));
}
}, MyError, "await-finally-throw (func-expr)");
assertThrowsAsync(async() => {
try {
return "early-return (arrow)";
} finally {
await rejectLater(new MyError("await-finally-throw (arrow)"));
}
}, MyError, "await-finally-throw (arrow)");
assertThrowsAsync(AsyncFunction(`
try {
return "early-return (eval)";
} finally {
await rejectLater(new MyError("await-finally-throw (eval)"));
}
`), MyError, "await-finally-throw (eval)");
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