blob: 45d6a0d39fab3b32b449861e7a472d7b9f56439c [file] [log] [blame]
// Copyright (c) 2012, 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.
import 'package:front_end/src/fasta/scanner.dart' show Token;
import 'common/names.dart' show Identifiers;
import 'common/resolution.dart' show ParsingContext, Resolution;
import 'common/tasks.dart' show CompilerTask, Measurer;
import 'common.dart';
import 'compiler.dart' show Compiler;
import 'constants/expressions.dart';
import 'elements/elements.dart';
import 'elements/entities.dart';
import 'elements/entity_utils.dart' as utils;
import 'elements/modelx.dart'
show BaseFunctionElementX, ClassElementX, ElementX;
import 'elements/resolution_types.dart';
import 'elements/types.dart';
import 'elements/visitor.dart' show ElementVisitor;
import 'js_backend/js_backend.dart' show JavaScriptBackend;
import 'js_backend/runtime_types.dart';
import 'resolution/tree_elements.dart' show TreeElements;
import 'tree/tree.dart';
import 'util/util.dart';
import 'world.dart' show ClosedWorldRefiner;
// TODO(johnniwinther,efortuna): Split [ClosureConversionTask] from
// [ClosureDataLookup].
abstract class ClosureConversionTask<T> extends CompilerTask
implements ClosureDataLookup<T> {
ClosureConversionTask(Measurer measurer) : super(measurer);
//void analyzeClosures();
void convertClosures(Iterable<MemberEntity> processedEntities,
ClosedWorldRefiner closedWorldRefiner);
}
/// Class that provides information for how closures are rewritten/represented
/// to preserve Dart semantics when compiled to JavaScript. Given a particular
/// node to look up, it returns a information about the internal representation
/// of how closure conversion is implemented. T is an ir.Node or Node.
abstract class ClosureDataLookup<T> {
/// Look up information about the variables that have been mutated and are
/// used inside the scope of [node].
ScopeInfo getScopeInfo(MemberEntity member);
/// This returns the same information as ScopeInfo, but can be called in
/// situations when you are sure you are dealing with a closure specifically.
// TODO(johnniwinther,efortuna): Remove the need for this. It is now only
// used in inference.
ClosureRepresentationInfo getClosureInfoForMember(MemberEntity member);
ClosureRepresentationInfo getClosureInfo(T localFunction);
/// Look up information about a loop, in case any variables it declares need
/// to be boxed/snapshotted.
CapturedLoopScope getCapturedLoopScope(T loopNode);
/// Accessor to the information about scopes that closures capture. Used by
/// the SSA builder.
CapturedScope getCapturedScope(MemberEntity entity);
}
/// Class that represents one level of scoping information, whether this scope
/// is a closure or not. This is specifically used to store information
/// about the usage of variables in try or sync blocks, because they need to be
/// boxed.
///
/// Variables that are used in a try must be treated as boxed because the
/// control flow can be non-linear. Also parameters to a `sync*` generator must
/// be boxed, because of the way we rewrite sync* functions. See also comments
/// in [ClosureClassMap.useLocal].
class ScopeInfo {
const ScopeInfo();
/// Convenience reference pointer to the element representing `this`.
/// If this scope is not in an instance member, it will be null.
Local get thisLocal => null;
/// Returns true if this [variable] is used inside a `try` block or a `sync*`
/// generator (this is important to know because boxing/redirection needs to
/// happen for those local variables).
///
/// Variables that are used in a try must be treated as boxed because the
/// control flow can be non-linear.
///
/// Also parameters to a `sync*` generator must be boxed, because of the way
/// we rewrite sync* functions. See also comments in
/// [ClosureClassMap.useLocal].
bool localIsUsedInTryOrSync(Local variable) => false;
/// Loop through each variable that has been defined in this scope, modified
/// anywhere (this scope or another scope) and used in another scope. Because
/// it is used in another scope, these variables need to be "boxed", creating
/// a thin wrapper around accesses to these variables so that accesses get
/// the correct updated value. The variables in localsUsedInTryOrSync may
/// be included in this set.
///
/// In the case of loops, this is the set of iteration variables (or any
/// variables declared in the for loop expression (`for (...here...)`) that
/// need to be boxed to snapshot their value.
void forEachBoxedVariable(f(Local local, FieldEntity field)) {}
/// True if [variable] has been mutated and is also used in another scope.
bool isBoxed(Local variable) => false;
}
/// Class representing the usage of a scope that has been captured in the
/// context of a closure.
class CapturedScope extends ScopeInfo {
const CapturedScope();
/// If true, this closure accesses a variable that was defined in an outside
/// scope and this variable gets modified at some point (sometimes we say that
/// variable has been "captured"). In this situation, access to this variable
/// is controlled via a wrapper (box) so that updates to this variable
/// are done in a way that is in line with Dart's closure rules.
bool get requiresContextBox => false;
/// Accessor to the local environment in which a particular closure node is
/// executed. This will encapsulate the value of any variables that have been
/// scoped into this context from outside. This is an accessor to the
/// contextBox that [requiresContextBox] is testing is required.
Local get context => null;
}
/// Class that describes the actual mechanics of how values of variables
/// instantiated in a loop are captured inside closures in the loop body.
/// Unlike JS, the value of a declared loop iteration variable in any closure
/// is captured/snapshotted inside at each iteration point, as if we created a
/// new local variable for that value inside the loop. For example, for the
/// following loop:
///
/// var lst = [];
/// for (int i = 0; i < 5; i++) lst.add(()=>i);
/// var result = list.map((f) => f()).toList();
///
/// `result` will be [0, 1, 2, 3, 4], whereas were this JS code
/// the result would be [5, 5, 5, 5, 5]. Because of this difference we need to
/// create a closure for these sorts of loops to capture the variable's value at
/// each iteration, by boxing the iteration variable[s].
class CapturedLoopScope extends CapturedScope {
const CapturedLoopScope();
/// True if this loop scope declares in the first part of the loop
/// `for (<here>;...;...)` any variables that need to be boxed.
bool get hasBoxedLoopVariables => false;
/// The set of iteration variables (or variables declared in the for loop
/// expression (`for (<here>; ... ; ...)`) that need to be boxed to snapshot
/// their value. These variables are also included in the set of
/// `forEachBoxedVariable` method. The distinction between these two sets is
/// in this example:
///
/// run(f) => f();
/// var a;
/// for (int i = 0; i < 3; i++) {
/// var b = 3;
/// a = () => b = i;
/// }
///
/// `i` would be a part of the boxedLoopVariables AND boxedVariables, but b
/// would only be a part of boxedVariables.
List<Local> get boxedLoopVariables => const <Local>[];
}
/// Class that describes the actual mechanics of how the converted, rewritten
/// closure is implemented. For example, for the following closure (named foo
/// for convenience):
///
/// var foo = (x) => y + x;
///
/// We would produce the following class to control access to these variables in
/// the following way (modulo naming of variables, assuming that y is modified
/// elsewhere in its scope):
///
/// class FooClosure {
/// int y;
/// FooClosure(this.y);
/// call(x) => this.y + x;
/// }
///
/// and then to execute this closure, for example:
///
/// var foo = new FooClosure(1);
/// foo.call(2);
///
/// if `y` is modified elsewhere within its scope, accesses to y anywhere in the
/// code will be controlled via a box object.
///
/// Because in these examples `y` was declared in some other, outer scope, but
/// used in the inner scope of this closure, we say `y` is a "captured"
/// variable.
/// TODO(efortuna): Make interface simpler in subsequent refactorings.
class ClosureRepresentationInfo extends ScopeInfo {
const ClosureRepresentationInfo();
/// The original local function before any translation.
///
/// Will be null for methods.
Local get closureEntity => null;
/// The entity for the class used to represent the rewritten closure in the
/// emitted JavaScript.
///
/// Closures are rewritten in the form of classes that have fields to control
/// the redirection and editing of captured variables.
ClassEntity get closureClassEntity => null;
/// The function that implements the [local] function as a `call` method on
/// the closure class.
FunctionEntity get callMethod => null;
/// List of locals that this closure class has created corresponding field
/// entities for.
@deprecated
List<Local> get createdFieldEntities => const <Local>[];
/// As shown in the example in the comments at the top of this class, we
/// create fields in the closure class for each captured variable. This is an
/// accessor the [local] for which [field] was created.
/// Returns the [local] for which [field] was created.
Local getLocalForField(FieldEntity field) {
failedAt(field, "No local for $field.");
return null;
}
/// Convenience pointer to the field entity representation in the closure
/// class of the element representing `this`.
FieldEntity get thisFieldEntity => null;
/// Loop through every variable that has been captured in this closure. This
/// consists of all the free variables (variables captured *just* in this
/// closure) and all variables captured in nested scopes that we may be
/// capturing as well. These nested scopes hold "boxes" to hold the executable
/// context for that scope.
void forEachCapturedVariable(f(Local from, FieldEntity to)) {}
/// Loop through each variable that has been boxed in this closure class. Only
/// captured variables that are mutated need to be "boxed" (which basically
/// puts a thin layer between updates and reads to this variable to ensure
/// that every place that accesses it gets the correct updated value). This
/// includes looping over variables that were boxed from other scopes, not
/// strictly variables defined in this closure, unlike the behavior in
/// the superclass ScopeInfo.
@override
void forEachBoxedVariable(f(Local local, FieldEntity field)) {}
/// Loop through each free variable in this closure. Free variables are the
/// variables that have been captured *just* in this closure, not in nested
/// scopes.
void forEachFreeVariable(f(Local variable, FieldEntity field)) {}
/// Return true if [variable] has been captured and mutated (all other
/// variables do not require boxing).
bool isVariableBoxed(Local variable) => false;
// TODO(efortuna): Remove this method. The old system was using
// ClosureClassMaps for situations other than closure class maps, and that's
// just confusing.
bool get isClosure => false;
}
class ClosureTask extends ClosureConversionTask<Node> {
Map<Node, CapturedScopeImpl> _closureInfoMap = <Node, CapturedScopeImpl>{};
Map<MemberElement, ClosureClassMap> _closureMemberMappingCache =
<MemberElement, ClosureClassMap>{};
Map<FunctionExpression, ClosureClassMap> _closureNodeMappingCache =
<FunctionExpression, ClosureClassMap>{};
Compiler compiler;
ClosureTask(Compiler compiler)
: compiler = compiler,
super(compiler.measurer);
String get name => "Closure Simplifier";
DiagnosticReporter get reporter => compiler.reporter;
void convertClosures(Iterable<MemberEntity> processedEntities,
ClosedWorldRefiner closedWorldRefiner) {
createClosureClasses(closedWorldRefiner);
}
CapturedScope _getCapturedScope(Node node) {
var value = _closureInfoMap[node];
return value == null ? const CapturedScope() : value;
}
CapturedScope getCapturedScope(covariant MemberElement member) {
ResolvedAst resolvedAst = member.resolvedAst;
if (resolvedAst.kind != ResolvedAstKind.PARSED)
return const CapturedScope();
return _getCapturedScope(resolvedAst.node);
}
ScopeInfo getScopeInfo(MemberEntity member) {
return _getMemberMapping(member);
}
ClosureRepresentationInfo getClosureInfoForMember(MemberEntity member) {
return _getMemberMapping(member);
}
ClosureRepresentationInfo getClosureInfo(covariant FunctionExpression node) {
return _getClosureMapping(node);
}
CapturedLoopScope getCapturedLoopScope(Node loopNode) {
var value = _closureInfoMap[loopNode];
return value == null ? const CapturedLoopScope() : value;
}
/// Returns the [ClosureClassMap] computed for [element].
ClosureClassMap _getMemberMapping(MemberElement element) {
return measure(() {
if (element.isGenerativeConstructorBody) {
ConstructorBodyElement constructorBody = element;
element = constructorBody.constructor;
}
ClosureClassMap closureClassMap = _closureMemberMappingCache[element];
assert(closureClassMap != null,
failedAt(element, "No ClosureClassMap computed for ${element}."));
return closureClassMap;
});
}
/// Returns the [ClosureClassMap] computed for [node].
ClosureClassMap _getClosureMapping(FunctionExpression node) {
return measure(() {
ClosureClassMap closureClassMap = _closureNodeMappingCache[node];
assert(closureClassMap != null,
failedAt(node, "No ClosureClassMap computed for ${node}."));
return closureClassMap;
});
}
/// Create [ClosureClassMap]s for all live members.
void createClosureClasses(ClosedWorldRefiner closedWorldRefiner) {
compiler.enqueuer.resolution.processedEntities
.forEach((MemberEntity _element) {
MemberElement element = _element;
ResolvedAst resolvedAst = element.resolvedAst;
if (element.isAbstract) return;
if (element.isField &&
!element.isInstanceMember &&
resolvedAst.body == null) {
// Skip top-level/static fields without an initializer.
return;
}
computeClosureToClassMapping(element, closedWorldRefiner);
});
}
ClosureClassMap computeClosureToClassMapping(
MemberElement element, ClosedWorldRefiner closedWorldRefiner) {
return measure(() {
ClosureClassMap cached = _closureMemberMappingCache[element];
if (cached != null) return cached;
if (element.resolvedAst.kind != ResolvedAstKind.PARSED) {
return _closureMemberMappingCache[element] = new ClosureClassMap(
null, null, null, new ThisLocalVariable(element));
}
return reporter.withCurrentElement(element.implementation, () {
Node node = element.resolvedAst.node;
TreeElements elements = element.resolvedAst.elements;
ClosureTranslator translator = new ClosureTranslator(
compiler,
closedWorldRefiner,
elements,
_closureMemberMappingCache,
_closureNodeMappingCache,
_closureInfoMap);
// The translator will store the computed closure-mappings inside the
// cache. One for given node and one for each nested closure.
if (node is FunctionExpression) {
translator.translateFunction(element, node);
} else if (element.isSynthesized) {
reporter.internalError(
element, "Unexpected synthesized element: $element");
_closureMemberMappingCache[element] = new ClosureClassMap(
null, null, null, new ThisLocalVariable(element));
} else {
assert(element.isField,
failedAt(element, "Expected $element to be a field."));
Node initializer = element.resolvedAst.body;
if (initializer != null) {
// The lazy initializer of a static.
translator.translateLazyInitializer(element, node, initializer);
} else {
assert(
element.isInstanceMember,
failedAt(
element,
"Expected $element (${element.runtimeType}) "
"to be an instance field."));
_closureMemberMappingCache[element] = new ClosureClassMap(
null, null, null, new ThisLocalVariable(element));
}
}
assert(_closureMemberMappingCache[element] != null,
failedAt(element, "No ClosureClassMap computed for ${element}."));
return _closureMemberMappingCache[element];
});
});
}
}
// TODO(ahe): These classes continuously cause problems. We need to
// find a more general solution.
class ClosureFieldElement extends ElementX
implements FieldElement, PrivatelyNamedJSEntity {
/// The [BoxLocal] or [LocalElement] being accessed through the field.
final Local local;
ClosureFieldElement(String name, this.local, ClosureClassElement enclosing)
: super(name, ElementKind.FIELD, enclosing);
/// Use [closureClass] instead.
@deprecated
get enclosingElement => super.enclosingElement;
ClosureClassElement get closureClass => super.enclosingElement;
MemberElement get memberContext => closureClass.methodElement.memberContext;
@override
Local get declaredEntity => local;
@override
Entity get rootOfScope => closureClass;
bool get hasNode => false;
VariableDefinitions get node {
throw failedAt(local, 'Should not access node of ClosureFieldElement.');
}
bool get hasResolvedAst => hasTreeElements;
ResolvedAst get resolvedAst {
return new ParsedResolvedAst(this, null, null, treeElements,
memberContext.compilationUnit.script.resourceUri);
}
Expression get initializer {
throw failedAt(
local, 'Should not access initializer of ClosureFieldElement.');
}
bool get isInstanceMember => true;
bool get isAssignable => false;
ResolutionDartType computeType(Resolution resolution) => type;
ResolutionDartType get type {
if (local is LocalElement) {
LocalElement element = local;
return element.type;
}
return const ResolutionDynamicType();
}
String toString() => "ClosureFieldElement($name)";
accept(ElementVisitor visitor, arg) {
return visitor.visitClosureFieldElement(this, arg);
}
AnalyzableElement get analyzableElement =>
closureClass.methodElement.analyzableElement;
@override
List<MethodElement> get nestedClosures => const <MethodElement>[];
@override
bool get hasConstant => false;
@override
ConstantExpression get constant => null;
}
// TODO(ahe): These classes continuously cause problems. We need to find
// a more general solution.
class ClosureClassElement extends ClassElementX {
ResolutionInterfaceType rawType;
ResolutionInterfaceType thisType;
ResolutionFunctionType callType;
/// Node that corresponds to this closure, used for source position.
final FunctionExpression node;
/**
* The element for the declaration of the function expression.
*/
final LocalFunctionElement methodElement;
final List<ClosureFieldElement> _closureFields = <ClosureFieldElement>[];
ClosureClassElement(
this.node, String name, Compiler compiler, LocalFunctionElement closure)
: this.methodElement = closure,
super(
name,
closure.compilationUnit,
// By assigning a fresh class-id we make sure that the hashcode
// is unique, but also emit closure classes after all other
// classes (since the emitter sorts classes by their id).
compiler.idGenerator.getNextFreeId(),
STATE_DONE) {
ClassElement superclass = compiler.resolution.commonElements.closureClass;
superclass.ensureResolved(compiler.resolution);
supertype = superclass.thisType;
interfaces = const Link<ResolutionDartType>();
thisType = rawType = new ResolutionInterfaceType(this);
allSupertypesAndSelf =
superclass.allSupertypesAndSelf.extendClass(thisType);
callType = methodElement.type;
}
Iterable<ClosureFieldElement> get closureFields => _closureFields;
void addField(ClosureFieldElement field, DiagnosticReporter listener) {
_closureFields.add(field);
addMember(field, listener);
}
bool get hasNode => true;
bool get isClosure => true;
Token get position => node.getBeginToken();
Node parseNode(ParsingContext parsing) => node;
// A [ClosureClassElement] is nested inside a function or initializer in terms
// of [enclosingElement], but still has to be treated as a top-level
// element.
bool get isTopLevel => true;
get enclosingElement => methodElement;
accept(ElementVisitor visitor, arg) {
return visitor.visitClosureClassElement(this, arg);
}
}
/// A local variable that contains the box object holding the [BoxFieldElement]
/// fields.
class BoxLocal extends Local {
final String name;
final int hashCode = _nextHashCode = (_nextHashCode + 10007).toUnsigned(30);
static int _nextHashCode = 0;
BoxLocal(this.name);
String toString() => 'BoxLocal($name)';
}
class BoxLocalVariable extends BoxLocal implements LocalVariable {
final MemberElement memberContext;
BoxLocalVariable(String name, this.memberContext) : super(name);
ExecutableElement get executableContext => memberContext;
}
// TODO(ngeoffray, ahe): These classes continuously cause problems. We need to
// find a more general solution.
class BoxFieldElement extends ElementX
implements TypedElement, FieldElement, PrivatelyNamedJSEntity {
final LocalVariableElement variableElement;
final BoxLocalVariable box;
BoxFieldElement(String name, this.variableElement, BoxLocalVariable box)
: this.box = box,
super(name, ElementKind.FIELD, box.executableContext);
ResolutionDartType computeType(Resolution resolution) => type;
ResolutionDartType get type => variableElement.type;
@override
Local get declaredEntity => variableElement;
@override
Entity get rootOfScope => box;
accept(ElementVisitor visitor, arg) {
return visitor.visitBoxFieldElement(this, arg);
}
@override
bool get hasNode => false;
@override
bool get hasResolvedAst => false;
@override
Expression get initializer {
throw new UnsupportedError("BoxFieldElement.initializer");
}
@override
MemberElement get memberContext => box.memberContext;
@override
List<MethodElement> get nestedClosures => const <MethodElement>[];
@override
VariableDefinitions get node {
throw new UnsupportedError("BoxFieldElement.node");
}
@override
ResolvedAst get resolvedAst {
throw new UnsupportedError("BoxFieldElement.resolvedAst");
}
@override
bool get hasConstant => false;
@override
ConstantExpression get constant => null;
}
/// A local variable used encode the direct (uncaptured) references to [this].
class ThisLocal extends Local {
final ClassEntity enclosingClass;
ThisLocal(MemberEntity member) : enclosingClass = member.enclosingClass;
String get name => 'this';
bool operator ==(other) {
return other is ThisLocal && other.enclosingClass == enclosingClass;
}
int get hashCode => enclosingClass.hashCode;
}
/// A local variable used encode the direct (uncaptured) references to [this].
class ThisLocalVariable extends ThisLocal implements LocalVariable {
final MemberElement memberContext;
ThisLocalVariable(this.memberContext) : super(memberContext);
ExecutableElement get executableContext => memberContext;
}
/// Call method of a closure class.
// ignore: STRONG_MODE_INVALID_METHOD_OVERRIDE_FROM_BASE
class SynthesizedCallMethodElementX extends BaseFunctionElementX
implements MethodElement {
final LocalFunctionElement expression;
final FunctionExpression node;
final TreeElements treeElements;
SynthesizedCallMethodElementX(String name, LocalFunctionElement other,
ClosureClassElement enclosing, this.node, this.treeElements)
: expression = other,
super(name, other.kind, Modifiers.EMPTY, enclosing) {
asyncMarker = other.asyncMarker;
functionSignature = other.functionSignature;
expression.callMethod = this;
}
/// Use [closureClass] instead.
@deprecated
get enclosingElement => super.enclosingElement;
ClosureClassElement get closureClass => super.enclosingElement;
MemberElement get memberContext {
return closureClass.methodElement.memberContext;
}
bool get hasNode => node != null;
FunctionExpression parseNode(ParsingContext parsing) => node;
AnalyzableElement get analyzableElement =>
closureClass.methodElement.analyzableElement;
bool get hasResolvedAst => true;
ResolvedAst get resolvedAst {
return new ParsedResolvedAst(this, node, node.body, treeElements,
expression.compilationUnit.script.resourceUri);
}
accept(ElementVisitor visitor, arg) {
return visitor.visitMethodElement(this, arg);
}
}
// The box-element for a scope, and the captured variables that need to be
// stored in the box.
class CapturedScopeImpl implements CapturedScope, CapturedLoopScope {
final BoxLocal boxElement;
final Map<Local, BoxFieldElement> capturedVariables;
// If the scope is attached to a [For] contains the variables that are
// declared in the initializer of the [For] and that need to be boxed.
// Otherwise contains the empty List.
List<Local> boxedLoopVariables = const <Local>[];
CapturedScopeImpl(this.boxElement, this.capturedVariables);
Local get context => boxElement;
bool get requiresContextBox => capturedVariables.keys.isNotEmpty;
void forEachBoxedVariable(f(Local local, FieldEntity field)) {
capturedVariables.forEach(f);
}
bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty;
bool isBoxed(Local variable) {
return capturedVariables.containsKey(variable);
}
void forEachCapturedVariable(
f(LocalVariableElement variable, BoxFieldElement boxField)) {
capturedVariables.forEach(f);
}
// Should not be called. Added to make the new interface happy.
bool localIsUsedInTryOrSync(Local variable) =>
throw new UnsupportedError("CapturedScopeImpl.localIsUsedInTryOrSync");
// Should not be called. Added to make the new interface happy.
Local get thisLocal =>
throw new UnsupportedError("CapturedScopeImpl.thisLocal");
String toString() {
String separator = '';
StringBuffer sb = new StringBuffer();
sb.write('CapturedScopeImpl(');
if (boxElement != null) {
sb.write('box=$boxElement');
separator = ',';
}
if (boxedLoopVariables.isNotEmpty) {
sb.write(separator);
sb.write('boxedLoopVariables=${boxedLoopVariables}');
separator = ',';
}
if (capturedVariables.isNotEmpty) {
sb.write(separator);
sb.write('capturedVariables=$capturedVariables');
}
sb.write(')');
return sb.toString();
}
}
class ClosureClassMap implements ClosureRepresentationInfo {
/// The local function element before any translation.
///
/// Will be null for methods.
final LocalFunctionElement closureEntity;
/// The synthesized closure class for [closureEntity].
///
/// The closureClassEntity will be null for methods that are not local
/// closures.
final ClosureClassElement closureClassEntity;
/// The synthesized `call` method of the [closureClassEntity].
///
/// The callMethod will be null for methods that are not local closures.
final MethodElement callMethod;
/// The [thisLocal] makes handling 'this' easier by treating it like any
/// other argument. It is only set for instance-members.
final ThisLocal thisLocal;
/// Maps free locals, arguments, function elements, and box locals to
/// their locations.
final Map<Local, FieldEntity> freeVariableMap = new Map<Local, FieldEntity>();
/// Maps [Loop] and [FunctionExpression] nodes to their [CapturedScopeImpl] which
/// contains their box and the captured variables that are stored in the box.
/// This map will be empty if the method/closure of this [ClosureData] does
/// not contain any nested closure.
final Map<Node, CapturedScopeImpl> capturingScopes =
new Map<Node, CapturedScopeImpl>();
/// Set of [variable]s referenced in this scope that are used inside a
/// `try` block or a `sync*` generator (this is important to know because
/// boxing/redirection needs to happen for those local variables).
///
/// Variables that are used in a try must be treated as boxed because the
/// control flow can be non-linear.
///
/// Also parameters to a `sync*` generator must be boxed, because of the way
/// we rewrite sync* functions. See also comments in [useLocal].
// TODO(johnniwinther): Add variables to this only if the variable is mutated.
final Set<Local> localsUsedInTryOrSync = new Set<Local>();
ClosureClassMap(this.closureEntity, this.closureClassEntity, this.callMethod,
this.thisLocal);
List<Local> get createdFieldEntities {
List<Local> fields = <Local>[];
if (closureClassEntity == null) return const <Local>[];
closureClassEntity.closureFields.forEach((field) {
fields.add(field.local);
});
return fields;
}
@override
Local getLocalForField(covariant ClosureFieldElement field) => field.local;
void addFreeVariable(Local element) {
assert(freeVariableMap[element] == null);
freeVariableMap[element] = null;
}
Iterable<Local> get freeVariables => freeVariableMap.keys;
bool isFreeVariable(Local element) {
return freeVariableMap.containsKey(element);
}
void forEachFreeVariable(f(Local variable, FieldEntity field)) {
freeVariableMap.forEach(f);
}
FieldEntity get thisFieldEntity => freeVariableMap[thisLocal];
bool localIsUsedInTryOrSync(Local variable) =>
localsUsedInTryOrSync.contains(variable);
Local getLocalVariableForClosureField(ClosureFieldElement field) {
return field.local;
}
bool get isClosure => closureEntity != null;
bool capturingScopesBox(Local variable) {
return capturingScopes.values.any((scope) {
return scope.boxedLoopVariables.contains(variable);
});
}
bool isVariableBoxed(Local variable) {
FieldEntity copy = freeVariableMap[variable];
if (copy is BoxFieldElement) {
return true;
}
return capturingScopesBox(variable);
}
void forEachCapturedVariable(void f(Local variable, FieldEntity field)) {
freeVariableMap.forEach((variable, copy) {
if (variable is BoxLocal) return;
f(variable, copy);
});
capturingScopes.values.forEach((CapturedScopeImpl scope) {
scope.forEachCapturedVariable(f);
});
}
void forEachBoxedVariable(
void f(LocalVariableElement local, BoxFieldElement field)) {
freeVariableMap.forEach((variable, copy) {
if (!isVariableBoxed(variable)) return;
f(variable, copy);
});
capturingScopes.values.forEach((CapturedScopeImpl scope) {
scope.forEachCapturedVariable(f);
});
}
bool isBoxed(Local local) {
bool variableIsBoxed = false;
forEachBoxedVariable((LocalVariableElement element, BoxFieldElement field) {
if (element == local) variableIsBoxed = true;
});
return variableIsBoxed;
}
}
class ClosureTranslator extends Visitor {
final Compiler compiler;
final ClosedWorldRefiner closedWorldRefiner;
final TreeElements elements;
int closureFieldCounter = 0;
int boxedFieldCounter = 0;
bool inTryStatement = false;
final Map<MemberElement, ClosureClassMap> memberMappingCache;
final Map<FunctionExpression, ClosureClassMap> nodeMappingCache;
final Map<Node, CapturedScopeImpl> closureInfo;
// Map of captured variables. Initially they will map to `null`. If
// a variable needs to be boxed then the scope declaring the variable
// will update this to mapping to the capturing [BoxFieldElement].
Map<Local, BoxFieldElement> _capturedVariableMapping =
new Map<Local, BoxFieldElement>();
// List of encountered closures.
List<FunctionExpression> closures = <FunctionExpression>[];
// The local variables that have been declared in the current scope.
List<LocalVariableElement> scopeVariables;
// Keep track of the mutated local variables so that we don't need to box
// non-mutated variables.
Set<LocalVariableElement> mutatedVariables = new Set<LocalVariableElement>();
MemberElement outermostElement;
ExecutableElement executableContext;
// The closureData of the currentFunctionElement.
ClosureClassMap closureData;
bool insideClosure = false;
ClosureTranslator(this.compiler, this.closedWorldRefiner, this.elements,
this.memberMappingCache, this.nodeMappingCache, this.closureInfo);
DiagnosticReporter get reporter => compiler.reporter;
RuntimeTypesNeed get rtiNeed => closedWorldRefiner.closedWorld.rtiNeed;
/// Generate a unique name for the [id]th closure field, with proposed name
/// [name].
///
/// The result is used as the name of [ClosureFieldElement]s, and must
/// therefore be unique to avoid breaking an invariant in the element model
/// (classes cannot declare multiple fields with the same name).
///
/// Also, the names should be distinct from real field names to prevent
/// clashes with selectors for those fields.
///
/// These names are not used in generated code, just as element name.
String getClosureVariableName(String name, int id) {
return "_captured_${name}_$id";
}
/// Generate a unique name for the [id]th box field, with proposed name
/// [name].
///
/// The result is used as the name of [BoxFieldElement]s, and must
/// therefore be unique to avoid breaking an invariant in the element model
/// (classes cannot declare multiple fields with the same name).
///
/// Also, the names should be distinct from real field names to prevent
/// clashes with selectors for those fields.
///
/// These names are not used in generated code, just as element name.
String getBoxFieldName(int id) {
return "_box_$id";
}
bool isCapturedVariable(Local element) {
return _capturedVariableMapping.containsKey(element);
}
void addCapturedVariable(Node node, Local variable) {
if (_capturedVariableMapping[variable] != null) {
reporter.internalError(node, 'In closure analyzer.');
}
_capturedVariableMapping[variable] = null;
}
void setCapturedVariableBoxField(Local variable, BoxFieldElement boxField) {
assert(isCapturedVariable(variable));
_capturedVariableMapping[variable] = boxField;
}
BoxFieldElement getCapturedVariableBoxField(Local variable) {
return _capturedVariableMapping[variable];
}
void translateFunction(Element element, FunctionExpression node) {
// For constructors the [element] and the [:elements[node]:] may differ.
// The [:elements[node]:] always points to the generative-constructor
// element, whereas the [element] might be the constructor-body element.
visit(node); // [visitFunctionExpression] will call [visitInvokable].
// When variables need to be boxed their [_capturedVariableMapping] is
// updated, but we delay updating the similar freeVariableMapping in the
// closure datas that capture these variables.
// The closures don't have their fields (in the closure class) set, either.
updateClosures();
}
void translateLazyInitializer(
FieldElement element, VariableDefinitions node, Expression initializer) {
visitInvokable(element, node, () {
visit(initializer);
});
updateClosures();
}
// This function runs through all of the existing closures and updates their
// free variables to the boxed value. It also adds the field-elements to the
// class representing the closure.
void updateClosures() {
for (FunctionExpression closure in closures) {
// The captured variables that need to be stored in a field of the closure
// class.
Set<Local> fieldCaptures = new Set<Local>();
Set<BoxLocal> boxes = new Set<BoxLocal>();
ClosureClassMap data = nodeMappingCache[closure];
// We get a copy of the keys and iterate over it, to avoid modifications
// to the map while iterating over it.
Iterable<Local> freeVariables = data.freeVariables.toList();
freeVariables.forEach((Local fromElement) {
assert(data.isFreeVariable(fromElement));
assert(data.freeVariableMap[fromElement] == null);
assert(isCapturedVariable(fromElement));
BoxFieldElement boxFieldElement =
getCapturedVariableBoxField(fromElement);
if (boxFieldElement == null) {
assert(fromElement is! BoxLocal);
// The variable has not been boxed.
fieldCaptures.add(fromElement);
} else {
// A boxed element.
data.freeVariableMap[fromElement] = boxFieldElement;
boxes.add(boxFieldElement.box);
}
});
ClosureClassElement closureClass = data.closureClassEntity;
assert(closureClass != null || (fieldCaptures.isEmpty && boxes.isEmpty));
void addClosureField(Local local, String name) {
ClosureFieldElement closureField =
new ClosureFieldElement(name, local, closureClass);
closureClass.addField(closureField, reporter);
data.freeVariableMap[local] = closureField;
}
// Add the box elements first so we get the same ordering.
// TODO(sra): What is the canonical order of multiple boxes?
for (BoxLocal capturedElement in boxes) {
addClosureField(capturedElement, capturedElement.name);
}
/// Comparator for locals. Position boxes before elements.
int compareLocals(a, b) {
if (a is Element && b is Element) {
return Elements.compareByPosition(a, b);
} else if (a is Element) {
return 1;
} else if (b is Element) {
return -1;
} else {
return a.name.compareTo(b.name);
}
}
for (Local capturedLocal in fieldCaptures.toList()..sort(compareLocals)) {
int id = closureFieldCounter++;
String name = getClosureVariableName(capturedLocal.name, id);
addClosureField(capturedLocal, name);
}
}
}
void useLocal(Local variable) {
// If the element is not declared in the current function and the element
// is not the closure itself we need to mark the element as free variable.
// Note that the check on [insideClosure] is not just an
// optimization: factories have type parameters as function
// parameters, and type parameters are declared in the class, not
// the factory.
bool inCurrentContext(LocalVariable variable) {
return variable == executableContext ||
variable.executableContext == executableContext;
}
if (insideClosure && !inCurrentContext(variable)) {
closureData.addFreeVariable(variable);
} else if (inTryStatement) {
// Don't mark the this-element or a self-reference. This would complicate
// things in the builder.
// Note that nested (named) functions are immutable.
if (variable != closureData.thisLocal &&
variable != closureData.closureEntity &&
variable is! TypeVariableLocal) {
closureData.localsUsedInTryOrSync.add(variable);
}
} else if (variable is LocalParameterElement &&
variable.functionDeclaration.asyncMarker == AsyncMarker.SYNC_STAR) {
// Parameters in a sync* function are shared between each Iterator created
// by the Iterable returned by the function, therefore they must be boxed.
closureData.localsUsedInTryOrSync.add(variable);
}
}
void useTypeVariableAsLocal(ResolutionTypeVariableType typeVariable) {
useLocal(new TypeVariableLocalVariable(
typeVariable, outermostElement, outermostElement.memberContext));
}
void declareLocal(LocalVariableElement element) {
scopeVariables.add(element);
}
void registerNeedsThis() {
if (closureData.thisLocal != null) {
useLocal(closureData.thisLocal);
}
}
visit(Node node) => node.accept(this);
visitNode(Node node) => node.visitChildren(this);
visitVariableDefinitions(VariableDefinitions node) {
if (node.type != null) {
visit(node.type);
}
for (Link<Node> link = node.definitions.nodes;
!link.isEmpty;
link = link.tail) {
Node definition = link.head;
LocalElement element = elements[definition];
assert(element != null);
if (!element.isInitializingFormal) {
declareLocal(element);
}
// We still need to visit the right-hand sides of the init-assignments.
// For SendSets don't visit the left again. Otherwise it would be marked
// as mutated.
if (definition is Send) {
Send assignment = definition;
Node arguments = assignment.argumentsNode;
if (arguments != null) {
visit(arguments);
}
} else {
visit(definition);
}
}
}
visitTypeAnnotation(TypeAnnotation node) {
MemberElement member = executableContext.memberContext;
ResolutionDartType type = elements.getType(node);
// TODO(karlklose,johnniwinther): if the type is null, the annotation is
// from a parameter which has been analyzed before the method has been
// resolved and the result has been thrown away.
if (compiler.options.enableTypeAssertions &&
type != null &&
type.containsTypeVariables) {
if (insideClosure && member.isFactoryConstructor) {
// This is a closure in a factory constructor. Since there is no
// [:this:], we have to mark the type arguments as free variables to
// capture them in the closure.
type.forEachTypeVariable((ResolutionTypeVariableType variable) {
useTypeVariableAsLocal(variable);
});
}
if (member.isInstanceMember && !member.isField) {
// In checked mode, using a type variable in a type annotation may lead
// to a runtime type check that needs to access the type argument and
// therefore the closure needs a this-element, if it is not in a field
// initializer; field initializers are evaluated in a context where
// the type arguments are available in locals.
registerNeedsThis();
}
}
}
visitIdentifier(Identifier node) {
if (node.isThis()) {
registerNeedsThis();
} else {
Element element = elements[node];
if (element != null && element.isTypeVariable) {
if (outermostElement.isConstructor || outermostElement.isField) {
TypeVariableElement typeVariable = element;
useTypeVariableAsLocal(typeVariable.type);
} else {
registerNeedsThis();
}
}
}
node.visitChildren(this);
}
visitSend(Send node) {
Element element = elements[node];
if (Elements.isLocal(element)) {
LocalElement localElement = element;
useLocal(localElement);
} else if (element != null && element.isTypeVariable) {
TypeVariableElement variable = element;
analyzeType(variable.type);
} else if (node.receiver == null &&
Elements.isInstanceSend(node, elements)) {
registerNeedsThis();
} else if (node.isSuperCall) {
registerNeedsThis();
} else if (node.isTypeTest || node.isTypeCast) {
TypeAnnotation annotation = node.typeAnnotationFromIsCheckOrCast;
ResolutionDartType type = elements.getType(annotation);
analyzeType(type);
} else if (node.isTypeTest) {
ResolutionDartType type =
elements.getType(node.typeAnnotationFromIsCheckOrCast);
analyzeType(type);
} else if (node.isTypeCast) {
ResolutionDartType type = elements.getType(node.arguments.head);
analyzeType(type);
}
node.visitChildren(this);
}
visitSendSet(SendSet node) {
Element element = elements[node];
if (Elements.isLocal(element)) {
mutatedVariables.add(element);
if (compiler.options.enableTypeAssertions) {
TypedElement typedElement = element;
analyzeTypeVariables(typedElement.type);
}
}
super.visitSendSet(node);
}
visitNewExpression(NewExpression node) {
ResolutionDartType type = elements.getType(node);
analyzeType(type);
node.visitChildren(this);
}
visitLiteralList(LiteralList node) {
ResolutionDartType type = elements.getType(node);
analyzeType(type);
node.visitChildren(this);
}
visitLiteralMap(LiteralMap node) {
ResolutionDartType type = elements.getType(node);
analyzeType(type);
node.visitChildren(this);
}
void analyzeTypeVariables(ResolutionDartType type) {
type.forEachTypeVariable((ResolutionTypeVariableType typeVariable) {
// Field initializers are inlined and access the type variable as
// normal parameters.
if (!outermostElement.isField && !outermostElement.isConstructor) {
registerNeedsThis();
} else {
useTypeVariableAsLocal(typeVariable);
}
});
}
void analyzeType(ResolutionDartType type) {
// TODO(johnniwinther): Find out why this can be null.
if (type == null) return;
if (outermostElement.isClassMember &&
rtiNeed.classNeedsRti(outermostElement.enclosingClass)) {
if (outermostElement.isConstructor || outermostElement.isField) {
analyzeTypeVariables(type);
} else if (outermostElement.isInstanceMember) {
if (type.containsTypeVariables) {
registerNeedsThis();
}
}
}
}
// If variables that are declared in the [node] scope are captured and need
// to be boxed create a box-element and update the [capturingScopes] in the
// current [closureData].
// The boxed variables are updated in the [capturedVariableMapping].
void attachCapturedScopeVariables(Node node) {
BoxLocalVariable box = null;
Map<LocalVariableElement, BoxFieldElement> scopeMapping =
new Map<LocalVariableElement, BoxFieldElement>();
void boxCapturedVariable(LocalVariableElement variable) {
if (isCapturedVariable(variable)) {
if (box == null) {
// TODO(floitsch): construct better box names.
String boxName = getBoxFieldName(closureFieldCounter++);
box = new BoxLocalVariable(boxName, executableContext.memberContext);
}
String elementName = variable.name;
String boxedName =
getClosureVariableName(elementName, boxedFieldCounter++);
// TODO(kasperl): Should this be a FieldElement instead?
BoxFieldElement boxed = new BoxFieldElement(boxedName, variable, box);
scopeMapping[variable] = boxed;
setCapturedVariableBoxField(variable, boxed);
}
}
for (LocalVariableElement variable in scopeVariables) {
// No need to box non-assignable elements.
if (!variable.isAssignable) continue;
if (!mutatedVariables.contains(variable)) continue;
boxCapturedVariable(variable);
}
if (!scopeMapping.isEmpty) {
CapturedScopeImpl scope = new CapturedScopeImpl(box, scopeMapping);
closureData.capturingScopes[node] = scope;
assert(closureInfo[node] == null);
closureInfo[node] = scope;
}
}
void inNewScope(Node node, Function action) {
List<LocalVariableElement> oldScopeVariables = scopeVariables;
scopeVariables = <LocalVariableElement>[];
action();
attachCapturedScopeVariables(node);
mutatedVariables.removeAll(scopeVariables);
scopeVariables = oldScopeVariables;
}
visitLoop(Loop node) {
inNewScope(node, () {
node.visitChildren(this);
});
}
visitFor(For node) {
List<LocalVariableElement> boxedLoopVariables = <LocalVariableElement>[];
inNewScope(node, () {
// First visit initializer and update so we can easily check if a loop
// variable was captured in one of these subexpressions.
if (node.initializer != null) visit(node.initializer);
if (node.update != null) visit(node.update);
// Loop variables that have not been captured yet can safely be flagged as
// non-mutated, because no nested function can observe the mutation.
if (node.initializer is VariableDefinitions) {
VariableDefinitions definitions = node.initializer;
definitions.definitions.nodes.forEach((Node node) {
LocalVariableElement local = elements[node];
if (!isCapturedVariable(local)) {
mutatedVariables.remove(local);
}
});
}
// Visit condition and body.
// This must happen after the above, so any loop variables mutated in the
// condition or body are indeed flagged as mutated.
if (node.conditionStatement != null) visit(node.conditionStatement);
if (node.body != null) visit(node.body);
// See if we have declared loop variables that need to be boxed.
if (node.initializer == null) return;
VariableDefinitions definitions =
node.initializer.asVariableDefinitions();
if (definitions == null) return;
for (Link<Node> link = definitions.definitions.nodes;
!link.isEmpty;
link = link.tail) {
Node definition = link.head;
LocalVariableElement element = elements[definition];
// Non-mutated variables should not be boxed. The mutatedVariables set
// gets cleared when 'inNewScope' returns, so check it here.
if (isCapturedVariable(element) && mutatedVariables.contains(element)) {
boxedLoopVariables.add(element);
}
}
});
CapturedScopeImpl scopeData = closureData.capturingScopes[node];
if (scopeData == null) return;
scopeData.boxedLoopVariables = boxedLoopVariables;
}
/** Returns a non-unique name for the given closure element. */
String computeClosureName(Element element) {
Link<String> parts = const Link<String>();
String ownName = element.name;
if (ownName == null || ownName == "") {
parts = parts.prepend("closure");
} else {
parts = parts.prepend(ownName);
}
for (Element enclosingElement = element.enclosingElement;
enclosingElement != null &&
(enclosingElement.kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY ||
enclosingElement.kind == ElementKind.GENERATIVE_CONSTRUCTOR ||
enclosingElement.kind == ElementKind.CLASS ||
enclosingElement.kind == ElementKind.FUNCTION ||
enclosingElement.kind == ElementKind.GETTER ||
enclosingElement.kind == ElementKind.SETTER);
enclosingElement = enclosingElement.enclosingElement) {
// TODO(johnniwinther): Simplify computed names.
if (enclosingElement.isGenerativeConstructor ||
enclosingElement.isGenerativeConstructorBody ||
enclosingElement.isFactoryConstructor) {
ConstructorElement constructor = enclosingElement;
parts = parts.prepend(utils.reconstructConstructorName(constructor));
} else {
String surroundingName =
Elements.operatorNameToIdentifier(enclosingElement.name);
parts = parts.prepend(surroundingName);
}
// A generative constructors's parent is the class; the class name is
// already part of the generative constructor's name.
if (enclosingElement.kind == ElementKind.GENERATIVE_CONSTRUCTOR) break;
}
StringBuffer sb = new StringBuffer();
parts.printOn(sb, '_');
return sb.toString();
}
JavaScriptBackend get backend => compiler.backend;
ClosureClassMap globalizeClosure(
FunctionExpression node, LocalFunctionElement element) {
String closureName = computeClosureName(element);
ClosureClassElement globalizedElement =
new ClosureClassElement(node, closureName, compiler, element);
// Extend [globalizedElement] as an instantiated class in the closed world.
closedWorldRefiner.registerClosureClass(globalizedElement);
MethodElement callElement = new SynthesizedCallMethodElementX(
Identifiers.call, element, globalizedElement, node, elements);
backend.mirrorsDataBuilder.maybeMarkClosureAsNeededForReflection(
globalizedElement, callElement, element);
MemberElement enclosing = element.memberContext;
enclosing.nestedClosures.add(callElement);
globalizedElement.addMember(callElement, reporter);
globalizedElement.computeAllClassMembers(compiler.resolution);
// The nested function's 'this' is the same as the one for the outer
// function. It could be [null] if we are inside a static method.
ThisLocal thisElement = closureData.thisLocal;
return new ClosureClassMap(
element, globalizedElement, callElement, thisElement);
}
void visitInvokable(
ExecutableElement element, Node node, void visitChildren()) {
bool oldInsideClosure = insideClosure;
Element oldFunctionElement = executableContext;
ClosureClassMap oldClosureData = closureData;
insideClosure = outermostElement != null;
LocalFunctionElement closure;
executableContext = element;
bool needsRti = false;
if (insideClosure) {
closure = element;
closures.add(node);
nodeMappingCache[node] = closureData = globalizeClosure(node, closure);
needsRti = compiler.options.enableTypeAssertions ||
rtiNeed.localFunctionNeedsRti(closure);
} else {
outermostElement = element;
ThisLocal thisElement = null;
if (element.isInstanceMember || element.isGenerativeConstructor) {
MemberElement member = element;
thisElement = new ThisLocalVariable(member);
}
closureData = new ClosureClassMap(null, null, null, thisElement);
memberMappingCache[element] = closureData;
memberMappingCache[element.declaration] = closureData;
if (element is MethodElement) {
needsRti = compiler.options.enableTypeAssertions ||
rtiNeed.methodNeedsRti(element);
}
}
if (closureData.callMethod != null) {
memberMappingCache[closureData.callMethod] = closureData;
}
inNewScope(node, () {
// If the method needs RTI, or checked mode is set, we need to
// escape the potential type variables used in that closure.
if (needsRti) {
analyzeTypeVariables(element.type);
}
visitChildren();
});
ClosureClassMap savedClosureData = closureData;
bool savedInsideClosure = insideClosure;
// Restore old values.
insideClosure = oldInsideClosure;
closureData = oldClosureData;
executableContext = oldFunctionElement;
// Mark all free variables as captured and use them in the outer function.
Iterable<Local> freeVariables = savedClosureData.freeVariables;
assert(freeVariables.isEmpty || savedInsideClosure);
for (Local freeVariable in freeVariables) {
addCapturedVariable(node, freeVariable);
useLocal(freeVariable);
}
}
visitFunctionExpression(FunctionExpression node) {
Element element = elements[node];
if (element.isRegularParameter) {
// TODO(ahe): This is a hack. This method should *not* call
// visitChildren.
return node.name.accept(this);
}
visitInvokable(element, node, () {
// TODO(ahe): This is problematic. The backend should not repeat
// the work of the resolver. It is the resolver's job to create
// parameters, etc. Other phases should only visit statements.
if (node.parameters != null) node.parameters.accept(this);
if (node.initializers != null) node.initializers.accept(this);
if (node.body != null) node.body.accept(this);
});
}
visitTryStatement(TryStatement node) {
// TODO(ngeoffray): implement finer grain state.
bool oldInTryStatement = inTryStatement;
inTryStatement = true;
node.visitChildren(this);
inTryStatement = oldInTryStatement;
}
visitCatchBlock(CatchBlock node) {
if (node.type != null) {
// The "on T" clause may contain type variables.
analyzeType(elements.getType(node.type));
}
if (node.formals != null) {
node.formals.visitChildren(this);
}
node.block.accept(this);
}
visitAsyncForIn(AsyncForIn node) {
// An `await for` loop is enclosed in an implicit try-finally.
bool oldInTryStatement = inTryStatement;
inTryStatement = true;
visitLoop(node);
inTryStatement = oldInTryStatement;
}
}
/// A type variable as a local variable.
class TypeVariableLocal implements Local {
final TypeVariableType typeVariable;
TypeVariableLocal(this.typeVariable);
String get name => typeVariable.element.name;
int get hashCode => typeVariable.hashCode;
bool operator ==(other) {
if (other is! TypeVariableLocal) return false;
return typeVariable == other.typeVariable;
}
String toString() {
StringBuffer sb = new StringBuffer();
sb.write('type_variable_local(');
sb.write(typeVariable);
sb.write(')');
return sb.toString();
}
}
class TypeVariableLocalVariable extends TypeVariableLocal
implements LocalVariable {
final ExecutableElement executableContext;
final MemberElement memberContext;
TypeVariableLocalVariable(
TypeVariableType typeVariable, this.executableContext, this.memberContext)
: super(typeVariable);
}
///
/// Move the below classes to a JS model eventually.
///
abstract class JSEntity implements MemberEntity {
Local get declaredEntity;
}
abstract class PrivatelyNamedJSEntity implements JSEntity {
Entity get rootOfScope;
}