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 9061b0217fc66ba5e4448e4190581e50a08b3a0f..de72dc78b914dd6a83a287ab3139c9650d7a0f07 100644 |
| --- a/pkg/kernel/lib/transformations/closure/converter.dart |
| +++ b/pkg/kernel/lib/transformations/closure/converter.dart |
| @@ -10,9 +10,11 @@ import '../../ast.dart' |
| Block, |
| Catch, |
| Class, |
| + ClosureCreation, |
| Constructor, |
| ConstructorInvocation, |
| DartType, |
| + DynamicType, |
| EmptyStatement, |
| Expression, |
| ExpressionStatement, |
| @@ -23,7 +25,9 @@ import '../../ast.dart' |
| FunctionDeclaration, |
| FunctionExpression, |
| FunctionNode, |
| + FunctionType, |
| Initializer, |
| + InterfaceType, |
| InvalidExpression, |
| InvocationExpression, |
| Let, |
| @@ -33,6 +37,7 @@ import '../../ast.dart' |
| MethodInvocation, |
| Name, |
| NamedExpression, |
| + NamedType, |
| NullLiteral, |
| Procedure, |
| ProcedureKind, |
| @@ -110,6 +115,7 @@ class ClosureConverter extends Transformer { |
| AstRewriter rewriter; |
| + /// TODO(29181): update this comment when the type variables are restored. |
| /// Maps original type variable (aka type parameter) to a hoisted type |
| /// variable type. |
| /// |
| @@ -252,6 +258,10 @@ class ClosureConverter extends Transformer { |
| return new BlockRewriter(body); |
| } |
| + bool isObject(DartType type) { |
| + return type is InterfaceType && type.classNode.supertype == null; |
| + } |
| + |
| Expression handleLocalFunction(FunctionNode function) { |
| FunctionNode enclosingFunction = currentFunction; |
| Map<TypeParameter, DartType> enclosingTypeSubstitution = typeSubstitution; |
| @@ -261,10 +271,8 @@ class ClosureConverter extends Transformer { |
| rewriter = makeRewriterForBody(function); |
| - VariableDeclaration contextVariable = new VariableDeclaration( |
| - "#contextParameter", |
| - type: const VectorType(), |
| - isFinal: true); |
| + VariableDeclaration contextVariable = |
| + new VariableDeclaration("#contextParameter", type: const VectorType()); |
| Context parent = context; |
| context = context.toNestedContext( |
| new VariableAccessor(contextVariable, null, TreeNode.noOffset)); |
| @@ -276,8 +284,32 @@ class ClosureConverter extends Transformer { |
| typeSubstitution = const <TypeParameter, DartType>{}; |
| } |
| + // TODO(29181): remove replacementTypeSubstitution variable and its usages. |
| + // All the type variables used in this function body are replaced with |
| + // either dynamic or their bounds. This is to temporarily remove the type |
| + // variables from closure conversion. They should be returned after the VM |
| + // changes are done to support vectors and closure creation. See #29181. |
| + Map<TypeParameter, DartType> replacementTypeSubstitution = |
| + <TypeParameter, DartType>{}; |
| + for (TypeParameter parameter in typeSubstitution.keys) { |
| + replacementTypeSubstitution[parameter] = const DynamicType(); |
| + } |
| + for (TypeParameter parameter in typeSubstitution.keys) { |
| + if (!isObject(parameter.bound)) { |
| + replacementTypeSubstitution[parameter] = |
| + substitute(parameter.bound, replacementTypeSubstitution); |
| + } |
| + } |
| + typeSubstitution = replacementTypeSubstitution; |
| function.transformChildren(this); |
| + // TODO(29181): don't replace typeSubstitution with an empty map. |
| + // Information about captured type variables is deleted from the closure |
| + // class, because the type variables in this function body are already |
| + // replaced with either dynamic or their bounds. This change should be |
| + // undone after the VM support for vectors and closure creation is |
| + // implemented. See #29181. |
| + typeSubstitution = <TypeParameter, DartType>{}; |
| Expression result = addClosure(function, contextVariable, parent.expression, |
| typeSubstitution, enclosingTypeSubstitution); |
| currentFunction = enclosingFunction; |
| @@ -312,64 +344,49 @@ class ClosureConverter extends Transformer { |
| }); |
| } |
| - /// Add a new class to the current library that looks like this: |
| + /// Add a new procedure to the current library that looks like this: |
| /// |
| - /// class Closure#0 extends core::Object implements core::Function { |
| - /// field Vector context; |
| - /// constructor •(final Vector #t1) → dynamic |
| - /// : self::Closure#0::context = #t1 |
| - /// ; |
| - /// method call(/* The parameters of [function] */) → dynamic { |
| - /// /// #t2 is [contextVariable]. |
| - /// final Vector #t2 = this.{self::Closure#0::context}; |
| - /// /* The body of [function]. */ |
| - /// } |
| - /// } |
| + /// static method closure#0(Vector #c, /* Parameters of [function]. */) |
| + /// → dynamic { |
| /// |
| - /// Returns a constructor call to invoke the above constructor. |
| + /// /* Context is represented by #c. */ |
| /// |
| - /// TODO(ahe): We shouldn't create a class for each closure. Instead we turn |
| - /// [function] into a top-level function and use the Dart VM's mechnism for |
| - /// closures. |
| + /// /* Body of [function]. */ |
| + /// |
| + /// } |
| + /// |
| + /// Returns an invocation of the closure creation primitive that binds the |
| + /// above top-level function to a context represented as Vector. |
| Expression addClosure( |
| FunctionNode function, |
| VariableDeclaration contextVariable, |
| Expression accessContext, |
| Map<TypeParameter, DartType> substitution, |
| Map<TypeParameter, DartType> enclosingTypeSubstitution) { |
| - Field contextField = new Field( |
| - // TODO(ahe): Rename to #context. |
| - new Name("context"), |
| - type: const VectorType(), |
| + function.positionalParameters.insert(0, contextVariable); |
| + ++function.requiredParameterCount; |
| + Procedure closedTopLevelFunction = new Procedure( |
| + new Name(createNameForClosedTopLevelFunction(function)), |
| + ProcedureKind.Method, |
| + function, |
| + isStatic: true, |
| fileUri: currentFileUri); |
| - Class closureClass = createClosureClass(function, |
| - fields: [contextField], substitution: substitution); |
| - closureClass.addMember(new Procedure( |
| - new Name("call"), ProcedureKind.Method, function, |
| - fileUri: currentFileUri)); |
| - newLibraryMembers.add(closureClass); |
| - Statement note = new ExpressionStatement( |
| - new StringLiteral("This is a temporary solution. " |
| - "In the VM, this will become an additional parameter.")); |
| - List<Statement> statements = <Statement>[note, contextVariable]; |
| - Statement body = function.body; |
| - if (body is Block) { |
| - statements.addAll(body.statements); |
| - } else { |
| - statements.add(body); |
| - } |
| - function.body = new Block(statements); |
| - function.body.parent = function; |
| - contextVariable.initializer = |
| - new PropertyGet(new ThisExpression(), contextField.name, contextField); |
| - contextVariable.initializer.parent = contextVariable; |
| - return new ConstructorInvocation( |
| - closureClass.constructors.single, |
| - new Arguments(<Expression>[accessContext], types: |
| - new List<DartType>.from(substitution.keys.map((TypeParameter t) { |
| - return substitute( |
| - new TypeParameterType(t), enclosingTypeSubstitution); |
| - })))); |
| + newLibraryMembers.add(closedTopLevelFunction); |
| + |
| + FunctionType closureType = new FunctionType( |
| + function.positionalParameters |
| + .map((VariableDeclaration decl) => decl.type) |
| + .toList()..removeAt(0), |
|
asgerf
2017/03/31 11:03:52
How about positionalParameters.skip(1).map(...)?
Dmitry Stefantsov
2017/03/31 11:54:13
I like the 'skip' approach better. Done. Thanks!
|
| + function.returnType, |
| + namedParameters: function.namedParameters |
| + .map((VariableDeclaration decl) => |
| + new NamedType(decl.name, decl.type)) |
| + .toList(), |
| + typeParameters: function.typeParameters, |
| + requiredParameterCount: function.requiredParameterCount - 1); |
| + |
| + return new ClosureCreation( |
| + closedTopLevelFunction.name.name, accessContext, closureType); |
| } |
| TreeNode visitField(Field node) { |
| @@ -403,7 +420,7 @@ class ClosureConverter extends Transformer { |
| node.name = tearOffName; |
| addGetterForwarder(oldName, node); |
| } else if (node.kind == ProcedureKind.Method) { |
| - addTearOffGetter(tearOffName, node); |
| + addTearOffMethod(tearOffName, node); |
| } |
| } |
| } |
| @@ -478,7 +495,13 @@ class ClosureConverter extends Transformer { |
| node.transformChildren(this); |
| if (!capturedVariables.contains(node)) return node; |
| - context.extend(node, node.initializer ?? new NullLiteral()); |
| + 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. |
| + context.extend(node, new VariableGet(node)); |
| + } else { |
| + context.extend(node, node.initializer ?? new NullLiteral()); |
| + } |
| if (node.parent == currentFunction) { |
| return node; |
| @@ -609,7 +632,11 @@ class ClosureConverter extends Transformer { |
| TreeNode visitStaticGet(StaticGet node) { |
| Member target = node.target; |
| if (target is Procedure && target.kind == ProcedureKind.Method) { |
| - Expression expression = getTearOffExpression(node.target); |
| + VariableDeclaration contextVariable = new VariableDeclaration( |
| + "#contextParameter", |
| + type: const VectorType()); |
| + Expression expression = getTearOffExpression( |
| + null, node.target, contextVariable, new NullLiteral()); |
| expression.transformChildren(this); |
| return expression; |
| } |
| @@ -619,7 +646,9 @@ class ClosureConverter extends Transformer { |
| TreeNode visitPropertyGet(PropertyGet node) { |
| Name tearOffName = tearOffGetterNames[node.name]; |
| if (tearOffName != null) { |
| - node.name = tearOffName; |
| + MethodInvocation replacement = new MethodInvocation( |
| + node.receiver, tearOffName, new Arguments(<Expression>[])); |
| + return super.visitMethodInvocation(replacement); |
| } |
| return super.visitPropertyGet(node); |
| } |
| @@ -652,9 +681,13 @@ class ClosureConverter extends Transformer { |
| return statement is Block ? statement : new Block(<Statement>[statement]); |
| } |
| - /// Creates a closure that will invoke [procedure] and return an expression |
| - /// that instantiates that closure. |
| - Expression getTearOffExpression(Procedure procedure) { |
| + /// Creates a closure that will invoke method [procedure] of [receiver] and |
| + /// return an expression that instantiates that closure. |
| + Expression getTearOffExpression( |
| + VariableDeclaration receiver, |
| + Procedure procedure, |
| + VariableDeclaration contextVariable, |
| + Expression accessContext) { |
| Map<TypeParameter, DartType> substitution = procedure.isInstanceMember |
| // Note: we do not attempt to avoid copying type variables that aren't |
| // used in the signature of [procedure]. It might be more economical to |
| @@ -663,49 +696,52 @@ class ClosureConverter extends Transformer { |
| // variables will be handled most efficiently. |
| ? copyTypeVariables(procedure.enclosingClass.typeParameters) |
| : const <TypeParameter, DartType>{}; |
| - Expression receiver = null; |
| - List<Field> fields = null; |
| - if (procedure.isInstanceMember) { |
| - // TODO(ahe): Rename to #self. |
| - Field self = new Field(new Name("self"), fileUri: currentFileUri); |
| - self.type = substitute(procedure.enclosingClass.thisType, substitution); |
| - fields = <Field>[self]; |
| - receiver = new PropertyGet(new ThisExpression(), self.name, self); |
| + |
| + // TODO(29181): remove variable `dynamicSubstitution` and replace its usages |
| + // with `substitution`. |
| + |
| + Map<TypeParameter, DartType> dynamicSubstitution = |
| + <TypeParameter, DartType>{}; |
| + for (TypeParameter parameter in substitution.keys) { |
| + dynamicSubstitution[parameter] = const DynamicType(); |
| + } |
| + for (TypeParameter parameter in substitution.keys) { |
| + if (!isObject(parameter.bound)) { |
| + dynamicSubstitution[parameter] = |
| + substitute(parameter.bound, dynamicSubstitution); |
| + } |
| } |
| // Find the closure class for the function. If there isn't one, create it. |
| - String closureClassName = createNameForClosureClass(procedure.function); |
| - Class closureClass = null; |
| + String closedTopLevelFunctionName = |
| + createNameForClosedTopLevelFunction(procedure.function); |
| + Procedure closedTopLevelFunction = null; |
| for (TreeNode node in newLibraryMembers) { |
| - if (node is Class && node.name == closureClassName) { |
| - closureClass = node; |
| + if (node is Procedure && node.name.name == closedTopLevelFunctionName) { |
| + closedTopLevelFunction = node; |
| } |
| } |
| - if (closureClass == null) { |
| - closureClass = createClosureClass(procedure.function, |
| - fields: fields, substitution: substitution); |
| - closureClass.addMember(new Procedure( |
| - new Name("call"), |
| + if (closedTopLevelFunction == null) { |
| + closedTopLevelFunction = new Procedure( |
| + new Name(closedTopLevelFunctionName), |
| ProcedureKind.Method, |
| - forwardFunction(procedure, receiver, substitution), |
| - fileUri: currentFileUri)); |
| - newLibraryMembers.add(closureClass); |
| + forwardFunction( |
| + procedure, receiver, contextVariable, dynamicSubstitution), |
| + isStatic: true, |
| + fileUri: currentFileUri); |
| + newLibraryMembers.add(closedTopLevelFunction); |
| } |
| - Arguments constructorArguments = procedure.isInstanceMember |
| - ? new Arguments(<Expression>[new ThisExpression()]) |
| - : new Arguments.empty(); |
| - if (substitution.isNotEmpty) { |
| - constructorArguments.types |
| - .addAll(procedure.enclosingClass.thisType.typeArguments); |
| - } |
| - return new ConstructorInvocation( |
| - closureClass.constructors.single, constructorArguments); |
| + return new ClosureCreation(closedTopLevelFunctionName, accessContext, |
| + procedure.function.functionType); |
| } |
| /// Creates a function that has the same signature as `procedure.function` |
| /// and which forwards all arguments to `procedure`. |
| - FunctionNode forwardFunction(Procedure procedure, Expression receiver, |
| + FunctionNode forwardFunction( |
| + Procedure procedure, |
| + VariableDeclaration receiver, |
| + VariableDeclaration contextVariable, |
| Map<TypeParameter, DartType> substitution) { |
| CloneVisitor cloner = substitution.isEmpty |
| ? this.cloner |
| @@ -715,6 +751,9 @@ class ClosureConverter extends Transformer { |
| function.typeParameters.map(cloner.clone).toList(); |
| List<VariableDeclaration> positionalParameters = |
| function.positionalParameters.map(cloner.clone).toList(); |
| + if (contextVariable != null) { |
| + positionalParameters.insert(0, contextVariable); |
| + } |
| List<VariableDeclaration> namedParameters = |
| function.namedParameters.map(cloner.clone).toList(); |
| @@ -724,6 +763,9 @@ class ClosureConverter extends Transformer { |
| List<Expression> positional = positionalParameters |
| .map((VariableDeclaration parameter) => new VariableGet(parameter)) |
| .toList(); |
| + if (contextVariable != null) { |
| + positional.removeAt(0); |
| + } |
| List<NamedExpression> named = |
| namedParameters.map((VariableDeclaration parameter) { |
| return new NamedExpression(parameter.name, new VariableGet(parameter)); |
| @@ -731,13 +773,18 @@ class ClosureConverter extends Transformer { |
| Arguments arguments = new Arguments(positional, types: types, named: named); |
| InvocationExpression invocation = procedure.isInstanceMember |
| - ? new MethodInvocation(receiver, procedure.name, arguments, procedure) |
| + ? new MethodInvocation( |
| + context.lookup(receiver), procedure.name, arguments, procedure) |
| : new StaticInvocation(procedure, arguments); |
| + int requiredParameterCount = function.requiredParameterCount; |
| + if (contextVariable != null) { |
| + ++requiredParameterCount; |
| + } |
| return new FunctionNode(new ReturnStatement(invocation), |
| typeParameters: typeParameters, |
| positionalParameters: positionalParameters, |
| namedParameters: namedParameters, |
| - requiredParameterCount: function.requiredParameterCount, |
| + requiredParameterCount: requiredParameterCount, |
| returnType: substitute(function.returnType, cloner.typeSubstitution)); |
| } |
| @@ -759,42 +806,8 @@ class ClosureConverter extends Transformer { |
| return substitution; |
| } |
| - String createNameForClosureClass(FunctionNode function) { |
| - return 'Closure#${localNames[function]}'; |
| - } |
| - |
| - Class createClosureClass(FunctionNode function, |
| - {List<Field> fields, Map<TypeParameter, DartType> substitution}) { |
| - List<TypeParameter> typeParameters = new List<TypeParameter>.from( |
| - substitution.values |
| - .map((DartType t) => (t as TypeParameterType).parameter)); |
| - Class closureClass = new Class( |
| - name: createNameForClosureClass(function), |
| - supertype: new Supertype(coreTypes.objectClass, const <DartType>[]), |
| - typeParameters: typeParameters, |
| - implementedTypes: <Supertype>[ |
| - new Supertype(coreTypes.functionClass, const <DartType>[]) |
| - ], |
| - fileUri: currentFileUri); |
| - addClosureClassNote(closureClass); |
| - |
| - List<VariableDeclaration> parameters = <VariableDeclaration>[]; |
| - List<Initializer> initializers = <Initializer>[]; |
| - for (Field field in fields ?? const <Field>[]) { |
| - closureClass.addMember(field); |
| - VariableDeclaration parameter = new VariableDeclaration(field.name.name, |
| - type: field.type, isFinal: true); |
| - parameters.add(parameter); |
| - initializers.add(new FieldInitializer(field, new VariableGet(parameter))); |
| - } |
| - |
| - closureClass.addMember(new Constructor( |
| - new FunctionNode(new EmptyStatement(), |
| - positionalParameters: parameters), |
| - name: new Name(""), |
| - initializers: initializers)); |
| - |
| - return closureClass; |
| + String createNameForClosedTopLevelFunction(FunctionNode function) { |
| + return 'closure#${localNames[function]}'; |
| } |
| Statement forwardToThisProperty(Member node) { |
| @@ -822,19 +835,54 @@ class ClosureConverter extends Transformer { |
| .add(copyWithBody(getter, forwardToThisProperty(getter))..name = name); |
| } |
| - void addTearOffGetter(Name name, Procedure procedure) { |
| - newClassMembers.add(new Procedure(name, ProcedureKind.Getter, |
| - new FunctionNode(new ReturnStatement(getTearOffExpression(procedure))), |
| - fileUri: currentFileUri)); |
| - } |
| + void addTearOffMethod(Name name, Procedure procedure) { |
| + // [addTearOffMethod] generates a method along with a context that captures |
| + // `this`. The work with contexts is typically done using the data gathered |
| + // by a [ClosureInfo] instance. In absence of this information, we need to |
| + // create some variables, like `#self` and `#context`, and manipulate |
| + // contexts directly in some cases. |
| + // |
| + // Also, the tear-off method is generated during a visit to the AST node |
| + // of the procedure being torn off, so we need to save and restore some |
| + // auxiliary variables like `currentMember` and `currentMemberFunction` |
| + // and use [saveContext], so that those variables have proper values when |
| + // the procedure itself is being transformed. |
| + Member oldCurrentMember = currentMember; |
| + FunctionNode oldCurrentMemberFunction = currentMemberFunction; |
| + try { |
| + saveContext(() { |
| + Block body = new Block(<Statement>[]); |
| + FunctionNode tearOffMethodFunction = new FunctionNode(body); |
| + setupContextForFunctionBody(tearOffMethodFunction); |
| + |
| + // We need a variable that refers to `this` to put it into the context. |
| + VariableDeclaration self = new VariableDeclaration("#self", |
| + type: procedure.enclosingClass.rawType); |
| + context.extend(self, new ThisExpression()); |
| - // TODO(ahe): Remove this method when we don't generate closure classes |
| - // anymore. |
| - void addClosureClassNote(Class closureClass) { |
| - closureClass.addMember(new Field(new Name("note"), |
| - type: coreTypes.stringClass.rawType, |
| - initializer: new StringLiteral( |
| - "This is temporary. The VM doesn't need closure classes."), |
| - fileUri: currentFileUri)); |
| + // The `#context` variable is used to access the context in the closed |
| + // top-level function that represents the closure and is generated in |
| + // [getTearOffExpression]. |
| + VariableDeclaration contextVariable = new VariableDeclaration( |
| + "#contextParameter", |
| + type: const VectorType()); |
| + Context parent = context; |
| + context = context.toNestedContext( |
| + new VariableAccessor(contextVariable, null, TreeNode.noOffset)); |
| + |
| + body.addStatement(new ReturnStatement(getTearOffExpression( |
| + self, procedure, contextVariable, parent.expression))); |
| + |
| + Procedure tearOffMethod = new Procedure( |
| + name, ProcedureKind.Method, tearOffMethodFunction, |
| + fileUri: currentFileUri); |
| + newClassMembers.add(tearOffMethod); |
| + |
| + resetContext(); |
| + }); |
| + } finally { |
| + currentMember = oldCurrentMember; |
| + currentMemberFunction = oldCurrentMemberFunction; |
| + } |
| } |
| } |