| 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));
|
| + }
|
| +}
|
|
|