blob: 91a36dfc5e17e4933a479a8f6bb807851dedf391 [file] [log] [blame]
// 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 for ${currentMember.runtimeType}";
}
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) {
assert(isEmptyContext);
currentMember = node;
FunctionNode function = node.function;
if (function.body != null && function.body is! EmptyStatement) {
setupContextForFunctionBody(function);
VariableDeclaration self = thisAccess[currentMemberFunction];
// TODO(karlklose): transform initializers
if (self != null) {
context.extend(self, new ThisExpression());
}
node.function.accept(this);
resetContext();
}
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) {
assert(isEmptyContext);
currentMember = node;
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);
}
}
}
FunctionNode function = node.function;
if (function.body != null) {
setupContextForFunctionBody(function);
VariableDeclaration self = thisAccess[currentMemberFunction];
if (self != null) {
context.extend(self, new ThisExpression());
}
node.transformChildren(this);
resetContext();
}
return node;
}
void setupContextForFunctionBody(FunctionNode function) {
Statement body = function.body;
assert(body != null);
currentMemberFunction = 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]);
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);
}
void resetContext() {
_currentBlock = null;
_insertionIndex = 0;
context = null;
currentMemberFunction = null;
currentMember = null;
}
bool get isEmptyContext {
return _currentBlock == null && _insertionIndex == 0 && context == null;
}
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) {
return 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;
} else {
assert(node.parent is Block);
// When returning null, the parent block will remove this node from its
// list of statements.
return null;
}
}
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));
}
}