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

Unified Diff: pkg/kernel/lib/transformations/closure/converter.dart

Issue 2991853002: Fix duplicate context creation when closures appear in initializers. (Closed)
Patch Set: Review comments. Created 3 years, 4 months 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
« no previous file with comments | « no previous file | pkg/kernel/lib/transformations/closure/info.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/kernel/lib/transformations/closure/converter.dart
diff --git a/pkg/kernel/lib/transformations/closure/converter.dart b/pkg/kernel/lib/transformations/closure/converter.dart
index 713ab38cdc6995e812fabb5dad3932f2fabd491d..42562b2992eedb5092a7e89d5a3f018ca2704a0b 100644
--- a/pkg/kernel/lib/transformations/closure/converter.dart
+++ b/pkg/kernel/lib/transformations/closure/converter.dart
@@ -23,11 +23,13 @@ import '../../ast.dart'
FunctionExpression,
FunctionNode,
FunctionType,
+ Initializer,
InterfaceType,
InvalidExpression,
InvocationExpression,
Library,
LocalInitializer,
+ RedirectingInitializer,
Member,
MethodInvocation,
Name,
@@ -61,7 +63,7 @@ import '../../type_algebra.dart' show substitute;
import 'clone_without_body.dart' show CloneWithoutBody;
-import 'context.dart' show Context, NoContext;
+import 'context.dart' show Context, NoContext, LocalContext;
import 'info.dart' show ClosureInfo;
@@ -69,7 +71,12 @@ import 'rewriter.dart' show AstRewriter, BlockRewriter, InitializerListRewriter;
class ClosureConverter extends Transformer {
final CoreTypes coreTypes;
- final Set<VariableDeclaration> capturedVariables;
+
+ // This map pairs variables that are captured with flags indicating whether
+ // they are captured inside or outside an initializer. See
+ // [ClosureInfo.variables].
+ final Map<VariableDeclaration, int> capturedVariables;
+
final Map<FunctionNode, Set<TypeParameter>> capturedTypeVariables;
final Map<FunctionNode, VariableDeclaration> thisAccess;
final Map<FunctionNode, String> localNames;
@@ -181,44 +188,125 @@ class ClosureConverter extends Transformer {
return node;
}
- void extendContextWith(VariableDeclaration parameter) {
- context.extend(parameter, new VariableGet(parameter));
+ extendContextConditionally({bool inInitializer}) {
+ return (VariableDeclaration parameter) {
+ int flags = capturedVariables[parameter];
+
+ // When moving variables into the context while scanning initializers,
+ // we need to add the variable if it's captured in an initializer,
+ // whether or not it's used/captured in the body. However, in the body,
+ // we only need to add the variable into the context if it's *not*
+ // captured in an initializer.
+ if (flags != null && inInitializer
+ ? (flags & ClosureInfo.INSIDE_INITIALIZER) > 0
+ : flags == ClosureInfo.OUTSIDE_INITIALIZER) {
+ context.extend(parameter, new VariableGet(parameter));
+ }
+ return flags ?? 0;
+ };
}
TreeNode visitConstructor(Constructor node) {
assert(isEmptyContext);
currentMember = node;
+ // If we created a context for the initializers, we need to re-use that
+ // context in the body of the function. Unfortunately, the context is
+ // declared in a local initializer and local initializers aren't visible
+ // in the body of the constructor. To work around this issue, we move the
+ // body into a new constructor and make this constructor redirect to that
+ // one, passing the context as an argument to the new constructor.
+ var movingCtor = false;
+
// Transform initializers.
if (node.initializers.length > 0) {
var initRewriter = new InitializerListRewriter(node);
rewriter = initRewriter;
context = new NoContext(this);
+ final int capturedBoth =
+ ClosureInfo.OUTSIDE_INITIALIZER | ClosureInfo.INSIDE_INITIALIZER;
+
// TODO(karlklose): add a fine-grained analysis of captured parameters.
- node.function.positionalParameters
- .where(capturedVariables.contains)
- .forEach(extendContextWith);
- node.function.namedParameters
- .where(capturedVariables.contains)
- .forEach(extendContextWith);
+ movingCtor = node.function.positionalParameters
+ .map(extendContextConditionally(inInitializer: true))
+ .any((flags) => flags == capturedBoth) ||
+ node.function.namedParameters
+ .map(extendContextConditionally(inInitializer: true))
+ .any((flags) => flags == capturedBoth);
transformList(node.initializers, this, node);
node.initializers.insertAll(0, initRewriter.prefix);
- context = rewriter = null;
+ rewriter = null;
}
// Transform constructor body.
FunctionNode function = node.function;
if (function.body != null && function.body is! EmptyStatement) {
- setupContextForFunctionBody(function);
+ setupRewriterForFunctionBody(function);
+ if (!movingCtor) context = new NoContext(this);
VariableDeclaration self = thisAccess[currentMemberFunction];
if (self != null) {
context.extend(self, new ThisExpression());
}
node.function.accept(this);
- resetContext();
+
+ if (movingCtor) {
+ var contextDecl = (context as LocalContext).self;
+ var newCtorName = new Name("${node.name.name}#redir");
+ var newCtor = new Constructor(node.function, name: newCtorName);
+ newClassMembers.add(newCtor);
+
+ LocalInitializer contextDeclInit = null;
+ for (var init in node.initializers) {
+ if (init is LocalInitializer && init.variable == contextDecl) {
+ contextDeclInit = init;
+ } else {
+ newCtor.initializers.add(init);
+ }
+ }
+
+ node.initializers = <Initializer>[contextDeclInit];
+
+ var cv = new CloneVisitor();
+ var oldCtorParams = function.positionalParameters
+ .map(cv.visitVariableDeclaration)
+ .toList();
+ var oldCtorNamedParams =
+ function.namedParameters.map(cv.visitVariableDeclaration).toList();
+
+ function.positionalParameters.addAll(function.namedParameters);
+ function.namedParameters = [];
+
+ var args = <Expression>[];
+ args.addAll(oldCtorParams.map((decl) => new VariableGet(decl)));
+ args.addAll(oldCtorNamedParams.map((decl) => new VariableGet(decl)));
+
+ node.function = new FunctionNode(new EmptyStatement(),
+ typeParameters: [],
+ positionalParameters: oldCtorParams,
+ namedParameters: oldCtorNamedParams,
+ requiredParameterCount: function.requiredParameterCount,
+ returnType: function.returnType,
+ asyncMarker: function.asyncMarker,
+ dartAsyncMarker: function.dartAsyncMarker);
+ node.function.parent = node;
+
+ var oldCtorDecl = cv.visitVariableDeclaration(contextDecl);
+ contextDecl.initializer = null;
+ function.positionalParameters.add(contextDecl);
+ function.requiredParameterCount++;
+
+ contextDeclInit.variable = oldCtorDecl;
+ oldCtorDecl.parent = contextDeclInit;
+
+ args.add(new VariableGet(oldCtorDecl));
+ var redirInit =
+ new RedirectingInitializer(newCtor, new Arguments(args));
+ node.initializers.add(redirInit);
+ }
}
+ resetContext();
return node;
}
@@ -274,7 +362,7 @@ class ClosureConverter extends Transformer {
TreeNode visitFunctionDeclaration(FunctionDeclaration node) {
/// Is this closure itself captured by a closure?
- bool isCaptured = capturedVariables.contains(node.variable);
+ bool isCaptured = capturedVariables.containsKey(node.variable);
if (isCaptured) {
context.extend(node.variable, new InvalidExpression());
}
@@ -368,7 +456,11 @@ class ClosureConverter extends Transformer {
FunctionNode function = node.function;
if (function.body != null) {
- setupContextForFunctionBody(function);
+ setupRewriterForFunctionBody(function);
+ // Start with no context. This happens after setting up _currentBlock
+ // so statements can be emitted into _currentBlock if necessary.
+ context = new NoContext(this);
+
VariableDeclaration self = thisAccess[currentMemberFunction];
if (self != null) {
context.extend(self, new ThisExpression());
@@ -380,15 +472,12 @@ class ClosureConverter extends Transformer {
return node;
}
- void setupContextForFunctionBody(FunctionNode function) {
+ void setupRewriterForFunctionBody(FunctionNode function) {
Statement body = function.body;
assert(body != null);
currentMemberFunction = function;
// Ensure that the body is a block which becomes the current block.
rewriter = makeRewriterForBody(function);
- // Start with no context. This happens after setting up _currentBlock
- // so statements can be emitted into _currentBlock if necessary.
- context = new NoContext(this);
}
void resetContext() {
@@ -403,21 +492,20 @@ class ClosureConverter extends Transformer {
}
TreeNode visitLocalInitializer(LocalInitializer node) {
- assert(!capturedVariables.contains(node.variable));
+ assert(!capturedVariables.containsKey(node.variable));
node.transformChildren(this);
return node;
}
TreeNode visitFunctionNode(FunctionNode node) {
transformList(node.typeParameters, this, node);
- // TODO: Can parameters contain initializers (e.g., for optional ones) that
- // need to be closure converted?
+ // Initializers for optional parameters must be compile-time constants,
+ // which excludes closures. Therefore, we can avoid looking for closures in
+ // initializers of the parameters.
node.positionalParameters
- .where(capturedVariables.contains)
- .forEach(extendContextWith);
+ .forEach(extendContextConditionally(inInitializer: false));
node.namedParameters
- .where(capturedVariables.contains)
- .forEach(extendContextWith);
+ .forEach(extendContextConditionally(inInitializer: false));
assert(node.body != null);
node.body = node.body.accept(this);
node.body.parent = node;
@@ -435,7 +523,7 @@ class ClosureConverter extends Transformer {
TreeNode visitVariableDeclaration(VariableDeclaration node) {
node.transformChildren(this);
- if (!capturedVariables.contains(node)) return node;
+ if (!capturedVariables.containsKey(node)) return node;
if (node.initializer == null && node.parent is FunctionNode) {
// If the variable is a function parameter and doesn't have an
// initializer, just use this variable name to put it into the context.
@@ -455,7 +543,7 @@ class ClosureConverter extends Transformer {
}
TreeNode visitVariableGet(VariableGet node) {
- return capturedVariables.contains(node.variable)
+ return capturedVariables.containsKey(node.variable)
? context.lookup(node.variable)
: node;
}
@@ -463,7 +551,7 @@ class ClosureConverter extends Transformer {
TreeNode visitVariableSet(VariableSet node) {
node.transformChildren(this);
- return capturedVariables.contains(node.variable)
+ return capturedVariables.containsKey(node.variable)
? context.assign(node.variable, node.value,
voidContext: isInVoidContext(node))
: node;
@@ -499,7 +587,7 @@ class ClosureConverter extends Transformer {
}
TreeNode visitForStatement(ForStatement node) {
- if (node.variables.any(capturedVariables.contains)) {
+ if (node.variables.any(capturedVariables.containsKey)) {
// In Dart, loop variables are new variables on each iteration of the
// loop. This is only observable when a loop variable is captured by a
// closure, which is the situation we're in here. So we transform the
@@ -536,7 +624,7 @@ class ClosureConverter extends Transformer {
}
TreeNode visitForInStatement(ForInStatement node) {
- if (capturedVariables.contains(node.variable)) {
+ if (capturedVariables.containsKey(node.variable)) {
// In Dart, loop variables are new variables on each iteration of the
// loop. This is only observable when the loop variable is captured by a
// closure, so we need to transform the for-in loop when `node.variable`
@@ -573,7 +661,7 @@ class ClosureConverter extends Transformer {
TreeNode visitCatch(Catch node) {
VariableDeclaration exception = node.exception;
VariableDeclaration stackTrace = node.stackTrace;
- if (stackTrace != null && capturedVariables.contains(stackTrace)) {
+ if (stackTrace != null && capturedVariables.containsKey(stackTrace)) {
Block block = node.body = ensureBlock(node.body);
block.parent = node;
node.stackTrace = new VariableDeclaration(null);
@@ -582,7 +670,7 @@ class ClosureConverter extends Transformer {
block.statements.insert(0, stackTrace);
stackTrace.parent = block;
}
- if (exception != null && capturedVariables.contains(exception)) {
+ if (exception != null && capturedVariables.containsKey(exception)) {
Block block = node.body = ensureBlock(node.body);
block.parent = node;
node.exception = new VariableDeclaration(null);
« no previous file with comments | « no previous file | pkg/kernel/lib/transformations/closure/info.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698