blob: ccce835a7ce7a71bea79a9485a063763ec524551 [file] [log] [blame]
// Copyright (c) 2017, 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:js_runtime/shared/embedded_names.dart';
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/class_hierarchy.dart' as ir;
import 'package:kernel/core_types.dart' as ir;
import 'package:kernel/type_environment.dart' as ir;
import '../closure.dart' show BoxLocal, ThisLocal;
import '../common.dart';
import '../constants/values.dart';
import '../elements/entities.dart';
import '../elements/entity_utils.dart' as utils;
import '../elements/indexed.dart';
import '../elements/types.dart';
import '../environment.dart';
import '../ir/util.dart';
import '../js/js.dart' as js;
import '../js_backend/native_data.dart';
import '../js_emitter/code_emitter_task.dart';
import '../js_model/closure.dart';
import '../js_model/elements.dart';
import '../js_model/element_map.dart';
import '../js_model/locals.dart';
import '../kernel/element_map.dart';
import '../kernel/element_map_impl.dart';
import '../kernel/env.dart';
import '../ssa/type_builder.dart';
import 'element_map.dart';
/// Interface for kernel queries needed to implement the [CodegenWorldBuilder].
abstract class KernelToWorldBuilder implements JsToElementMap {
/// Returns `true` if [field] has a constant initializer.
bool hasConstantFieldInitializer(FieldEntity field);
/// Returns the constant initializer for [field].
ConstantValue getConstantFieldInitializer(FieldEntity field);
/// Calls [f] for each parameter of [function] providing the type and name of
/// the parameter and the [defaultValue] if the parameter is optional.
void forEachParameter(FunctionEntity function,
void f(DartType type, String name, ConstantValue defaultValue));
}
class JsKernelToElementMap extends KernelToElementMapBase
with JsElementCreatorMixin
implements KernelToWorldBuilder, JsToElementMap {
Map<ir.Library, IndexedLibrary> libraryMap = <ir.Library, IndexedLibrary>{};
Map<ir.Class, IndexedClass> classMap = <ir.Class, IndexedClass>{};
Map<ir.Typedef, IndexedTypedef> typedefMap = <ir.Typedef, IndexedTypedef>{};
/// Map from [ir.TypeParameter] nodes to the corresponding
/// [TypeVariableEntity].
///
/// Normally the type variables are [IndexedTypeVariable]s, but for type
/// parameters on local function (in the frontend) these are _not_ since
/// their type declaration is neither a class nor a member. In the backend,
/// these type parameters belong to the call-method and are therefore indexed.
Map<ir.TypeParameter, TypeVariableEntity> typeVariableMap =
<ir.TypeParameter, TypeVariableEntity>{};
Map<ir.Member, IndexedConstructor> constructorMap =
<ir.Member, IndexedConstructor>{};
Map<ir.Procedure, IndexedFunction> methodMap =
<ir.Procedure, IndexedFunction>{};
Map<ir.Field, IndexedField> fieldMap = <ir.Field, IndexedField>{};
Map<ir.TreeNode, Local> localFunctionMap = <ir.TreeNode, Local>{};
/// Map from members to the call methods created for their nested closures.
Map<MemberEntity, List<FunctionEntity>> _nestedClosureMap =
<MemberEntity, List<FunctionEntity>>{};
@override
NativeBasicData nativeBasicData;
Map<FunctionEntity, JGeneratorBody> _generatorBodies =
<FunctionEntity, JGeneratorBody>{};
Map<ClassEntity, List<MemberEntity>> _injectedClassMembers =
<ClassEntity, List<MemberEntity>>{};
JsKernelToElementMap(DiagnosticReporter reporter, Environment environment,
KernelToElementMapImpl _elementMap, Iterable<MemberEntity> liveMembers)
: super(_elementMap.options, reporter, environment) {
env = _elementMap.env;
for (int libraryIndex = 0;
libraryIndex < _elementMap.libraries.length;
libraryIndex++) {
IndexedLibrary oldLibrary = _elementMap.libraries.getEntity(libraryIndex);
LibraryEnv env = _elementMap.libraries.getEnv(oldLibrary);
LibraryData data = _elementMap.libraries.getData(oldLibrary);
IndexedLibrary newLibrary = convertLibrary(oldLibrary);
libraryMap[env.library] =
libraries.register<IndexedLibrary, LibraryData, LibraryEnv>(
newLibrary, data.copy(), env.copyLive(_elementMap, liveMembers));
assert(newLibrary.libraryIndex == oldLibrary.libraryIndex);
}
for (int classIndex = 0;
classIndex < _elementMap.classes.length;
classIndex++) {
IndexedClass oldClass = _elementMap.classes.getEntity(classIndex);
ClassEnv env = _elementMap.classes.getEnv(oldClass);
ClassData data = _elementMap.classes.getData(oldClass);
IndexedLibrary oldLibrary = oldClass.library;
LibraryEntity newLibrary = libraries.getEntity(oldLibrary.libraryIndex);
IndexedClass newClass = convertClass(newLibrary, oldClass);
classMap[env.cls] = classes.register(
newClass, data.copy(), env.copyLive(_elementMap, liveMembers));
assert(newClass.classIndex == oldClass.classIndex);
}
for (int typedefIndex = 0;
typedefIndex < _elementMap.typedefs.length;
typedefIndex++) {
IndexedTypedef oldTypedef = _elementMap.typedefs.getEntity(typedefIndex);
TypedefData data = _elementMap.typedefs.getData(oldTypedef);
IndexedLibrary oldLibrary = oldTypedef.library;
LibraryEntity newLibrary = libraries.getEntity(oldLibrary.libraryIndex);
IndexedTypedef newTypedef = convertTypedef(newLibrary, oldTypedef);
typedefMap[data.node] = typedefs.register(
newTypedef,
new TypedefData(
data.node,
newTypedef,
new TypedefType(
newTypedef,
new List<DartType>.filled(
data.node.typeParameters.length, const DynamicType()),
getDartType(data.node.type))));
assert(newTypedef.typedefIndex == oldTypedef.typedefIndex);
}
for (int memberIndex = 0;
memberIndex < _elementMap.members.length;
memberIndex++) {
IndexedMember oldMember = _elementMap.members.getEntity(memberIndex);
if (!liveMembers.contains(oldMember)) {
members.skipIndex(oldMember.memberIndex);
continue;
}
MemberDataImpl data = _elementMap.members.getData(oldMember);
IndexedLibrary oldLibrary = oldMember.library;
IndexedClass oldClass = oldMember.enclosingClass;
LibraryEntity newLibrary = libraries.getEntity(oldLibrary.libraryIndex);
ClassEntity newClass =
oldClass != null ? classes.getEntity(oldClass.classIndex) : null;
IndexedMember newMember = convertMember(newLibrary, newClass, oldMember);
members.register(newMember, data.copy());
assert(newMember.memberIndex == oldMember.memberIndex);
if (newMember.isField) {
fieldMap[data.node] = newMember;
} else if (newMember.isConstructor) {
constructorMap[data.node] = newMember;
} else {
methodMap[data.node] = newMember;
}
}
for (int typeVariableIndex = 0;
typeVariableIndex < _elementMap.typeVariables.length;
typeVariableIndex++) {
IndexedTypeVariable oldTypeVariable =
_elementMap.typeVariables.getEntity(typeVariableIndex);
TypeVariableData oldTypeVariableData =
_elementMap.typeVariables.getData(oldTypeVariable);
Entity newTypeDeclaration;
if (oldTypeVariable.typeDeclaration is ClassEntity) {
IndexedClass cls = oldTypeVariable.typeDeclaration;
newTypeDeclaration = classes.getEntity(cls.classIndex);
} else if (oldTypeVariable.typeDeclaration is MemberEntity) {
IndexedMember member = oldTypeVariable.typeDeclaration;
newTypeDeclaration = members.getEntity(member.memberIndex);
} else {
assert(oldTypeVariable.typeDeclaration is Local);
}
IndexedTypeVariable newTypeVariable = createTypeVariable(
newTypeDeclaration, oldTypeVariable.name, oldTypeVariable.index);
typeVariableMap[oldTypeVariableData.node] =
typeVariables.register<IndexedTypeVariable, TypeVariableData>(
newTypeVariable, oldTypeVariableData.copy());
assert(newTypeVariable.typeVariableIndex ==
oldTypeVariable.typeVariableIndex);
}
//typeVariableMap.keys.forEach((n) => print(n.parent));
// TODO(johnniwinther): We should close the environment in the beginning of
// this constructor but currently we need the [MemberEntity] to query if the
// member is live, thus potentially creating the [MemberEntity] in the
// process. Avoid this.
_elementMap.envIsClosed = true;
}
@override
void forEachNestedClosure(
MemberEntity member, void f(FunctionEntity closure)) {
assert(checkFamily(member));
_nestedClosureMap[member]?.forEach(f);
}
@override
InterfaceType getMemberThisType(MemberEntity member) {
return members.getData(member).getMemberThisType(this);
}
@override
ClassTypeVariableAccess getClassTypeVariableAccessForMember(
MemberEntity member) {
return members.getData(member).classTypeVariableAccess;
}
@override
bool checkFamily(Entity entity) {
assert(
'$entity'.startsWith(jsElementPrefix),
failedAt(entity,
"Unexpected entity $entity, expected family $jsElementPrefix."));
return true;
}
@override
Spannable getSpannable(MemberEntity member, ir.Node node) {
SourceSpan sourceSpan;
if (node is ir.TreeNode) {
sourceSpan = computeSourceSpanFromTreeNode(node);
}
sourceSpan ??= getSourceSpan(member, null);
return sourceSpan;
}
@override
Iterable<LibraryEntity> get libraryListInternal {
return libraryMap.values;
}
@override
LibraryEntity getLibraryInternal(ir.Library node, [LibraryEnv env]) {
LibraryEntity library = libraryMap[node];
assert(library != null, "No library entity for $node");
return library;
}
@override
ClassEntity getClassInternal(ir.Class node, [ClassEnv env]) {
ClassEntity cls = classMap[node];
assert(cls != null, "No class entity for $node");
return cls;
}
@override
FieldEntity getFieldInternal(ir.Field node) {
FieldEntity field = fieldMap[node];
assert(field != null, "No field entity for $node");
return field;
}
@override
FunctionEntity getMethodInternal(ir.Procedure node) {
FunctionEntity function = methodMap[node];
assert(function != null, "No function entity for $node");
return function;
}
@override
ConstructorEntity getConstructorInternal(ir.Member node) {
ConstructorEntity constructor = constructorMap[node];
assert(constructor != null, "No constructor entity for $node");
return constructor;
}
@override
TypeVariableEntity getTypeVariableInternal(ir.TypeParameter node) {
TypeVariableEntity typeVariable = typeVariableMap[node];
if (typeVariable == null) {
if (node.parent is ir.FunctionNode) {
ir.FunctionNode func = node.parent;
int index = func.typeParameters.indexOf(node);
if (func.parent is ir.Constructor) {
ir.Constructor constructor = func.parent;
ir.Class cls = constructor.enclosingClass;
typeVariableMap[node] =
typeVariable = getTypeVariableInternal(cls.typeParameters[index]);
} else if (func.parent is ir.Procedure) {
ir.Procedure procedure = func.parent;
if (procedure.kind == ir.ProcedureKind.Factory) {
ir.Class cls = procedure.enclosingClass;
typeVariableMap[node] = typeVariable =
getTypeVariableInternal(cls.typeParameters[index]);
}
}
}
}
assert(typeVariable != null,
"No type variable entity for $node on ${node.parent is ir.FunctionNode ? node.parent.parent : node.parent}");
return typeVariable;
}
@override
TypedefEntity getTypedefInternal(ir.Typedef node) {
TypedefEntity typedef = typedefMap[node];
assert(typedef != null, "No typedef entity for $node");
return typedef;
}
@override
FunctionEntity getConstructorBody(ir.Constructor node) {
ConstructorEntity constructor = getConstructor(node);
return _getConstructorBody(node, constructor);
}
FunctionEntity _getConstructorBody(
ir.Constructor node, covariant IndexedConstructor constructor) {
ConstructorDataImpl data = members.getData(constructor);
if (data.constructorBody == null) {
JConstructorBody constructorBody = createConstructorBody(constructor);
members.register<IndexedFunction, FunctionData>(
constructorBody,
new ConstructorBodyDataImpl(
node,
node.function,
new SpecialMemberDefinition(
constructorBody, node, MemberKind.constructorBody)));
IndexedClass cls = constructor.enclosingClass;
ClassEnvImpl classEnv = classes.getEnv(cls);
// TODO(johnniwinther): Avoid this by only including live members in the
// js-model.
classEnv.addConstructorBody(constructorBody);
data.constructorBody = constructorBody;
}
return data.constructorBody;
}
@override
JConstructorBody createConstructorBody(ConstructorEntity constructor);
@override
MemberDefinition getMemberDefinition(MemberEntity member) {
return getMemberDefinitionInternal(member);
}
@override
ClassDefinition getClassDefinition(ClassEntity cls) {
return getClassDefinitionInternal(cls);
}
@override
ConstantValue getFieldConstantValue(covariant IndexedField field) {
assert(checkFamily(field));
FieldData data = members.getData(field);
return data.getFieldConstantValue(this);
}
@override
bool hasConstantFieldInitializer(covariant IndexedField field) {
FieldData data = members.getData(field);
return data.hasConstantFieldInitializer(this);
}
@override
ConstantValue getConstantFieldInitializer(covariant IndexedField field) {
FieldData data = members.getData(field);
return data.getConstantFieldInitializer(this);
}
@override
void forEachParameter(covariant IndexedFunction function,
void f(DartType type, String name, ConstantValue defaultValue)) {
FunctionData data = members.getData(function);
data.forEachParameter(this, f);
}
@override
void forEachConstructorBody(
IndexedClass cls, void f(ConstructorBodyEntity member)) {
ClassEnv env = classes.getEnv(cls);
env.forEachConstructorBody(f);
}
@override
void forEachInjectedClassMember(
IndexedClass cls, void f(MemberEntity member)) {
_injectedClassMembers[cls]?.forEach(f);
}
JRecordField _constructRecordFieldEntry(
InterfaceType memberThisType,
ir.VariableDeclaration variable,
BoxLocal boxLocal,
JClass container,
Map<String, MemberEntity> memberMap,
KernelToLocalsMap localsMap) {
Local local = localsMap.getLocalVariable(variable);
JRecordField boxedField =
new JRecordField(local.name, boxLocal, container, variable.isConst);
members.register(
boxedField,
new ClosureFieldData(
new ClosureMemberDefinition(
boxedField,
computeSourceSpanFromTreeNode(variable),
MemberKind.closureField,
variable),
memberThisType));
memberMap[boxedField.name] = boxedField;
return boxedField;
}
/// Make a container controlling access to records, that is, variables that
/// are accessed in different scopes. This function creates the container
/// and returns a map of locals to the corresponding records created.
@override
Map<Local, JRecordField> makeRecordContainer(
KernelScopeInfo info, MemberEntity member, KernelToLocalsMap localsMap) {
Map<Local, JRecordField> boxedFields = {};
if (info.boxedVariables.isNotEmpty) {
NodeBox box = info.capturedVariablesAccessor;
Map<String, MemberEntity> memberMap = <String, MemberEntity>{};
JRecord container = new JRecord(member.library, box.name);
InterfaceType thisType = new InterfaceType(container, const <DartType>[]);
InterfaceType supertype = commonElements.objectType;
ClassData containerData = new RecordClassData(
new ClosureClassDefinition(container,
computeSourceSpanFromTreeNode(getMemberDefinition(member).node)),
thisType,
supertype,
getOrderedTypeSet(supertype.element).extendClass(thisType));
classes.register(container, containerData, new RecordEnv(memberMap));
BoxLocal boxLocal = new BoxLocal(box.name);
InterfaceType memberThisType = member.enclosingClass != null
? elementEnvironment.getThisType(member.enclosingClass)
: null;
for (ir.VariableDeclaration variable in info.boxedVariables) {
boxedFields[localsMap.getLocalVariable(variable)] =
_constructRecordFieldEntry(memberThisType, variable, boxLocal,
container, memberMap, localsMap);
}
}
return boxedFields;
}
bool _isInRecord(
Local local, Map<Local, JRecordField> recordFieldsVisibleInScope) =>
recordFieldsVisibleInScope.containsKey(local);
KernelClosureClassInfo constructClosureClass(
MemberEntity member,
ir.FunctionNode node,
JLibrary enclosingLibrary,
Map<Local, JRecordField> recordFieldsVisibleInScope,
KernelScopeInfo info,
KernelToLocalsMap localsMap,
InterfaceType supertype,
{bool createSignatureMethod}) {
InterfaceType memberThisType = member.enclosingClass != null
? elementEnvironment.getThisType(member.enclosingClass)
: null;
ClassTypeVariableAccess typeVariableAccess =
members.getData(member).classTypeVariableAccess;
if (typeVariableAccess == ClassTypeVariableAccess.instanceField) {
// A closure in a field initializer will only be executed in the
// constructor and type variables are therefore accessed through
// parameters.
typeVariableAccess = ClassTypeVariableAccess.parameter;
}
String name = _computeClosureName(node);
SourceSpan location = computeSourceSpanFromTreeNode(node);
Map<String, MemberEntity> memberMap = <String, MemberEntity>{};
JClass classEntity = new JClosureClass(enclosingLibrary, name);
// Create a classData and set up the interfaces and subclass
// relationships that _ensureSupertypes and _ensureThisAndRawType are doing
InterfaceType thisType = new InterfaceType(classEntity, const <DartType>[]);
ClosureClassData closureData = new ClosureClassData(
new ClosureClassDefinition(classEntity, location),
thisType,
supertype,
getOrderedTypeSet(supertype.element).extendClass(thisType));
classes.register(classEntity, closureData, new ClosureClassEnv(memberMap));
Local closureEntity;
if (node.parent is ir.FunctionDeclaration) {
ir.FunctionDeclaration parent = node.parent;
closureEntity = localsMap.getLocalVariable(parent.variable);
} else if (node.parent is ir.FunctionExpression) {
closureEntity = new JLocal('', localsMap.currentMember);
}
FunctionEntity callMethod = new JClosureCallMethod(
classEntity, getParameterStructure(node), getAsyncMarker(node));
_nestedClosureMap
.putIfAbsent(member, () => <FunctionEntity>[])
.add(callMethod);
// We need create the type variable here - before we try to make local
// variables from them (in `JsScopeInfo.from` called through
// `KernelClosureClassInfo.fromScopeInfo` below).
int index = 0;
for (ir.TypeParameter typeParameter in node.typeParameters) {
typeVariableMap[typeParameter] = typeVariables.register(
createTypeVariable(callMethod, typeParameter.name, index),
new TypeVariableData(typeParameter));
index++;
}
KernelClosureClassInfo closureClassInfo =
new KernelClosureClassInfo.fromScopeInfo(
classEntity,
node,
<Local, JRecordField>{},
info,
localsMap,
closureEntity,
info.hasThisLocal ? new ThisLocal(localsMap.currentMember) : null,
this);
_buildClosureClassFields(closureClassInfo, member, memberThisType, info,
localsMap, recordFieldsVisibleInScope, memberMap);
if (createSignatureMethod) {
_constructSignatureMethod(closureClassInfo, memberMap, node,
memberThisType, location, typeVariableAccess);
}
closureData.callType = getFunctionType(node);
members.register<IndexedFunction, FunctionData>(
callMethod,
new ClosureFunctionData(
new ClosureMemberDefinition(
callMethod, location, MemberKind.closureCall, node.parent),
memberThisType,
closureData.callType,
node,
typeVariableAccess));
memberMap[callMethod.name] = closureClassInfo.callMethod = callMethod;
return closureClassInfo;
}
void _buildClosureClassFields(
KernelClosureClassInfo closureClassInfo,
MemberEntity member,
InterfaceType memberThisType,
KernelScopeInfo info,
KernelToLocalsMap localsMap,
Map<Local, JRecordField> recordFieldsVisibleInScope,
Map<String, MemberEntity> memberMap) {
// TODO(efortuna): Limit field number usage to when we need to distinguish
// between two variables with the same name from different scopes.
int fieldNumber = 0;
// For the captured variables that are boxed, ensure this closure has a
// field to reference the box. This puts the boxes first in the closure like
// the AST front-end, but otherwise there is no reason to separate this loop
// from the one below.
// TODO(redemption): Merge this loop and the following.
for (ir.Node variable in info.freeVariables) {
if (variable is ir.VariableDeclaration) {
Local capturedLocal = localsMap.getLocalVariable(variable);
if (_isInRecord(capturedLocal, recordFieldsVisibleInScope)) {
bool constructedField = _constructClosureFieldForRecord(
capturedLocal,
closureClassInfo,
memberThisType,
memberMap,
variable,
recordFieldsVisibleInScope,
fieldNumber);
if (constructedField) fieldNumber++;
}
}
}
// Add a field for the captured 'this'.
if (info.thisUsedAsFreeVariable) {
_constructClosureField(
closureClassInfo.thisLocal,
closureClassInfo,
memberThisType,
memberMap,
getClassDefinition(member.enclosingClass).node,
true,
false,
fieldNumber);
fieldNumber++;
}
for (ir.Node variable in info.freeVariables) {
// Make a corresponding field entity in this closure class for the
// free variables in the KernelScopeInfo.freeVariable.
if (variable is ir.VariableDeclaration) {
Local capturedLocal = localsMap.getLocalVariable(variable);
if (!_isInRecord(capturedLocal, recordFieldsVisibleInScope)) {
_constructClosureField(
capturedLocal,
closureClassInfo,
memberThisType,
memberMap,
variable,
variable.isConst,
false, // Closure field is never assigned (only box fields).
fieldNumber);
fieldNumber++;
}
} else if (variable is TypeVariableTypeWithContext) {
_constructClosureField(
localsMap.getLocalTypeVariable(variable.type, this),
closureClassInfo,
memberThisType,
memberMap,
variable.type.parameter,
true,
false,
fieldNumber);
fieldNumber++;
} else {
throw new UnsupportedError("Unexpected field node type: $variable");
}
}
}
/// Records point to one or more local variables declared in another scope
/// that are captured in a scope. Access to those variables goes entirely
/// through the record container, so we only create a field for the *record*
/// holding [capturedLocal] and not the individual local variables accessed
/// through the record. Records, by definition, are not mutable (though the
/// locals they contain may be). Returns `true` if we constructed a new field
/// in the closure class.
bool _constructClosureFieldForRecord(
Local capturedLocal,
KernelClosureClassInfo closureClassInfo,
InterfaceType memberThisType,
Map<String, MemberEntity> memberMap,
ir.TreeNode sourceNode,
Map<Local, JRecordField> recordFieldsVisibleInScope,
int fieldNumber) {
JRecordField recordField = recordFieldsVisibleInScope[capturedLocal];
// Don't construct a new field if the box that holds this local already has
// a field in the closure class.
if (closureClassInfo.localToFieldMap.containsKey(recordField.box)) {
closureClassInfo.boxedVariables[capturedLocal] = recordField;
return false;
}
FieldEntity closureField = new JClosureField(
'_box_$fieldNumber', closureClassInfo, true, false, recordField.box);
members.register<IndexedField, FieldData>(
closureField,
new ClosureFieldData(
new ClosureMemberDefinition(
closureClassInfo.localToFieldMap[capturedLocal],
computeSourceSpanFromTreeNode(sourceNode),
MemberKind.closureField,
sourceNode),
memberThisType));
memberMap[closureField.name] = closureField;
closureClassInfo.localToFieldMap[recordField.box] = closureField;
closureClassInfo.boxedVariables[capturedLocal] = recordField;
return true;
}
void _constructSignatureMethod(
KernelClosureClassInfo closureClassInfo,
Map<String, MemberEntity> memberMap,
ir.FunctionNode closureSourceNode,
InterfaceType memberThisType,
SourceSpan location,
ClassTypeVariableAccess typeVariableAccess) {
FunctionEntity signatureMethod = new JSignatureMethod(
closureClassInfo.closureClassEntity.library,
closureClassInfo.closureClassEntity,
// SignatureMethod takes no arguments.
const ParameterStructure(0, 0, const [], 0),
AsyncMarker.SYNC);
members.register<IndexedFunction, FunctionData>(
signatureMethod,
new SignatureFunctionData(
new SpecialMemberDefinition(signatureMethod,
closureSourceNode.parent, MemberKind.signature),
memberThisType,
null,
closureSourceNode.typeParameters,
typeVariableAccess));
memberMap[signatureMethod.name] =
closureClassInfo.signatureMethod = signatureMethod;
}
void _constructClosureField(
Local capturedLocal,
KernelClosureClassInfo closureClassInfo,
InterfaceType memberThisType,
Map<String, MemberEntity> memberMap,
ir.TreeNode sourceNode,
bool isConst,
bool isAssignable,
int fieldNumber) {
FieldEntity closureField = new JClosureField(
_getClosureVariableName(capturedLocal.name, fieldNumber),
closureClassInfo,
isConst,
isAssignable,
capturedLocal);
members.register<IndexedField, FieldData>(
closureField,
new ClosureFieldData(
new ClosureMemberDefinition(
closureClassInfo.localToFieldMap[capturedLocal],
computeSourceSpanFromTreeNode(sourceNode),
MemberKind.closureField,
sourceNode),
memberThisType));
memberMap[closureField.name] = closureField;
closureClassInfo.localToFieldMap[capturedLocal] = closureField;
}
// Returns a non-unique name for the given closure element.
String _computeClosureName(ir.TreeNode treeNode) {
var parts = <String>[];
// First anonymous is called 'closure', outer ones called '' to give a
// compound name where increasing nesting level corresponds to extra
// underscores.
var anonymous = 'closure';
ir.TreeNode current = treeNode;
// TODO(johnniwinther): Simplify computed names.
while (current != null) {
var node = current;
if (node is ir.FunctionExpression) {
parts.add(anonymous);
anonymous = '';
} else if (node is ir.FunctionDeclaration) {
String name = node.variable.name;
if (name != null && name != "") {
parts.add(utils.operatorNameToIdentifier(name));
} else {
parts.add(anonymous);
anonymous = '';
}
} else if (node is ir.Class) {
// TODO(sra): Do something with abstracted mixin type names like '^#U0'.
parts.add(node.name);
break;
} else if (node is ir.Procedure) {
if (node.kind == ir.ProcedureKind.Factory) {
parts.add(utils.reconstructConstructorName(getMember(node)));
} else {
parts.add(utils.operatorNameToIdentifier(node.name.name));
}
} else if (node is ir.Constructor) {
parts.add(utils.reconstructConstructorName(getMember(node)));
break;
}
current = current.parent;
}
return parts.reversed.join('_');
}
/// 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";
}
@override
JGeneratorBody getGeneratorBody(covariant IndexedFunction function) {
JGeneratorBody generatorBody = _generatorBodies[function];
if (generatorBody == null) {
FunctionData functionData = members.getData(function);
ir.TreeNode node = functionData.definition.node;
DartType elementType =
elementEnvironment.getFunctionAsyncOrSyncStarElementType(function);
generatorBody = createGeneratorBody(function, elementType);
members.register<IndexedFunction, FunctionData>(
generatorBody,
new GeneratorBodyFunctionData(
functionData,
new SpecialMemberDefinition(
generatorBody, node, MemberKind.generatorBody)));
if (function.enclosingClass != null) {
// TODO(sra): Integrate this with ClassEnvImpl.addConstructorBody ?
(_injectedClassMembers[function.enclosingClass] ??= <MemberEntity>[])
.add(generatorBody);
}
}
return generatorBody;
}
@override
JGeneratorBody createGeneratorBody(
FunctionEntity function, DartType elementType);
@override
js.Template getJsBuiltinTemplate(
ConstantValue constant, CodeEmitterTask emitter) {
int index = extractEnumIndexFromConstantValue(
constant, commonElements.jsBuiltinEnum);
if (index == null) return null;
return emitter.builtinTemplateFor(JsBuiltin.values[index]);
}
}