Fix a bug in the async transformation of let expressions
The transformation worked as if there was never an await "to the
right" of the body of a let expression (i.e., an expression evaluated
after the let expression's body but before the value of the let
expression's body is used). This is obviously not right.
Fixes https://github.com/dart-lang/sdk/issues/33206
Change-Id: Idc175dc8c65f3d520de8b65f2285164d361ff38e
Reviewed-on: https://dart-review.googlesource.com/56492
Commit-Queue: Kevin Millikin <kmillikin@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/pkg/front_end/testcases/bug33206.dart b/pkg/front_end/testcases/bug33206.dart
new file mode 100644
index 0000000..79892c5
--- /dev/null
+++ b/pkg/front_end/testcases/bug33206.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+class X {
+ final x;
+ final y;
+
+ X(this.x, this.y);
+
+ toString() => "X($x, $y)";
+}
+
+class Y {
+ f(_) {}
+}
+
+Future<List<Object>> f1() async {
+ return [1];
+}
+
+List<Object> f2() => [2];
+
+Future<Object> f3() async {
+ return 3;
+}
+
+Future<X> foo() async {
+ return X(Y()..f(await f1())..f(f2()), await f3());
+}
+
+Future<void> main() async {
+ print(await foo());
+}
diff --git a/pkg/front_end/testcases/bug33206.dart.direct.expect b/pkg/front_end/testcases/bug33206.dart.direct.expect
new file mode 100644
index 0000000..45898a6
--- /dev/null
+++ b/pkg/front_end/testcases/bug33206.dart.direct.expect
@@ -0,0 +1,34 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "dart:async" as asy;
+
+class X extends core::Object {
+ final field dynamic x;
+ final field dynamic y;
+ constructor •(dynamic x, dynamic y) → void
+ : self::X::x = x, self::X::y = y, super core::Object::•()
+ ;
+ method toString() → dynamic
+ return "X(${this.{self::X::x}}, ${this.{self::X::y}})";
+}
+class Y extends core::Object {
+ synthetic constructor •() → void
+ : super core::Object::•()
+ ;
+ method f(dynamic _) → dynamic {}
+}
+static method f1() → asy::Future<core::List<core::Object>> async {
+ return <dynamic>[1];
+}
+static method f2() → core::List<core::Object>
+ return <dynamic>[2];
+static method f3() → asy::Future<core::Object> async {
+ return 3;
+}
+static method foo() → asy::Future<self::X> async {
+ return new self::X::•(let final dynamic #t1 = new self::Y::•() in let final dynamic #t2 = #t1.f(await self::f1()) in let final dynamic #t3 = #t1.f(self::f2()) in #t1, await self::f3());
+}
+static method main() → asy::Future<void> async {
+ core::print(await self::foo());
+}
diff --git a/pkg/front_end/testcases/bug33206.dart.direct.transformed.expect b/pkg/front_end/testcases/bug33206.dart.direct.transformed.expect
new file mode 100644
index 0000000..9d44bff
--- /dev/null
+++ b/pkg/front_end/testcases/bug33206.dart.direct.transformed.expect
@@ -0,0 +1,137 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "dart:async" as asy;
+
+class X extends core::Object {
+ final field dynamic x;
+ final field dynamic y;
+ constructor •(dynamic x, dynamic y) → void
+ : self::X::x = x, self::X::y = y, super core::Object::•()
+ ;
+ method toString() → dynamic
+ return "X(${this.{self::X::x}}, ${this.{self::X::y}})";
+}
+class Y extends core::Object {
+ synthetic constructor •() → void
+ : super core::Object::•()
+ ;
+ method f(dynamic _) → dynamic {}
+}
+static method f1() → asy::Future<core::List<core::Object>> /* originally async */ {
+ final asy::Completer<core::List<core::Object>> :async_completer = asy::Completer::sync<core::List<core::Object>>();
+ asy::FutureOr<core::List<core::Object>> :return_value;
+ dynamic :async_stack_trace;
+ dynamic :async_op_then;
+ dynamic :async_op_error;
+ dynamic :await_jump_var = 0;
+ dynamic :await_ctx_var;
+ function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
+ try {
+ #L1:
+ {
+ :return_value = <dynamic>[1];
+ break #L1;
+ }
+ :async_completer.{asy::Completer::complete}(:return_value);
+ return;
+ }
+ on dynamic catch(dynamic :exception, dynamic :stack_trace) {
+ :async_completer.{asy::Completer::completeError}(:exception, :stack_trace);
+ }
+ :async_stack_trace = asy::_asyncStackTraceHelper(:async_op);
+ :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+ :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+ asy::Future::microtask<dynamic>(:async_op);
+ return :async_completer.{asy::Completer::future};
+}
+static method f2() → core::List<core::Object>
+ return <dynamic>[2];
+static method f3() → asy::Future<core::Object> /* originally async */ {
+ final asy::Completer<core::Object> :async_completer = asy::Completer::sync<core::Object>();
+ asy::FutureOr<core::Object> :return_value;
+ dynamic :async_stack_trace;
+ dynamic :async_op_then;
+ dynamic :async_op_error;
+ dynamic :await_jump_var = 0;
+ dynamic :await_ctx_var;
+ function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
+ try {
+ #L2:
+ {
+ :return_value = 3;
+ break #L2;
+ }
+ :async_completer.{asy::Completer::complete}(:return_value);
+ return;
+ }
+ on dynamic catch(dynamic :exception, dynamic :stack_trace) {
+ :async_completer.{asy::Completer::completeError}(:exception, :stack_trace);
+ }
+ :async_stack_trace = asy::_asyncStackTraceHelper(:async_op);
+ :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+ :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+ asy::Future::microtask<dynamic>(:async_op);
+ return :async_completer.{asy::Completer::future};
+}
+static method foo() → asy::Future<self::X> /* originally async */ {
+ final asy::Completer<self::X> :async_completer = asy::Completer::sync<self::X>();
+ asy::FutureOr<self::X> :return_value;
+ dynamic :async_stack_trace;
+ dynamic :async_op_then;
+ dynamic :async_op_error;
+ dynamic :await_jump_var = 0;
+ dynamic :await_ctx_var;
+ dynamic :saved_try_context_var0;
+ function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
+ try {
+ #L3:
+ {
+ final dynamic #t1 = new self::Y::•();
+ [yield] let dynamic #t2 = asy::_awaitHelper(self::f1(), :async_op_then, :async_op_error, :async_op) in null;
+ final dynamic #t3 = #t1.f(:result);
+ final dynamic #t4 = #t1.f(self::f2());
+ [yield] let dynamic #t5 = asy::_awaitHelper(self::f3(), :async_op_then, :async_op_error, :async_op) in null;
+ :return_value = new self::X::•(#t1, :result);
+ break #L3;
+ }
+ :async_completer.{asy::Completer::complete}(:return_value);
+ return;
+ }
+ on dynamic catch(dynamic :exception, dynamic :stack_trace) {
+ :async_completer.{asy::Completer::completeError}(:exception, :stack_trace);
+ }
+ :async_stack_trace = asy::_asyncStackTraceHelper(:async_op);
+ :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+ :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+ asy::Future::microtask<dynamic>(:async_op);
+ return :async_completer.{asy::Completer::future};
+}
+static method main() → asy::Future<void> /* originally async */ {
+ final asy::Completer<void> :async_completer = asy::Completer::sync<void>();
+ asy::FutureOr<void> :return_value;
+ dynamic :async_stack_trace;
+ dynamic :async_op_then;
+ dynamic :async_op_error;
+ dynamic :await_jump_var = 0;
+ dynamic :await_ctx_var;
+ dynamic :saved_try_context_var0;
+ function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
+ try {
+ #L4:
+ {
+ [yield] let dynamic #t6 = asy::_awaitHelper(self::foo(), :async_op_then, :async_op_error, :async_op) in null;
+ core::print(:result);
+ }
+ :async_completer.{asy::Completer::complete}(:return_value);
+ return;
+ }
+ on dynamic catch(dynamic :exception, dynamic :stack_trace) {
+ :async_completer.{asy::Completer::completeError}(:exception, :stack_trace);
+ }
+ :async_stack_trace = asy::_asyncStackTraceHelper(:async_op);
+ :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+ :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+ asy::Future::microtask<dynamic>(:async_op);
+ return :async_completer.{asy::Completer::future};
+}
diff --git a/pkg/front_end/testcases/bug33206.dart.outline.expect b/pkg/front_end/testcases/bug33206.dart.outline.expect
new file mode 100644
index 0000000..bc0040e
--- /dev/null
+++ b/pkg/front_end/testcases/bug33206.dart.outline.expect
@@ -0,0 +1,29 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "dart:async" as asy;
+
+class X extends core::Object {
+ final field dynamic x;
+ final field dynamic y;
+ constructor •(dynamic x, dynamic y) → void
+ ;
+ method toString() → dynamic
+ ;
+}
+class Y extends core::Object {
+ synthetic constructor •() → void
+ ;
+ method f(dynamic _) → dynamic
+ ;
+}
+static method f1() → asy::Future<core::List<core::Object>>
+ ;
+static method f2() → core::List<core::Object>
+ ;
+static method f3() → asy::Future<core::Object>
+ ;
+static method foo() → asy::Future<self::X>
+ ;
+static method main() → asy::Future<void>
+ ;
diff --git a/pkg/front_end/testcases/bug33206.dart.strong.expect b/pkg/front_end/testcases/bug33206.dart.strong.expect
new file mode 100644
index 0000000..027b1f8
--- /dev/null
+++ b/pkg/front_end/testcases/bug33206.dart.strong.expect
@@ -0,0 +1,34 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "dart:async" as asy;
+
+class X extends core::Object {
+ final field dynamic x;
+ final field dynamic y;
+ constructor •(dynamic x, dynamic y) → void
+ : self::X::x = x, self::X::y = y, super core::Object::•()
+ ;
+ method toString() → core::String
+ return "X(${this.{self::X::x}}, ${this.{self::X::y}})";
+}
+class Y extends core::Object {
+ synthetic constructor •() → void
+ : super core::Object::•()
+ ;
+ method f(dynamic _) → dynamic {}
+}
+static method f1() → asy::Future<core::List<core::Object>> async {
+ return <core::Object>[1];
+}
+static method f2() → core::List<core::Object>
+ return <core::Object>[2];
+static method f3() → asy::Future<core::Object> async {
+ return 3;
+}
+static method foo() → asy::Future<self::X> async {
+ return new self::X::•(let final self::Y #t1 = new self::Y::•() in let final dynamic #t2 = #t1.{self::Y::f}(await self::f1()) in let final dynamic #t3 = #t1.{self::Y::f}(self::f2()) in #t1, await self::f3());
+}
+static method main() → asy::Future<void> async {
+ core::print(await self::foo());
+}
diff --git a/pkg/front_end/testcases/bug33206.dart.strong.transformed.expect b/pkg/front_end/testcases/bug33206.dart.strong.transformed.expect
new file mode 100644
index 0000000..cf29898
--- /dev/null
+++ b/pkg/front_end/testcases/bug33206.dart.strong.transformed.expect
@@ -0,0 +1,137 @@
+library;
+import self as self;
+import "dart:core" as core;
+import "dart:async" as asy;
+
+class X extends core::Object {
+ final field dynamic x;
+ final field dynamic y;
+ constructor •(dynamic x, dynamic y) → void
+ : self::X::x = x, self::X::y = y, super core::Object::•()
+ ;
+ method toString() → core::String
+ return "X(${this.{self::X::x}}, ${this.{self::X::y}})";
+}
+class Y extends core::Object {
+ synthetic constructor •() → void
+ : super core::Object::•()
+ ;
+ method f(dynamic _) → dynamic {}
+}
+static method f1() → asy::Future<core::List<core::Object>> /* originally async */ {
+ final asy::Completer<core::List<core::Object>> :async_completer = asy::Completer::sync<core::List<core::Object>>();
+ asy::FutureOr<core::List<core::Object>> :return_value;
+ dynamic :async_stack_trace;
+ dynamic :async_op_then;
+ dynamic :async_op_error;
+ dynamic :await_jump_var = 0;
+ dynamic :await_ctx_var;
+ function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
+ try {
+ #L1:
+ {
+ :return_value = <core::Object>[1];
+ break #L1;
+ }
+ :async_completer.{asy::Completer::complete}(:return_value);
+ return;
+ }
+ on dynamic catch(dynamic :exception, dynamic :stack_trace) {
+ :async_completer.{asy::Completer::completeError}(:exception, :stack_trace);
+ }
+ :async_stack_trace = asy::_asyncStackTraceHelper(:async_op);
+ :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+ :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+ asy::Future::microtask<dynamic>(:async_op);
+ return :async_completer.{asy::Completer::future};
+}
+static method f2() → core::List<core::Object>
+ return <core::Object>[2];
+static method f3() → asy::Future<core::Object> /* originally async */ {
+ final asy::Completer<core::Object> :async_completer = asy::Completer::sync<core::Object>();
+ asy::FutureOr<core::Object> :return_value;
+ dynamic :async_stack_trace;
+ dynamic :async_op_then;
+ dynamic :async_op_error;
+ dynamic :await_jump_var = 0;
+ dynamic :await_ctx_var;
+ function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
+ try {
+ #L2:
+ {
+ :return_value = 3;
+ break #L2;
+ }
+ :async_completer.{asy::Completer::complete}(:return_value);
+ return;
+ }
+ on dynamic catch(dynamic :exception, dynamic :stack_trace) {
+ :async_completer.{asy::Completer::completeError}(:exception, :stack_trace);
+ }
+ :async_stack_trace = asy::_asyncStackTraceHelper(:async_op);
+ :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+ :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+ asy::Future::microtask<dynamic>(:async_op);
+ return :async_completer.{asy::Completer::future};
+}
+static method foo() → asy::Future<self::X> /* originally async */ {
+ final asy::Completer<self::X> :async_completer = asy::Completer::sync<self::X>();
+ asy::FutureOr<self::X> :return_value;
+ dynamic :async_stack_trace;
+ dynamic :async_op_then;
+ dynamic :async_op_error;
+ dynamic :await_jump_var = 0;
+ dynamic :await_ctx_var;
+ dynamic :saved_try_context_var0;
+ function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
+ try {
+ #L3:
+ {
+ final self::Y #t1 = new self::Y::•();
+ [yield] let dynamic #t2 = asy::_awaitHelper(self::f1(), :async_op_then, :async_op_error, :async_op) in null;
+ final dynamic #t3 = #t1.{self::Y::f}(:result);
+ final dynamic #t4 = #t1.{self::Y::f}(self::f2());
+ [yield] let dynamic #t5 = asy::_awaitHelper(self::f3(), :async_op_then, :async_op_error, :async_op) in null;
+ :return_value = new self::X::•(#t1, :result);
+ break #L3;
+ }
+ :async_completer.{asy::Completer::complete}(:return_value);
+ return;
+ }
+ on dynamic catch(dynamic :exception, dynamic :stack_trace) {
+ :async_completer.{asy::Completer::completeError}(:exception, :stack_trace);
+ }
+ :async_stack_trace = asy::_asyncStackTraceHelper(:async_op);
+ :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+ :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+ asy::Future::microtask<dynamic>(:async_op);
+ return :async_completer.{asy::Completer::future};
+}
+static method main() → asy::Future<void> /* originally async */ {
+ final asy::Completer<void> :async_completer = asy::Completer::sync<void>();
+ asy::FutureOr<void> :return_value;
+ dynamic :async_stack_trace;
+ dynamic :async_op_then;
+ dynamic :async_op_error;
+ dynamic :await_jump_var = 0;
+ dynamic :await_ctx_var;
+ dynamic :saved_try_context_var0;
+ function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding
+ try {
+ #L4:
+ {
+ [yield] let dynamic #t6 = asy::_awaitHelper(self::foo(), :async_op_then, :async_op_error, :async_op) in null;
+ core::print(:result);
+ }
+ :async_completer.{asy::Completer::complete}(:return_value);
+ return;
+ }
+ on dynamic catch(dynamic :exception, dynamic :stack_trace) {
+ :async_completer.{asy::Completer::completeError}(:exception, :stack_trace);
+ }
+ :async_stack_trace = asy::_asyncStackTraceHelper(:async_op);
+ :async_op_then = asy::_asyncThenWrapperHelper(:async_op);
+ :async_op_error = asy::_asyncErrorWrapperHelper(:async_op);
+ asy::Future::microtask<dynamic>(:async_op);
+ return :async_completer.{asy::Completer::future};
+}
diff --git a/pkg/kernel/lib/transformations/async.dart b/pkg/kernel/lib/transformations/async.dart
index 72a0d20..21da8d9 100644
--- a/pkg/kernel/lib/transformations/async.dart
+++ b/pkg/kernel/lib/transformations/async.dart
@@ -454,15 +454,12 @@
}
TreeNode visitLet(Let expr) {
- var shouldName = seenAwait;
-
- seenAwait = false;
var body = expr.body.accept(this);
VariableDeclaration variable = expr.variable;
if (seenAwait) {
- // The body in `let var x = initializer in body` contained an await. We
- // will produce the sequence of statements:
+ // There is an await in the body of `let var x = initializer in body` or
+ // to its right. We will produce the sequence of statements:
//
// <initializer's statements>
// var x = <initializer's value>
@@ -488,7 +485,6 @@
} else {
// The body in `let x = initializer in body` did not contain an await. We
// can leave a let expression.
- seenAwait = shouldName;
return transform(expr, () {
// The body has already been translated.
expr.body = body..parent = expr;