Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(413)

Unified Diff: lib/transformations/continuation.dart

Issue 2460373002: Remove BlockExpression from the Kernel language. (Closed)
Patch Set: Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: lib/transformations/continuation.dart
diff --git a/lib/transformations/continuation.dart b/lib/transformations/continuation.dart
index 587a9f2da67055de823e9774b5d1ab87637ac69d..2bf950cd177c6ee30a79648ff879621b438630fd 100644
--- a/lib/transformations/continuation.dart
+++ b/lib/transformations/continuation.dart
@@ -33,15 +33,15 @@ class RecursiveContinuationRewriter extends Transformer {
visitFunctionNode(FunctionNode node) {
switch (node.asyncMarker) {
case AsyncMarker.Sync:
- return super.visitFunctionNode(node);
+ case AsyncMarker.SyncYielding:
+ node.transformChildren(new RecursiveContinuationRewriter(helper));
Kevin Millikin (Google) 2016/10/31 12:29:32 super.visitFunctionNode(node) is node.transformChi
+ return node;
case AsyncMarker.SyncStar:
return new SyncStarFunctionRewriter(helper, node).rewrite();
case AsyncMarker.Async:
return new AsyncFunctionRewriter(helper, node).rewrite();
case AsyncMarker.AsyncStar:
return new AsyncStarFunctionRewriter(helper, node).rewrite();
- case AsyncMarker.SyncYielding:
- return super.visitFunctionNode(node);
}
}
}
@@ -59,7 +59,7 @@ abstract class ContinuationRewriterBase extends RecursiveContinuationRewriter {
{this.currentTryDepth: 0})
: super(helper);
- Statement createContinuationPoint([value]) {
+ Statement createContinuationPoint([Expression value]) {
if (value == null) value = new NullLiteral();
capturedTryDepth = math.max(capturedTryDepth, currentTryDepth);
capturedCatchDepth = math.max(capturedCatchDepth, currentCatchDepth);
@@ -189,7 +189,7 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase {
: super(helper, enclosingFunction, currentTryDepth: 1) {
}
- setupAsyncContinuations(List<Statement> statements) {
+ void setupAsyncContinuations(List<Statement> statements) {
expressionRewriter = new ExpressionLifter(this);
// var :async_op_then;
@@ -201,7 +201,7 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase {
// :async_op([:result, :exception, :stack_trace]) {
// modified <node.body>;
// }
- final parameters = [
+ final parameters = <VariableDeclaration>[
expressionRewriter.asyncResult,
new VariableDeclaration(':exception'),
new VariableDeclaration(':stack_trace'),
@@ -227,7 +227,7 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase {
// :async_op_then = _asyncThenWrapperHelper(asyncBody);
final boundThenClosure = new StaticInvocation(
helper.asyncThenWrapper,
- new Arguments([new VariableGet(nestedClosureVariable)]));
+ new Arguments(<Expression>[new VariableGet(nestedClosureVariable)]));
final thenClosureVariableAssign = new ExpressionStatement(new VariableSet(
thenContinuationVariable, boundThenClosure));
statements.add(thenClosureVariableAssign);
@@ -235,7 +235,7 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase {
// :async_op_error = _asyncErrorWrapperHelper(asyncBody);
final boundCatchErrorClosure = new StaticInvocation(
helper.asyncErrorWrapper,
- new Arguments([new VariableGet(nestedClosureVariable)]));
+ new Arguments(<Expression>[new VariableGet(nestedClosureVariable)]));
final catchErrorClosureVariableAssign =
new ExpressionStatement(new VariableSet(
catchErrorContinuationVariable , boundCatchErrorClosure));
@@ -245,7 +245,7 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase {
Statement buildWrappedBody() {
// No explicit return at the end of the body => we will add one!
var body = addReturnStatementIfNecessary(enclosingFunction.body);
- var userBody = buildClosureBody(body);
+ var userBody = visitDelimited(body);
var exceptionVariable = new VariableDeclaration(":exception");
var stackTraceVariable = new VariableDeclaration(":stack_trace");
@@ -254,13 +254,13 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase {
buildCatchBody(exceptionVariable, stackTraceVariable);
var catchBody = new Block(<Statement>[completeErrorStatement]);
- var catches = [new Catch(exceptionVariable,
- catchBody,
- stackTrace: stackTraceVariable)];
+ var catches = <Catch>[new Catch(exceptionVariable,
+ catchBody,
+ stackTrace: stackTraceVariable)];
return new TryCatch(userBody, catches);
}
- addReturnStatementIfNecessary(Statement body) {
+ Statement addReturnStatementIfNecessary(Statement body) {
if (body is Block) {
Block block = body;
if (block.statements.isEmpty ||
@@ -271,18 +271,258 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase {
}
} else if (body is! ReturnStatement) {
var returnStatement = new ReturnStatement();
- body = new Block([body, returnStatement]);
+ body = new Block(<Statement>[body, returnStatement]);
}
return body;
}
- Statement buildClosureBody(Statement node);
-
Statement buildCatchBody(Statement exceptionVariable,
Statement stackTraceVariable);
- visitForInStatement(ForInStatement node) {
- if (node.isAsync) {
+ List<Statement> statements = <Statement>[];
+
+ TreeNode visitInvalidStatement(InvalidStatemnt stmt) {
asgerf 2016/11/01 10:44:45 InvalidStatemnt -> InvalidStatement
Kevin Millikin (Google) 2016/11/01 13:11:31 Done.
+ statements.add(stmt);
+ return null;
Kevin Millikin (Google) 2016/10/31 12:29:32 The statement translation functions always return
+ }
+
+ TreeNode visitExpressionStatement(ExpressionStatement stmt) {
+ stmt.expression = expressionRewriter.rewrite(stmt.expression, statements)
+ ..parent = stmt;
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitBlock(Block stmt) {
+ var saved = statements;
+ statements = <Statement>[];
+ for (var statement in stmt.statements) {
+ statement.accept(this);
+ }
+ saved.add(new Block(statements));
+ statements = saved;
+ return null;
+ }
+
+ TreeNode visitEmptyStatement(EmptyStatement stmt) {
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitAssertStatement(AssertStatement stmt) {
+ // TODO!
+ return null;
+ }
+
+ Statement visitDelimited(Statement stmt) {
+ var saved = statements;
+ statements = <Statement>[];
+ stmt.accept(this);
+ Statement result =
+ statements.length == 1 ? statements.first : new Block(statements);
+ statements = saved;
+ return result;
+ }
+
+ Statement visitLabeledStatement(LabeledStatement stmt) {
+ stmt.body = visitDelimited(stmt.body)..parent = stmt;
+ statements.add(stmt);
+ return null;
+ }
+
+ Statement visitBreakStatement(BreakStatement stmt) {
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitWhileStatement(WhileStatement stmt) {
+ Statement body = visitDelimited(stmt.body);
+ List<Statement> effects = <Statement>[];
+ Expression cond = expressionRewriter.rewrite(stmt.condition, effects);
+ if (effects.isEmpty) {
+ stmt.condition = cond..parent = stmt;
+ stmt.body = body..parent = stmt;
+ statements.add(stmt);
+ } else {
+ // The condition rewrote to a non-empty sequence of statements S* and
+ // value V. Rewrite the loop to:
+ //
+ // L: while (true) {
+ // S*
+ // if (V) {
+ // [body]
+ // else {
+ // break L;
+ // }
+ // }
+ LabeledStatement labeled = new LabeledStatement(stmt);
+ stmt.condition = new BoolLiteral(true)..parent = stmt;
+ effects.add(new IfStatement(cond, body, new BreakStatement(labeled)));
+ stmt.body = new Block(effects)..parent = stmt;
+ statements.add(labeled);
+ }
+ return null;
+ }
+
+ TreeNode visitDoStatement(DoStatement stmt) {
+ Statement body = visitDelimited(stmt.body);
+ List<Statement> effects = <Statement>[];
+ stmt.condition = expressionRewriter.rewrite(stmt.condition, effects)
+ ..parent = stmt;
+ if (effects.isNotEmpty) {
+ // The condition rewrote to a non-empty sequence of statements S* and
+ // value V. Add the statements to the end of the loop body.
+ Block block = body is Block ? body : body = new Block(<Statement>[body]);
+ for (var effect in effects) {
+ block.statements.add(effect);
+ effect.parent = body;
+ }
+ }
+ stmt.body = body..parent = stmt;
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitForStatement(ForStatement stmt) {
+ // Because of for-loop scoping and variable capture, it is tricky to deal
+ // with await in the loop's variable initializers or update expressions.
+ bool isSimple = true;
+ int length = stmt.variables.length;
+ List<List<Statement>> initEffects = new List<List<Statement>>(length);
+ for (int i = 0; i < length; ++i) {
+ VariableDeclaration decl = stmt.variables[i];
+ initEffects[i] = <Statement>[];
+ if (decl.initializer != null) {
+ decl.initializer =
+ expressionRewriter.rewrite(decl.initializer, initEffects[i])
+ ..parent = decl;
+ }
+ isSimple = isSimple && initEffects[i].isEmpty;
+ }
+
+ length = stmt.updates.length;
+ List<List<Statement>> updateEffects = new List<List<Statement>>(length);
+ for (int i = 0; i < stmt.updates.length; ++i) {
asgerf 2016/11/01 10:44:45 Did you mean to use "i < length" here? (since we h
Kevin Millikin (Google) 2016/11/01 13:11:31 Yes. Done.
+ updateEffects[i] = <Statement>[];
+ stmt.updates[i] =
+ expressionRewriter.rewrite(stmt.updates[i], updateEffects[i])
+ ..parent = stmt;
+ isSimple = isSimple && updateEffects[i].isEmpty;
+ }
+
+ Statement body = visitDelimited(stmt.body);
+ Expression cond = stmt.condition;
+ List<Statement> condEffects;
+ if (cond != null) {
+ condEffects = <Statement>[];
+ cond = expressionRewriter.rewrite(stmt.condition, condEffects);
+ }
+
+ if (isSimple) {
+ // If the condition contains await, we use a translation like the one for
+ // while loops, but leaving the variable declarations and the update
+ // expressions in place.
+ if (condEffects == null || condEffects.isEmpty) {
+ if (cond != null) stmt.condition = cond..parent = stmt;
+ stmt.body = body..parent = stmt;
+ statements.add(stmt);
+ } else {
+ LabeledStatement labeled = new LabeledStatement(stmt);
+ stmt.condition = null; // No condition in a for loop is the same as true.
asgerf 2016/11/01 10:44:45 Long line.
Kevin Millikin (Google) 2016/11/01 13:11:31 I just ran the whole file through the formatter.
+ condEffects.add(
+ new IfStatement(cond, body, new BreakStatement(labeled)));
+ stmt.body = new Block(condEffects)..parent = stmt;
+ statements.add(labeled);
+ }
+ return null;
+ }
+
+ // If the rewrite of the initializer or update expressions produces a
+ // non-empty sequence of statements then the loop is desugared. If the loop
+ // has the form:
+ //
+ // label: for (Type x = init; cond; update) body
+ //
+ // it is translated as if it were:
+ //
+ // {
+ // bool first = true;
+ // Type temp;
+ // label: while (true) {
+ // Type x;
+ // if (first) {
+ // first = false;
+ // x = init;
+ // } else {
+ // x = temp;
+ // update;
+ // }
+ // if (cond) {
+ // body;
+ // temp = x;
+ // } else {
+ // break;
+ // }
+ // }
+ // }
+
+ // Place the loop variable declarations at the beginning of the body
+ // statements and move their initializers to a guarded list of statements.
+ // Add assignments to the loop variables from the previous iteration's temp
+ // variables before the updates.
+ //
+ // temps.first is the flag 'first'.
+ // TODO(kmillikin) bool type for first.
+ List<VariableDeclaration> temps = <VariableDeclaration>[
+ new VariableDeclaration.forValue(new BoolLiteral(true),
+ isFinal: false)];
+ List<Statement> loopBody = <Statement>[];
+ List<Statement> initializers = <Statement>[
+ new ExpressionStatement(
+ new VariableSet(temps.first, new BoolLiteral(false)))];
+ List<Statment> updates = <Statement>[];
asgerf 2016/11/01 10:44:45 Statment -> Statement
Kevin Millikin (Google) 2016/11/01 13:11:31 Done.
+ List<Statement> newBody = <Statement>[body];
+ for (int i = 0; i < stmt.variables.length; ++i) {
+ VariableDeclaration decl = stmt.variables[i];
+ temps.add(new VariableDeclaration(null, type: decl.type));
+ loopBody.add(decl);
+ if (decl.initializer != null) {
+ initializers.addAll(initEffects[i]);
+ initializers.add(
+ new ExpressionStatement(new VariableSet(decl, decl.initializer)));
+ decl.initializer = null;
+ }
+ updates.add(new ExpressionStatement(
+ new VariableSet(decl, new VariableGet(temps.last))));
+ newBody.add(new ExpressionStatement(
+ new VariableSet(temps.last, new VariableGet(decl))));
+ }
+ // Add the updates to their guarded list of statements.
+ for (int i = 0; i < stmt.updates.length; ++i) {
+ updates.addAll(updateEffects[i]);
+ updates.add(new ExpressionStatement(stmt.updates[i]));
+ }
+ // Initializers or updates could be empty.
+ loopBody.add(new IfStatement(new VariableGet(temps.first),
+ new Block(initializers),
+ new Block(updates)));
+
+ LabeledStatement labeled = new LabeledStatement(null);
+ if (cond != null) {
+ loopBody.addAll(condEffects);
+ } else {
+ cond = new BoolLiteral(true);
+ }
+ loopBody.add(
+ new IfStatement(cond, new Block(newBody), new BreakStatement(labeled)));
+ labeled.body =
+ new WhileStatement(new BoolLiteral(true), new Block(loopBody));
+ statements.add(new Block(<Statement>[]..addAll(temps)..add(labeled)));
+ return null;
+ }
+
+ TreeNode visitForInStatement(ForInStatement stmt) {
+ if (stmt.isAsync) {
// Transform
//
// await for (var variable in <stream-expression>) { ... }
@@ -304,22 +544,22 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase {
':for-iterator',
initializer: new ConstructorInvocation(
helper.streamIteratorConstructor,
- new Arguments([expressionRewriter.rewrite(node.iterable)])));
+ new Arguments(<Expression>[stmt.iterable])));
// await iterator.moveNext()
var condition = new AwaitExpression(new MethodInvocation(
new VariableGet(iteratorVariable),
new Name('moveNext'),
- new Arguments([])));
+ new Arguments(<Expression>[])));
// var <variable> = iterator.current;
- var valueVariable = node.variable;
+ var valueVariable = stmt.variable;
valueVariable.initializer = new PropertyGet(
new VariableGet(iteratorVariable),
new Name('current'));
valueVariable.initializer.parent = valueVariable;
- var whileBody = new Block([valueVariable, node.body]);
+ var whileBody = new Block(<Statement>[valueVariable, stmt.body]);
var tryBody = new WhileStatement(condition, whileBody);
// iterator.cancel();
@@ -327,23 +567,105 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase {
new MethodInvocation(
new VariableGet(iteratorVariable),
new Name('cancel'),
- new Arguments([])));
+ new Arguments(<Expression>[])));
var tryFinally = new TryFinally(tryBody, tryFinalizer);
- var block = new Block([
- iteratorVariable,
- tryFinally,
- ]);
- return block.accept(this);
+ var block = new Block(<Statement>[iteratorVariable, tryFinally]);
+ block.accept(this);
} else {
- return super.visitForInStatement(node);
+ stmt.iterable =
+ expressionRewriter.rewrite(stmt.iterable, statements)..parent = stmt;
+ stmt.body = visitDelimited(stmt.body)..parent = stmt;
+ statements.add(stmt);
+ }
+ return null;
+ }
+
+ TreeNode visitSwitchStatement(SwitchStatement stmt) {
+ stmt.expression =
+ expressionRewriter.rewrite(stmt.expression, statements)..parent = stmt;
+ for (var switchCase in stmt.cases) {
+ // Expressions in switch cases cannot contain await so they do not need to
+ // be translated.
+ switchCase.body = visitDelimited(switchCase.body)..parent = switchCase;
+ }
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitContinueSwitchStatement(ContinueSwitchStatement stmt) {
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitIfStatement(IfStatement stmt) {
+ stmt.condition =
+ expressionRewriter.rewrite(stmt.condition, statements)..parent = stmt;
+ stmt.then = visitDelimited(stmt.then)..parent = stmt;
+ if (stmt.otherwise != null) {
+ stmt.otherwise = visitDelimited(stmt.otherwise)..parent = stmt;
+ }
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitReturnStatement(ReturnStatement stmt) {
+ if (stmt.expression != null) {
+ stmt.expression = expressionRewriter.rewrite(stmt.expression, statements)
+ ..parent = stmt;
+ }
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitTryCatch(TryCatch stmt) {
+ ++currentTryDepth;
+ stmt.body = visitDelimited(stmt.body)..parent = stmt;
+ --currentTryDepth;
+
+ ++currentCatchDepth;
+ for (var clause in stmt.catches) {
+ clause.body = visitDelimited(clause.body)..parent = clause;
+ }
+ --currentCatchDepth;
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitTryFinally(TryFinally stmt) {
+ ++currentTryDepth;
+ stmt.body = visitDelimited(stmt.body)..parent = stmt;
+ --currentTryDepth;
+ stmt.finalizer = visitDelimited(stmt.finalizer)..parent = stmt;
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitYieldStatement(YieldStatement stmt) {
+ stmt.expression =
+ expressionRewriter.rewrite(stmt.expression, statements)..parent = stmt;
+ statements.add(stmt);
+ return null;
+ }
+
+ TreeNode visitVariableDeclaration(VariableDeclaration stmt) {
+ if (stmt.initializer != null) {
+ stmt.initializer =
+ expressionRewriter.rewrite(stmt.initializer, statements)
+ ..parent = stmt;
}
+ statements.add(stmt);
+ return null;
}
- defaultExpression(TreeNode node) {
- return expressionRewriter.rewrite(node);
+ TreeNode visitFunctionDeclaration(FunctionDeclaration stmt) {
+ stmt.function = stmt.function.accept(this)..parent = stmt;
+ statements.add(stmt);
+ return null;
}
+
+ defaultExpression(TreeNode node) => throw 'unreachable';
}
class AsyncStarFunctionRewriter extends AsyncRewriterBase {
@@ -362,7 +684,8 @@ class AsyncStarFunctionRewriter extends AsyncRewriterBase {
super.setupAsyncContinuations(statements);
// :controller = new _AsyncController(:async_op);
- var arguments = new Arguments([new VariableGet(nestedClosureVariable)]);
+ var arguments =
+ new Arguments(<Expression>[new VariableGet(nestedClosureVariable)]);
var buildController = new ConstructorInvocation(
helper.streamControllerConstructor, arguments);
var setController = new ExpressionStatement(
@@ -381,50 +704,41 @@ class AsyncStarFunctionRewriter extends AsyncRewriterBase {
return enclosingFunction;
}
- Statement buildClosureBody(Statement node) {
- // The body will insert calls to
- // :controller.add()
- // :controller.addStream()
- // :controller.addError()
- // :controller.close()
- return node.accept(this);
- }
-
Statement buildCatchBody(exceptionVariable, stackTraceVariable) {
return new ExpressionStatement(
new MethodInvocation(
new VariableGet(controllerVariable),
new Name("completeError", helper.asyncLibrary),
- new Arguments([new VariableGet(exceptionVariable),
- new VariableGet(stackTraceVariable)])));
+ new Arguments(<Expression>[new VariableGet(exceptionVariable),
+ new VariableGet(stackTraceVariable)])));
}
- visitYieldStatement(YieldStatement node) {
- var transformedExpression = node.expression.accept(this);
+ TreeNode visitYieldStatement(YieldStatement stmt) {
+ Expression expr = expressionRewriter.rewrite(stmt.expression, statements);
var addExpression = new MethodInvocation(
- new VariableGet(controllerVariable),
- new Name(node.isYieldStar ? 'addStream' : 'add', helper.asyncLibrary),
- new Arguments([transformedExpression]));
+ new VariableGet(controllerVariable),
+ new Name(stmt.isYieldStar ? 'addStream' : 'add', helper.asyncLibrary),
+ new Arguments(<Expression>[expr]));
- var addAndReturnOrYield = new IfStatement(
+ statements.add(new IfStatement(
addExpression,
new ReturnStatement(new NullLiteral()),
- createContinuationPoint());
- return new Block([addAndReturnOrYield]);
+ createContinuationPoint()));
+ return null;
}
- visitReturnStatement(ReturnStatement node) {
+ TreeNode visitReturnStatement(ReturnStatement node) {
// async* functions cannot have normal [ReturnStatement]s in them.
assert(node.expression == null || node.expression is NullLiteral);
- var close = new ExpressionStatement(
+ statements.add(new ExpressionStatement(
new MethodInvocation(
- new VariableGet(controllerVariable),
- new Name("close", helper.asyncLibrary),
- new Arguments([])));
- var returnStatement = new ReturnStatement();
- return new Block([close, returnStatement]);
+ new VariableGet(controllerVariable),
+ new Name("close", helper.asyncLibrary),
+ new Arguments(<Expression>[]))));
+ statements.add(new ReturnStatement());
+ return null;
}
}
@@ -466,16 +780,6 @@ class AsyncFunctionRewriter extends AsyncRewriterBase {
return enclosingFunction;
}
- Statement buildClosureBody(Statement node) {
- // TODO(kustermann): Can we assume the frontend will insert proper
- // [ReturnStatement]s?
-
- // Translating the body will insert calls to
- // :completer.completeError()
- // :completer.complete()
- return node.accept(this);
- }
-
Statement buildCatchBody(exceptionVariable, stackTraceVariable) {
return new ExpressionStatement(
new MethodInvocation(
@@ -486,25 +790,20 @@ class AsyncFunctionRewriter extends AsyncRewriterBase {
}
visitReturnStatement(ReturnStatement node) {
- var transformedExpression;
+ var expr;
if (node.expression == null) {
- transformedExpression = new NullLiteral();
+ expr = new NullLiteral();
} else {
- transformedExpression = expressionRewriter.rewrite(node.expression);
+ expr = expressionRewriter.rewrite(node.expression, statements);
}
- // Note: transformed expression can't be used directly as part of the
- // method invocation because it might contain yield points and
- // expression stack might not be empty.
- var resultVar = new VariableDeclaration(':async_temp',
- initializer: transformedExpression);
- var completeCompleter = new ExpressionStatement(
+ statements.add(new ExpressionStatement(
new MethodInvocation(
new VariableGet(completerVariable),
new Name("complete", helper.asyncLibrary),
- new Arguments([new VariableGet(resultVar)])));
- var returnStatement = new ReturnStatement(new NullLiteral());
- return new Block([resultVar, completeCompleter, returnStatement]);
+ new Arguments([expr]))));
+ statements.add(new ReturnStatement(new NullLiteral()));
+ return null;
}
}
@@ -593,5 +892,5 @@ class HelperNodes {
findProcedure(asyncLibrary, '_asyncThenWrapperHelper'),
findProcedure(asyncLibrary, '_asyncErrorWrapperHelper'),
findProcedure(asyncLibrary, '_awaitHelper'));
- }
+ }
}

Powered by Google App Engine
This is Rietveld 408576698