Chromium Code Reviews| 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..3772328961a711496ee484e0ed36f20e3d5df43f 100644 |
| --- a/pkg/kernel/lib/transformations/closure/converter.dart |
| +++ b/pkg/kernel/lib/transformations/closure/converter.dart |
| @@ -28,6 +28,7 @@ import '../../ast.dart' |
| InvocationExpression, |
| Library, |
| LocalInitializer, |
| + RedirectingInitializer, |
| Member, |
| MethodInvocation, |
| Name, |
| @@ -61,7 +62,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 +70,8 @@ import 'rewriter.dart' show AstRewriter, BlockRewriter, InitializerListRewriter; |
| class ClosureConverter extends Transformer { |
| final CoreTypes coreTypes; |
| - final Set<VariableDeclaration> capturedVariables; |
| + final Map<VariableDeclaration, int /*capture flags -- see info.dart*/ > |
|
Dmitry Stefantsov
2017/07/31 15:05:35
Please, add put this comment on its own line above
sjindel
2017/07/31 15:32:16
Done.
|
| + capturedVariables; |
| final Map<FunctionNode, Set<TypeParameter>> capturedTypeVariables; |
| final Map<FunctionNode, VariableDeclaration> thisAccess; |
| final Map<FunctionNode, String> localNames; |
| @@ -181,44 +183,125 @@ class ClosureConverter extends Transformer { |
| return node; |
| } |
| - void extendContextWith(VariableDeclaration parameter) { |
| - context.extend(parameter, new VariableGet(parameter)); |
| - } |
| + extendContextConditionally(bool inInitializer) => |
|
Dmitry Stefantsov
2017/07/31 15:05:35
As Karl pointed out, the arrow notation is usually
Dmitry Stefantsov
2017/07/31 15:05:35
I would suggest to make [inInitializer] a named pa
sjindel
2017/07/31 15:32:16
Done.
sjindel
2017/07/31 15:32:16
Done.
|
| + (VariableDeclaration parameter) { |
| + var captured = capturedVariables[parameter]; |
|
Dmitry Stefantsov
2017/07/31 15:05:35
Also I think that the name [captured] is a bit mis
Dmitry Stefantsov
2017/07/31 15:05:35
I think using type annotation `int` here helps wit
sjindel
2017/07/31 15:32:16
Done.
sjindel
2017/07/31 15:32:16
Done.
|
| + |
| + // 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 (captured != null && inInitializer |
| + ? (captured & ClosureInfo.INSIDE_INITIALIZER) > 0 |
| + : captured == ClosureInfo.OUTSIDE_INITIALIZER) { |
| + context.extend(parameter, new VariableGet(parameter)); |
| + } |
| + return captured ?? 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); |
| + 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(true)) |
| + .any((flags) => flags == capturedBoth) || |
| + node.function.namedParameters |
| + .map(extendContextConditionally(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); |
| + 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) { |
|
Dmitry Stefantsov
2017/07/31 15:05:35
This version is definitely better. I was thinking
sjindel
2017/07/31 15:32:16
True, but I feel that this approach keeps the logi
Dmitry Stefantsov
2017/08/01 09:36:13
But it's not completely isolated. I think that su
|
| + if (init is LocalInitializer && init.variable == contextDecl) { |
| + contextDeclInit = init; |
| + } else { |
| + newCtor.initializers.add(init); |
| + } |
| + } |
| + |
| + node.initializers = node.initializers.take(1).toList(); |
| + |
| + 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>[]; |
| + assert(function.typeParameters.length == 0); |
| + 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 +357,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()); |
| } |
| @@ -369,6 +452,10 @@ class ClosureConverter extends Transformer { |
| FunctionNode function = node.function; |
| if (function.body != null) { |
| setupContextForFunctionBody(function); |
| + // Start with no context. This happens after setting up _currentBlock |
| + // so statements can be emitted into _currentBlock if necessary. |
| + context = new NoContext(this); |
|
Dmitry Stefantsov
2017/07/31 15:05:35
Why was [context] initialization moved here?
sjindel
2017/07/31 15:32:16
It didn't really make sense to do it in the functi
Dmitry Stefantsov
2017/08/01 09:36:13
If we had a special kind of rewriter for the case
|
| + |
| VariableDeclaration self = thisAccess[currentMemberFunction]; |
| if (self != null) { |
| context.extend(self, new ThisExpression()); |
| @@ -386,9 +473,6 @@ class ClosureConverter extends Transformer { |
| 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 +487,17 @@ 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? |
| - node.positionalParameters |
| - .where(capturedVariables.contains) |
| - .forEach(extendContextWith); |
| - node.namedParameters |
| - .where(capturedVariables.contains) |
| - .forEach(extendContextWith); |
| + // Initializers for optional parameters must be compile-time constants, |
| + // which excludes closures. |
| + node.positionalParameters.forEach(extendContextConditionally(false)); |
| + node.namedParameters.forEach(extendContextConditionally(false)); |
| assert(node.body != null); |
| node.body = node.body.accept(this); |
| node.body.parent = node; |
| @@ -435,7 +515,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 +535,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 +543,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 +579,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 +616,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 +653,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 +662,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); |