blob: 408d37c61a7563336ae19f8eae4656ab0d9460d7 [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.info;
import '../../ast.dart'
show
Class,
Constructor,
Field,
FunctionDeclaration,
FunctionNode,
Member,
Name,
Procedure,
ProcedureKind,
PropertyGet,
ThisExpression,
TypeParameter,
TypeParameterType,
VariableDeclaration,
VariableGet,
VariableSet;
import '../../visitor.dart' show RecursiveVisitor;
class ClosureInfo extends RecursiveVisitor {
FunctionNode currentFunction;
final Map<VariableDeclaration, FunctionNode> function =
<VariableDeclaration, FunctionNode>{};
final Set<VariableDeclaration> variables = new Set<VariableDeclaration>();
final Map<FunctionNode, Set<TypeParameter>> typeVariables =
<FunctionNode, Set<TypeParameter>>{};
/// Map from members to synthetic variables for accessing `this` in a local
/// function.
final Map<FunctionNode, VariableDeclaration> thisAccess =
<FunctionNode, VariableDeclaration>{};
final Set<String> currentMemberLocalNames = new Set<String>();
final Map<FunctionNode, String> localNames = <FunctionNode, String>{};
/// Contains all names used as getter through a [PropertyGet].
final Set<Name> invokedGetters = new Set<Name>();
/// Contains all names of declared regular instance methods (not including
/// accessors and operators).
final Set<Name> declaredInstanceMethodNames = new Set<Name>();
Class currentClass;
Member currentMember;
FunctionNode currentMemberFunction;
bool get isOuterMostContext {
return currentFunction == null || currentMemberFunction == currentFunction;
}
/// Maps the names of all instance methods that may be torn off (aka
/// implicitly closurized) to `${name.name}#get`.
Map<Name, Name> get tearOffGetterNames {
// TODO(dmitryas): Add support for tear-offs. When added, uncomment this.
//
// Map<Name, Name> result = <Name, Name>{};
// for (Name name in declaredInstanceMethodNames) {
// if (invokedGetters.contains(name)) {
// result[name] = new Name("${name.name}#get", name.library);
// }
// }
// return result;
//
// Currently an empty map is returned, so no tear-offs supporting functions
// and getters are generated, and no property-get targets are renamed.
return <Name, Name>{};
}
void beginMember(Member member, [FunctionNode function]) {
currentMemberLocalNames.clear();
if (function != null) {
localNames[function] = computeUniqueLocalName(member.name.name);
}
currentMember = member;
currentMemberFunction = function;
}
void endMember() {
currentMember = null;
currentMemberFunction = null;
}
visitClass(Class node) {
currentClass = node;
super.visitClass(node);
currentClass = null;
}
visitConstructor(Constructor node) {
/// [currentFunction] should be set to [currentMemberFunction] before
/// visiting the [FunctionNode] of the constructor, because initializers may
/// use constructor parameters and it shouldn't be treated as capturing
/// them. Consider the following code:
///
/// class A {
/// int x;
/// A(int x) /* [x] is visible in initializers and body. */
/// : this.x = x { /* Initializer. */
/// /* Constructor body. */
/// }
/// }
///
/// Here the parameter shouldn't be captured into a context in the
/// initializer. However, [currentFunction] is `null` if not set, and
/// `function[node.variable]` in this case points to the [FunctionNode] of
/// the constructor (which is not `null`). It leads to `x` being treated as
/// captured, because it's seen as used outside of the function where it is
/// declared. In turn, it leads to unnecessary context creation and usage.
beginMember(node, node.function);
saveCurrentFunction(() {
currentFunction = currentMemberFunction;
super.visitConstructor(node);
});
endMember();
}
visitProcedure(Procedure node) {
beginMember(node, node.function);
if (node.isInstanceMember && node.kind == ProcedureKind.Method) {
// Ignore the `length` method of [File] subclasses for now, as they
// will force us to rename the `length` getter (kernel issue #43).
// TODO(ahe): remove this condition.
Class parent = node.parent;
if (node.name.name != "length" ||
parent.enclosingLibrary.importUri.toString() != "dart:io") {
declaredInstanceMethodNames.add(node.name);
}
}
super.visitProcedure(node);
endMember();
}
visitField(Field node) {
beginMember(node);
super.visitField(node);
endMember();
}
String computeUniqueLocalName([String name]) {
if (name == null || name.isEmpty) {
name = "function";
}
if (currentFunction == null) {
if (currentMember != null) {
name = "${currentMember.name.name}#$name";
}
if (currentClass != null) {
name = "${currentClass.name}#$name";
}
} else {
name = "${localNames[currentFunction]}#$name";
}
int count = 1;
String candidate = name;
while (currentMemberLocalNames.contains(candidate)) {
candidate = "$name#${count++}";
}
currentMemberLocalNames.add(candidate);
return candidate;
}
visitFunctionDeclaration(FunctionDeclaration node) {
assert(!localNames.containsKey(node));
localNames[node.function] = computeUniqueLocalName(node.variable.name);
return super.visitFunctionDeclaration(node);
}
visitFunctionNode(FunctionNode node) {
localNames.putIfAbsent(node, computeUniqueLocalName);
saveCurrentFunction(() {
currentFunction = node;
node.visitChildren(this);
});
Set<TypeParameter> capturedTypeVariables = typeVariables[node];
if (capturedTypeVariables != null && !isOuterMostContext) {
// Propagate captured type variables to enclosing function.
typeVariables
.putIfAbsent(currentFunction, () => new Set<TypeParameter>())
.addAll(capturedTypeVariables);
}
}
visitVariableDeclaration(VariableDeclaration node) {
function[node] = currentFunction;
node.visitChildren(this);
}
visitVariableGet(VariableGet node) {
if (function[node.variable] != currentFunction) {
variables.add(node.variable);
}
node.visitChildren(this);
}
visitVariableSet(VariableSet node) {
if (function[node.variable] != currentFunction) {
variables.add(node.variable);
}
node.visitChildren(this);
}
visitTypeParameterType(TypeParameterType node) {
if (!isOuterMostContext) {
typeVariables
.putIfAbsent(currentFunction, () => new Set<TypeParameter>())
.add(node.parameter);
}
}
visitThisExpression(ThisExpression node) {
if (!isOuterMostContext) {
thisAccess.putIfAbsent(
currentMemberFunction, () => new VariableDeclaration("#self"));
}
}
visitPropertyGet(PropertyGet node) {
invokedGetters.add(node.name);
super.visitPropertyGet(node);
}
saveCurrentFunction(void f()) {
var saved = currentFunction;
try {
f();
} finally {
currentFunction = saved;
}
}
}