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 |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8f40b7cd7a1bf22efbdeb0637252d68cfc89510a |
--- /dev/null |
+++ b/pkg/kernel/lib/transformations/closure/converter.dart |
@@ -0,0 +1,792 @@ |
+// Copyright (c) 2016, 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. |
+ |
+library kernel.transformations.closure.converter; |
+ |
+import '../../ast.dart' |
+ show |
+ Arguments, |
+ Block, |
+ Catch, |
+ Class, |
+ Constructor, |
+ ConstructorInvocation, |
+ DartType, |
+ EmptyStatement, |
+ Expression, |
+ ExpressionStatement, |
+ Field, |
+ FieldInitializer, |
+ ForInStatement, |
+ ForStatement, |
+ FunctionDeclaration, |
+ FunctionExpression, |
+ FunctionNode, |
+ InferredValue, |
+ Initializer, |
+ InvalidExpression, |
+ InvocationExpression, |
+ Library, |
+ LocalInitializer, |
+ Member, |
+ MethodInvocation, |
+ Name, |
+ NamedExpression, |
+ NullLiteral, |
+ Procedure, |
+ ProcedureKind, |
+ PropertyGet, |
+ ReturnStatement, |
+ Statement, |
+ StaticGet, |
+ StaticInvocation, |
+ StringLiteral, |
+ Supertype, |
+ ThisExpression, |
+ Transformer, |
+ TreeNode, |
+ TypeParameter, |
+ TypeParameterType, |
+ VariableDeclaration, |
+ VariableGet, |
+ VariableSet, |
+ transformList; |
+ |
+import '../../frontend/accessors.dart' show VariableAccessor; |
+ |
+import '../../clone.dart' show CloneVisitor; |
+ |
+import '../../core_types.dart' show CoreTypes; |
+ |
+import '../../type_algebra.dart' show substitute; |
+ |
+import 'clone_without_body.dart' show CloneWithoutBody; |
+ |
+import 'context.dart' show Context, NoContext; |
+ |
+import 'info.dart' show ClosureInfo; |
+ |
+class ClosureConverter extends Transformer { |
+ final CoreTypes coreTypes; |
+ final Class contextClass; |
+ final Set<VariableDeclaration> capturedVariables; |
+ final Map<FunctionNode, Set<TypeParameter>> capturedTypeVariables; |
+ final Map<FunctionNode, VariableDeclaration> thisAccess; |
+ final Map<FunctionNode, String> localNames; |
+ |
+ /// Records place-holders for cloning contexts. See [visitForStatement]. |
+ final Set<InvalidExpression> contextClonePlaceHolders = |
+ new Set<InvalidExpression>(); |
+ |
+ /// Maps the names of all instance methods that may be torn off (aka |
+ /// implicitly closurized) to `${name.name}#get`. |
+ final Map<Name, Name> tearOffGetterNames; |
+ |
+ final CloneVisitor cloner = new CloneWithoutBody(); |
+ |
+ /// New members to add to [currentLibrary] after it has been |
+ /// transformed. These members will not be transformed themselves. |
+ final List<TreeNode> newLibraryMembers = <TreeNode>[]; |
+ |
+ /// New members to add to [currentClass] after it has been transformed. These |
+ /// members will not be transformed themselves. |
+ final List<Member> newClassMembers = <Member>[]; |
+ |
+ Library currentLibrary; |
+ |
+ Class currentClass; |
+ |
+ Member currentMember; |
+ |
+ FunctionNode currentMemberFunction; |
+ |
+ FunctionNode currentFunction; |
+ |
+ Block _currentBlock; |
+ |
+ int _insertionIndex = 0; |
+ |
+ Context context; |
+ |
+ /// Maps original type variable (aka type parameter) to a hoisted type |
+ /// variable type. |
+ /// |
+ /// For example, consider: |
+ /// |
+ /// class C<T> { |
+ /// f() => (x) => x is T; |
+ /// } |
+ /// |
+ /// This is currently converted to: |
+ /// |
+ /// class C<T> { |
+ /// f() => new Closure#0<T>(); |
+ /// } |
+ /// class Closure#0<T_> implements Function { |
+ /// call(x) => x is T_; |
+ /// } |
+ /// |
+ /// In this example, `typeSubstitution[T].parameter == T_` when transforming |
+ /// the closure in `f`. |
+ Map<TypeParameter, DartType> typeSubstitution = |
+ const <TypeParameter, DartType>{}; |
+ |
+ ClosureConverter(this.coreTypes, ClosureInfo info, this.contextClass) |
+ : this.capturedVariables = info.variables, |
+ this.capturedTypeVariables = info.typeVariables, |
+ this.thisAccess = info.thisAccess, |
+ this.localNames = info.localNames, |
+ this.tearOffGetterNames = info.tearOffGetterNames; |
+ |
+ bool get isOuterMostContext { |
+ return currentFunction == null || currentMemberFunction == currentFunction; |
+ } |
+ |
+ String get currentFileUri { |
+ if (currentMember is Constructor) return currentClass.fileUri; |
+ if (currentMember is Field) return (currentMember as Field).fileUri; |
+ if (currentMember is Procedure) return (currentMember as Procedure).fileUri; |
+ throw "No file uri"; |
+ } |
+ |
+ void insert(Statement statement) { |
+ _currentBlock.statements.insert(_insertionIndex++, statement); |
+ statement.parent = _currentBlock; |
+ } |
+ |
+ TreeNode saveContext(TreeNode f()) { |
+ Block savedBlock = _currentBlock; |
+ int savedIndex = _insertionIndex; |
+ Context savedContext = context; |
+ try { |
+ return f(); |
+ } finally { |
+ _currentBlock = savedBlock; |
+ _insertionIndex = savedIndex; |
+ context = savedContext; |
+ } |
+ } |
+ |
+ TreeNode visitLibrary(Library node) { |
+ assert(newLibraryMembers.isEmpty); |
+ if (node == contextClass.enclosingLibrary) return node; |
+ currentLibrary = node; |
+ node = super.visitLibrary(node); |
+ for (TreeNode member in newLibraryMembers) { |
+ if (member is Class) { |
+ node.addClass(member); |
+ } else { |
+ node.addMember(member); |
+ } |
+ } |
+ newLibraryMembers.clear(); |
+ currentLibrary = null; |
+ return node; |
+ } |
+ |
+ TreeNode visitClass(Class node) { |
+ assert(newClassMembers.isEmpty); |
+ currentClass = node; |
+ node = super.visitClass(node); |
+ newClassMembers.forEach(node.addMember); |
+ newClassMembers.clear(); |
+ currentClass = null; |
+ return node; |
+ } |
+ |
+ TreeNode visitConstructor(Constructor node) { |
+ // TODO(ahe): Convert closures in constructors as well. |
+ return node; |
+ } |
+ |
+ Expression handleLocalFunction(FunctionNode function) { |
+ FunctionNode enclosingFunction = currentFunction; |
+ Map<TypeParameter, DartType> enclosingTypeSubstitution = typeSubstitution; |
+ currentFunction = function; |
+ Statement body = function.body; |
+ assert(body != null); |
+ |
+ if (body is Block) { |
+ _currentBlock = body; |
+ } else { |
+ _currentBlock = new Block(<Statement>[body]); |
+ function.body = body.parent = _currentBlock; |
+ } |
+ _insertionIndex = 0; |
+ |
+ VariableDeclaration contextVariable = new VariableDeclaration( |
+ "#contextParameter", |
+ type: contextClass.rawType, |
+ isFinal: true); |
+ Context parent = context; |
+ context = context.toNestedContext(new VariableAccessor(contextVariable)); |
+ |
+ Set<TypeParameter> captured = capturedTypeVariables[currentFunction]; |
+ if (captured != null) { |
+ typeSubstitution = copyTypeVariables(captured); |
+ } else { |
+ typeSubstitution = const <TypeParameter, DartType>{}; |
+ } |
+ |
+ function.transformChildren(this); |
+ |
+ Expression result = addClosure(function, contextVariable, parent.expression, |
+ typeSubstitution, enclosingTypeSubstitution); |
+ currentFunction = enclosingFunction; |
+ typeSubstitution = enclosingTypeSubstitution; |
+ return result; |
+ } |
+ |
+ TreeNode visitFunctionDeclaration(FunctionDeclaration node) { |
+ /// Is this closure itself captured by a closure? |
+ bool isCaptured = capturedVariables.contains(node.variable); |
+ if (isCaptured) { |
+ context.extend(node.variable, new InvalidExpression()); |
+ } |
+ Context parent = context; |
+ return saveContext(() { |
+ Expression expression = handleLocalFunction(node.function); |
+ |
+ if (isCaptured) { |
+ parent.update(node.variable, expression); |
+ return null; |
+ } else { |
+ node.variable.initializer = expression; |
+ expression.parent = node.variable; |
+ return node.variable; |
+ } |
+ }); |
+ } |
+ |
+ TreeNode visitFunctionExpression(FunctionExpression node) => saveContext(() { |
+ return handleLocalFunction(node.function); |
+ }); |
+ |
+ /// Add a new class to the current library that looks like this: |
+ /// |
+ /// class Closure#0 extends core::Object implements core::Function { |
+ /// field _in::Context context; |
+ /// constructor •(final _in::Context #t1) → dynamic |
+ /// : self::Closure 0::context = #t1 |
+ /// ; |
+ /// method call(/* The parameters of [function] */) → dynamic { |
+ /// /// #t2 is [contextVariable]. |
+ /// final _in::Context #t2 = this.{self::Closure#0::context}; |
+ /// /* The body of [function]. */ |
+ /// } |
+ /// } |
+ /// |
+ /// Returns a constructor call to invoke the above constructor. |
+ /// |
+ /// 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. |
+ 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: contextClass.rawType, |
+ 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); |
+ })))); |
+ } |
+ |
+ TreeNode visitField(Field node) { |
+ currentMember = node; |
+ context = new NoContext(this); |
+ if (node.isInstanceMember) { |
+ Name tearOffName = tearOffGetterNames[node.name]; |
+ if (tearOffName != null) { |
+ // TODO(ahe): If we rewrite setters, we can rename the field to avoid |
+ // an indirection in most cases. |
+ addFieldForwarder(tearOffName, node); |
+ } |
+ } |
+ node = super.visitField(node); |
+ context = null; |
+ currentMember = null; |
+ return node; |
+ } |
+ |
+ TreeNode visitProcedure(Procedure node) { |
+ currentMember = node; |
+ assert(_currentBlock == null); |
+ assert(_insertionIndex == 0); |
+ assert(context == null); |
+ |
+ Statement body = node.function.body; |
+ |
+ if (node.isInstanceMember) { |
+ Name tearOffName = tearOffGetterNames[node.name]; |
+ if (tearOffName != null) { |
+ if (node.isGetter) { |
+ // We rename the getter to avoid an indirection in most cases. |
+ Name oldName = node.name; |
+ node.name = tearOffName; |
+ addGetterForwarder(oldName, node); |
+ } else if (node.kind == ProcedureKind.Method) { |
+ addTearOffGetter(tearOffName, node); |
+ } |
+ } |
+ } |
+ |
+ if (body == null) return node; |
+ |
+ currentMemberFunction = node.function; |
+ |
+ // Ensure that the body is a block which becomes the current block. |
+ if (body is Block) { |
+ _currentBlock = body; |
+ } else { |
+ _currentBlock = new Block(<Statement>[body]); |
+ node.function.body = body.parent = _currentBlock; |
+ } |
+ _insertionIndex = 0; |
+ |
+ // 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()); |
+ } |
+ |
+ node.transformChildren(this); |
+ |
+ _currentBlock = null; |
+ _insertionIndex = 0; |
+ context = null; |
+ currentMemberFunction = null; |
+ currentMember = null; |
+ return node; |
+ } |
+ |
+ TreeNode visitLocalInitializer(LocalInitializer node) { |
+ assert(!capturedVariables.contains(node.variable)); |
+ node.transformChildren(this); |
+ return node; |
+ } |
+ |
+ TreeNode visitFunctionNode(FunctionNode node) { |
+ transformList(node.typeParameters, this, node); |
+ |
+ void extend(VariableDeclaration parameter) { |
+ context.extend(parameter, new VariableGet(parameter)); |
+ } |
+ |
+ // TODO: Can parameters contain initializers (e.g., for optional ones) that |
+ // need to be closure converted? |
+ node.positionalParameters.where(capturedVariables.contains).forEach(extend); |
+ node.namedParameters.where(capturedVariables.contains).forEach(extend); |
+ |
+ assert(node.body != null); |
+ node.body = node.body.accept(this); |
+ node.body.parent = node; |
+ return node; |
+ } |
+ |
+ TreeNode visitBlock(Block node) => saveContext(() { |
+ if (_currentBlock != node) { |
+ _currentBlock = node; |
+ _insertionIndex = 0; |
+ } |
+ |
+ while (_insertionIndex < _currentBlock.statements.length) { |
+ assert(_currentBlock == node); |
+ |
+ var original = _currentBlock.statements[_insertionIndex]; |
+ var transformed = original.accept(this); |
+ assert(_currentBlock.statements[_insertionIndex] == original); |
+ if (transformed == null) { |
+ _currentBlock.statements.removeAt(_insertionIndex); |
+ } else { |
+ _currentBlock.statements[_insertionIndex++] = transformed; |
+ transformed.parent = _currentBlock; |
+ } |
+ } |
+ |
+ return node; |
+ }); |
+ |
+ TreeNode visitVariableDeclaration(VariableDeclaration node) { |
+ node.transformChildren(this); |
+ |
+ if (!capturedVariables.contains(node)) return node; |
+ context.extend(node, node.initializer ?? new NullLiteral()); |
+ |
+ if (node.parent == currentFunction) return node; |
+ if (node.parent is Block) { |
+ // When returning null, the parent block will remove this node from its |
+ // list of statements. |
+ // TODO(ahe): I'd like to avoid testing on the parent pointer. |
+ return null; |
+ } |
+ throw "Unexpected parent for $node: ${node.parent.parent}"; |
+ } |
+ |
+ TreeNode visitVariableGet(VariableGet node) { |
+ return capturedVariables.contains(node.variable) |
+ ? context.lookup(node.variable) |
+ : node; |
+ } |
+ |
+ TreeNode visitVariableSet(VariableSet node) { |
+ node.transformChildren(this); |
+ |
+ return capturedVariables.contains(node.variable) |
+ ? context.assign(node.variable, node.value, |
+ voidContext: isInVoidContext(node)) |
+ : node; |
+ } |
+ |
+ bool isInVoidContext(Expression node) { |
+ TreeNode parent = node.parent; |
+ return parent is ExpressionStatement || |
+ parent is ForStatement && parent.condition != node; |
+ } |
+ |
+ DartType visitDartType(DartType node) { |
+ return substitute(node, typeSubstitution); |
+ } |
+ |
+ VariableDeclaration getReplacementLoopVariable(VariableDeclaration variable) { |
+ VariableDeclaration newVariable = new VariableDeclaration(variable.name, |
+ initializer: variable.initializer, |
+ type: variable.type)..flags = variable.flags; |
+ variable.initializer = new VariableGet(newVariable); |
+ variable.initializer.parent = variable; |
+ return newVariable; |
+ } |
+ |
+ Expression cloneContext() { |
+ InvalidExpression placeHolder = new InvalidExpression(); |
+ contextClonePlaceHolders.add(placeHolder); |
+ return placeHolder; |
+ } |
+ |
+ TreeNode visitInvalidExpression(InvalidExpression node) { |
+ return contextClonePlaceHolders.remove(node) ? context.clone() : node; |
+ } |
+ |
+ TreeNode visitForStatement(ForStatement node) { |
+ if (node.variables.any(capturedVariables.contains)) { |
+ // 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 |
+ // loop. |
+ // |
+ // Consider the following example, where `x` is `node.variables.first`, |
+ // and `#t1` is a temporary variable: |
+ // |
+ // for (var x = 0; x < 10; x++) body; |
+ // |
+ // This is transformed to: |
+ // |
+ // { |
+ // var x = 0; |
+ // for (; x < 10; clone-context, x++) body; |
+ // } |
+ // |
+ // `clone-context` is a place-holder that will later be replaced by an |
+ // expression that clones the current closure context (see |
+ // [visitInvalidExpression]). |
+ return saveContext(() { |
+ context = context.toNestedContext(); |
+ List<Statement> statements = <Statement>[]; |
+ statements.addAll(node.variables); |
+ statements.add(node); |
+ node.variables.clear(); |
+ node.updates.insert(0, cloneContext()); |
+ _currentBlock = new Block(statements); |
+ _insertionIndex = 0; |
+ return _currentBlock.accept(this); |
+ }); |
+ } |
+ return super.visitForStatement(node); |
+ } |
+ |
+ TreeNode visitForInStatement(ForInStatement node) { |
+ if (capturedVariables.contains(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` |
+ // is captured. |
+ // |
+ // Consider the following example, where `x` is `node.variable`, and |
+ // `#t1` is a temporary variable: |
+ // |
+ // for (var x in expr) body; |
+ // |
+ // Notice that we can assume that `x` doesn't have an initializer based |
+ // on invariants in the Kernel AST. This is transformed to: |
+ // |
+ // for (var #t1 in expr) { var x = #t1; body; } |
+ // |
+ // After this, we call super to apply the normal closure conversion to |
+ // the transformed for-in loop. |
+ VariableDeclaration variable = node.variable; |
+ VariableDeclaration newVariable = getReplacementLoopVariable(variable); |
+ node.variable = newVariable; |
+ newVariable.parent = node; |
+ node.body = new Block(<Statement>[variable, node.body]); |
+ node.body.parent = node; |
+ } |
+ return super.visitForInStatement(node); |
+ } |
+ |
+ TreeNode visitThisExpression(ThisExpression node) { |
+ return isOuterMostContext |
+ ? node |
+ : context.lookup(thisAccess[currentMemberFunction]); |
+ } |
+ |
+ TreeNode visitStaticGet(StaticGet node) { |
+ Member target = node.target; |
+ if (target is Procedure && target.kind == ProcedureKind.Method) { |
+ Expression expression = getTearOffExpression(node.target); |
+ expression.transformChildren(this); |
+ return expression; |
+ } |
+ return super.visitStaticGet(node); |
+ } |
+ |
+ TreeNode visitPropertyGet(PropertyGet node) { |
+ Name tearOffName = tearOffGetterNames[node.name]; |
+ if (tearOffName != null) { |
+ node.name = tearOffName; |
+ } |
+ return super.visitPropertyGet(node); |
+ } |
+ |
+ TreeNode visitCatch(Catch node) { |
+ VariableDeclaration exception = node.exception; |
+ VariableDeclaration stackTrace = node.stackTrace; |
+ if (stackTrace != null && capturedVariables.contains(stackTrace)) { |
+ Block block = node.body = ensureBlock(node.body); |
+ block.parent = node; |
+ node.stackTrace = new VariableDeclaration(null); |
+ node.stackTrace.parent = node; |
+ stackTrace.initializer = new VariableGet(node.stackTrace); |
+ block.statements.insert(0, stackTrace); |
+ stackTrace.parent = block; |
+ } |
+ if (exception != null && capturedVariables.contains(exception)) { |
+ Block block = node.body = ensureBlock(node.body); |
+ block.parent = node; |
+ node.exception = new VariableDeclaration(null); |
+ node.exception.parent = node; |
+ exception.initializer = new VariableGet(node.exception); |
+ block.statements.insert(0, exception); |
+ exception.parent = block; |
+ } |
+ return super.visitCatch(node); |
+ } |
+ |
+ Block ensureBlock(Statement statement) { |
+ 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) { |
+ 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 |
+ // only copy type variables that are used. However, we assume that |
+ // passing type arguments that match the enclosing class' type |
+ // 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); |
+ } |
+ Class closureClass = createClosureClass(procedure.function, |
+ fields: fields, substitution: substitution); |
+ closureClass.addMember(new Procedure(new Name("call"), ProcedureKind.Method, |
+ forwardFunction(procedure, receiver, substitution), |
+ fileUri: currentFileUri)); |
+ newLibraryMembers.add(closureClass); |
+ 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); |
+ } |
+ |
+ /// Creates a function that has the same signature as `procedure.function` |
+ /// and which forwards all arguments to `procedure`. |
+ FunctionNode forwardFunction(Procedure procedure, Expression receiver, |
+ Map<TypeParameter, DartType> substitution) { |
+ CloneVisitor cloner = substitution.isEmpty |
+ ? this.cloner |
+ : new CloneWithoutBody(typeSubstitution: substitution); |
+ FunctionNode function = procedure.function; |
+ List<TypeParameter> typeParameters = |
+ function.typeParameters.map(cloner.clone).toList(); |
+ List<VariableDeclaration> positionalParameters = |
+ function.positionalParameters.map(cloner.clone).toList(); |
+ List<VariableDeclaration> namedParameters = |
+ function.namedParameters.map(cloner.clone).toList(); |
+ // TODO(ahe): Clone or copy inferredReturnValue? |
+ InferredValue inferredReturnValue = null; |
+ |
+ List<DartType> types = typeParameters |
+ .map((TypeParameter parameter) => new TypeParameterType(parameter)) |
+ .toList(); |
+ List<Expression> positional = positionalParameters |
+ .map((VariableDeclaration parameter) => new VariableGet(parameter)) |
+ .toList(); |
+ List<NamedExpression> named = |
+ namedParameters.map((VariableDeclaration parameter) { |
+ return new NamedExpression(parameter.name, new VariableGet(parameter)); |
+ }).toList(); |
+ |
+ Arguments arguments = new Arguments(positional, types: types, named: named); |
+ InvocationExpression invocation = procedure.isInstanceMember |
+ ? new MethodInvocation(receiver, procedure.name, arguments, procedure) |
+ : new StaticInvocation(procedure, arguments); |
+ return new FunctionNode(new ReturnStatement(invocation), |
+ typeParameters: typeParameters, |
+ positionalParameters: positionalParameters, |
+ namedParameters: namedParameters, |
+ requiredParameterCount: function.requiredParameterCount, |
+ returnType: substitute(function.returnType, substitution), |
+ inferredReturnValue: inferredReturnValue); |
+ } |
+ |
+ /// Creates copies of the type variables in [original] and returns a |
+ /// substitution that can be passed to [substitute] to substitute all uses of |
+ /// [original] with their copies. |
+ Map<TypeParameter, DartType> copyTypeVariables( |
+ Iterable<TypeParameter> original) { |
+ if (original.isEmpty) return const <TypeParameter, DartType>{}; |
+ Map<TypeParameter, DartType> substitution = <TypeParameter, DartType>{}; |
+ for (TypeParameter t in original) { |
+ substitution[t] = new TypeParameterType(new TypeParameter(t.name)); |
+ } |
+ substitution.forEach((TypeParameter t, DartType copy) { |
+ if (copy is TypeParameterType) { |
+ copy.parameter.bound = substitute(t.bound, substitution); |
+ } |
+ }); |
+ return substitution; |
+ } |
+ |
+ 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: 'Closure#${localNames[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; |
+ } |
+ |
+ Statement forwardToThisProperty(Member node) { |
+ assert(node is Field || (node is Procedure && node.isGetter)); |
+ return new ReturnStatement( |
+ new PropertyGet(new ThisExpression(), node.name, node)); |
+ } |
+ |
+ void addFieldForwarder(Name name, Field field) { |
+ newClassMembers.add(new Procedure(name, ProcedureKind.Getter, |
+ new FunctionNode(forwardToThisProperty(field)), |
+ fileUri: currentFileUri)); |
+ } |
+ |
+ Procedure copyWithBody(Procedure procedure, Statement body) { |
+ Procedure copy = cloner.clone(procedure); |
+ copy.function.body = body; |
+ copy.function.body.parent = copy.function; |
+ return copy; |
+ } |
+ |
+ void addGetterForwarder(Name name, Procedure getter) { |
+ assert(getter.isGetter); |
+ newClassMembers |
+ .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)); |
+ } |
+ |
+ // 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)); |
+ } |
+} |