blob: bf30c6581303845c53a758acd567da4e70a14917 [file] [log] [blame] [edit]
// 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_shared/variance.dart';
import 'package:kernel/ast.dart' as ir;
import '../closure.dart';
import '../common.dart';
import '../elements/entities.dart';
import '../elements/names.dart';
import '../elements/types.dart';
import '../ir/closure.dart';
import '../ir/element_map.dart';
import '../js_backend/annotations.dart';
import '../js_model/element_map.dart';
import '../ordered_typeset.dart';
import '../serialization/deferrable.dart';
import '../serialization/serialization.dart';
import '../universe/selector.dart';
import 'class_type_variable_access.dart';
import 'elements.dart';
import 'env.dart';
import 'js_world_builder.dart' show JClosedWorldBuilder;
class ClosureDataImpl implements ClosureData {
/// Tag used for identifying serialized [ClosureData] objects in a
/// debugging data stream.
static const String tag = 'closure-data';
final JsToElementMap _elementMap;
/// Map of the scoping information that corresponds to a particular entity.
final Map<MemberEntity, ScopeInfo> _scopeMap;
final Deferrable<Map<ir.TreeNode, CapturedScope>> _capturedScopesMap;
// Indicates the type variables (if any) that are captured in a given
// Signature function.
final Deferrable<Map<MemberEntity, CapturedScope>>
_capturedScopeForSignatureMap;
final Deferrable<Map<ir.LocalFunction, ClosureRepresentationInfo>>
_localClosureRepresentationMap;
final Map<MemberEntity, MemberEntity> _enclosingMembers;
ClosureDataImpl(
this._elementMap,
this._scopeMap,
Map<ir.TreeNode, CapturedScope> capturedScopesMap,
Map<MemberEntity, CapturedScope> capturedScopeForSignatureMap,
Map<ir.LocalFunction, ClosureRepresentationInfo>
localClosureRepresentationMap,
this._enclosingMembers,
) : _capturedScopesMap = Deferrable.eager(capturedScopesMap),
_capturedScopeForSignatureMap = Deferrable.eager(
capturedScopeForSignatureMap,
),
_localClosureRepresentationMap = Deferrable.eager(
localClosureRepresentationMap,
);
ClosureDataImpl._deserialized(
this._elementMap,
this._scopeMap,
this._capturedScopesMap,
this._capturedScopeForSignatureMap,
this._localClosureRepresentationMap,
this._enclosingMembers,
);
static Map<ir.TreeNode, CapturedScope> _readCapturedScopesMap(
DataSourceReader source,
) {
return source.readTreeNodeMap(
() => CapturedScope.readFromDataSource(source),
);
}
static Map<MemberEntity, CapturedScope> _readCapturedScopeForSignatureMap(
DataSourceReader source,
) {
return source.readMemberMap(
(_) => CapturedScope.readFromDataSource(source),
);
}
static Map<ir.LocalFunction, ClosureRepresentationInfo>
_readLocalClosureRepresentationMap(DataSourceReader source) {
return source.readTreeNodeMap<ir.LocalFunction, ClosureRepresentationInfo>(
() => ClosureRepresentationInfo.readFromDataSource(source),
);
}
/// Deserializes a [ClosureData] object from [source].
factory ClosureDataImpl.readFromDataSource(
JsToElementMap elementMap,
DataSourceReader source,
) {
source.begin(tag);
// TODO(johnniwinther): Support shared [ScopeInfo].
final scopeMap = source.readMemberMap(
(MemberEntity member) =>
source.readDeferrable(ScopeInfo.readFromDataSource),
);
final capturedScopesMap = source.readDeferrable(_readCapturedScopesMap);
final capturedScopeForSignatureMap = source.readDeferrable(
_readCapturedScopeForSignatureMap,
);
final localClosureRepresentationMap = source.readDeferrable(
_readLocalClosureRepresentationMap,
);
Map<MemberEntity, MemberEntity> enclosingMembers = source.readMemberMap(
(member) => source.readMember(),
);
source.end(tag);
return ClosureDataImpl._deserialized(
elementMap,
DeferrableValueMap(scopeMap),
capturedScopesMap,
capturedScopeForSignatureMap,
localClosureRepresentationMap,
enclosingMembers,
);
}
/// Serializes this [ClosureData] to [sink].
@override
void writeToDataSink(DataSinkWriter sink) {
sink.begin(tag);
sink.writeMemberMap(
_scopeMap,
(_, ScopeInfo info) =>
sink.writeDeferrable(() => info.writeToDataSink(sink)),
);
sink.writeDeferrable(
() => sink.writeTreeNodeMap(_capturedScopesMap.loaded(), (
CapturedScope scope,
) {
scope.writeToDataSink(sink);
}),
);
sink.writeDeferrable(
() => sink.writeMemberMap(
_capturedScopeForSignatureMap.loaded(),
(MemberEntity member, CapturedScope scope) =>
scope.writeToDataSink(sink),
),
);
sink.writeDeferrable(
() => sink.writeTreeNodeMap(_localClosureRepresentationMap.loaded(), (
ClosureRepresentationInfo info,
) {
info.writeToDataSink(sink);
}),
);
sink.writeMemberMap(_enclosingMembers, (
MemberEntity member,
MemberEntity value,
) {
sink.writeMember(value);
});
sink.end(tag);
}
@override
ScopeInfo getScopeInfo(MemberEntity entity) {
// TODO(johnniwinther): Remove this check when constructor bodies a created
// eagerly with the J-model; a constructor body should have it's own
// [ClosureRepresentationInfo].
if (entity is ConstructorBodyEntity) {
entity = entity.constructor;
} else if (entity is JParameterStub) {
entity = entity.target;
}
return _scopeMap[entity]!;
}
// TODO(efortuna): Eventually capturedScopesMap[node] should always
// be non-null, and we should just test that with an assert.
@override
CapturedScope getCapturedScope(MemberEntity entity) {
MemberDefinition definition = _elementMap.getMemberDefinition(entity);
switch (definition.kind) {
case MemberKind.regular:
case MemberKind.constructor:
case MemberKind.constructorBody:
case MemberKind.closureCall:
return _capturedScopesMap.loaded()[definition.node] ??
const CapturedScope();
case MemberKind.signature:
return _capturedScopeForSignatureMap.loaded()[entity] ??
const CapturedScope();
case MemberKind.parameterStub:
return const CapturedScope();
case MemberKind.closureField:
case MemberKind.generatorBody:
case MemberKind.recordGetter:
throw failedAt(entity, "Unexpected member definition $definition");
}
}
@override
CapturedScope getCapturedBlockScope(ir.Block blockNode) =>
_capturedScopesMap.loaded()[blockNode] ?? const CapturedScope();
@override
// TODO(efortuna): Eventually capturedScopesMap[node] should always
// be non-null, and we should just test that with an assert.
CapturedLoopScope getCapturedLoopScope(ir.Node loopNode) =>
_capturedScopesMap.loaded()[loopNode] as CapturedLoopScope? ??
const CapturedLoopScope();
@override
ClosureRepresentationInfo getClosureInfo(ir.LocalFunction node) =>
_localClosureRepresentationMap.loaded()[node]!;
@override
MemberEntity getEnclosingMember(MemberEntity member) {
return _enclosingMembers[member] ?? member;
}
}
/// Closure conversion code using our new Entity model. Closure conversion is
/// necessary because the semantics of closures are slightly different in Dart
/// than JavaScript. Closure conversion is separated out into two phases:
/// generation of a new (temporary) representation to store where variables need
/// to be hoisted/captured up at another level to re-write the closure, and then
/// the code generation phase where we generate elements and/or instructions to
/// represent this new code path.
///
/// For a general explanation of how closure conversion works at a high level,
/// check out:
/// http://siek.blogspot.com/2012/07/essence-of-closure-conversion.html or
/// http://matt.might.net/articles/closure-conversion/.
class ClosureDataBuilder {
final DiagnosticReporter _reporter;
final JsToElementMap _elementMap;
final AnnotationsData _annotationsData;
/// Map of the scoping information that corresponds to a particular entity.
final Map<MemberEntity, ScopeInfo> _scopeMap = {};
final Map<ir.TreeNode, CapturedScope> _capturedScopesMap = {};
// Indicates the type variables (if any) that are captured in a given
// Signature function.
final Map<MemberEntity, CapturedScope> _capturedScopeForSignatureMap = {};
final Map<ir.LocalFunction, ClosureRepresentationInfo>
_localClosureRepresentationMap = {};
final Map<MemberEntity, MemberEntity> _enclosingMembers = {};
ClosureDataBuilder(this._reporter, this._elementMap, this._annotationsData);
void _updateScopeBasedOnRtiNeed(
KernelScopeInfo scope,
ClosureRtiNeed rtiNeed,
MemberEntity outermostEntity,
) {
bool includeForRti(Set<VariableUse> useSet) {
for (VariableUse usage in useSet) {
switch (usage) {
case SimpleVariableUse.explicit:
return true;
case SimpleVariableUse.implicitCast:
if (_annotationsData
.getImplicitDowncastCheckPolicy(outermostEntity)
.isEmitted) {
return true;
}
break;
case SimpleVariableUse.localType:
break;
case ConstructorTypeArgumentVariableUse(:final member):
ConstructorEntity constructor = _elementMap.getConstructor(member);
if (rtiNeed.classNeedsTypeArguments(constructor.enclosingClass)) {
return true;
}
break;
case StaticTypeArgumentVariableUse(:final procedure):
FunctionEntity method = _elementMap.getMethod(procedure);
if (rtiNeed.methodNeedsTypeArguments(method)) {
return true;
}
break;
case InstanceTypeArgumentVariableUse(:final invocation):
Selector selector = _elementMap.getSelector(invocation);
if (rtiNeed.selectorNeedsTypeArguments(selector)) {
return true;
}
break;
case LocalTypeArgumentVariableUse(
:final localFunction,
:final invocation,
):
// TODO(johnniwinther): We should be able to track direct local
// function invocations and not have to use the selector here.
Selector selector = _elementMap.getSelector(invocation);
if (rtiNeed.localFunctionNeedsTypeArguments(localFunction) ||
rtiNeed.selectorNeedsTypeArguments(selector)) {
return true;
}
break;
case MemberParameterVariableUse(:final member):
if (_annotationsData
.getParameterCheckPolicy(outermostEntity)
.isEmitted) {
return true;
} else {
FunctionEntity method = _elementMap.getMethod(
member as ir.Procedure,
);
if (rtiNeed.methodNeedsSignature(method)) {
return true;
}
if (rtiNeed.methodNeedsTypeArguments(method)) {
// Stubs generated for this method might make use of this type
// parameter for default type arguments.
return true;
}
}
break;
case LocalParameterVariableUse(:final localFunction):
if (_annotationsData
.getParameterCheckPolicy(outermostEntity)
.isEmitted) {
return true;
} else if (rtiNeed.localFunctionNeedsSignature(localFunction)) {
return true;
} else if (rtiNeed.localFunctionNeedsTypeArguments(localFunction)) {
// Stubs generated for this local function might make use of this
// type parameter for default type arguments.
return true;
}
break;
case MemberReturnTypeVariableUse(:final member):
FunctionEntity method = _elementMap.getMethod(
member as ir.Procedure,
);
if (rtiNeed.methodNeedsSignature(method)) {
return true;
}
break;
case LocalReturnTypeVariableUse(:final localFunction):
if (localFunction.function.asyncMarker != ir.AsyncMarker.Sync) {
// The Future/Iterator/Stream implementation requires the type.
return true;
}
if (rtiNeed.localFunctionNeedsSignature(localFunction)) {
return true;
}
break;
case SimpleVariableUse.fieldType:
if (_annotationsData
.getParameterCheckPolicy(outermostEntity)
.isEmitted) {
return true;
}
break;
case SimpleVariableUse.listLiteral:
if (rtiNeed.classNeedsTypeArguments(
_elementMap.commonElements.jsArrayClass,
)) {
return true;
}
break;
case SimpleVariableUse.setLiteral:
if (rtiNeed.classNeedsTypeArguments(
_elementMap.commonElements.setLiteralClass,
)) {
return true;
}
break;
case SimpleVariableUse.mapLiteral:
if (rtiNeed.classNeedsTypeArguments(
_elementMap.commonElements.mapLiteralClass,
)) {
return true;
}
break;
case InstantiationTypeArgumentVariableUse(:final instantiation):
// TODO(johnniwinther): Use the static type of the expression.
if (rtiNeed.instantiationNeedsTypeArguments(
null,
instantiation.typeArguments.length,
)) {
return true;
}
break;
}
}
return false;
}
if (includeForRti(scope.thisUsedAsFreeVariableIfNeedsRti)) {
scope.thisUsedAsFreeVariable = true;
}
scope.freeVariablesForRti.forEach((
TypeVariableTypeWithContext typeVariable,
Set<VariableUse> useSet,
) {
if (includeForRti(useSet)) {
scope.freeVariables.add(typeVariable);
}
});
}
ClosureData createClosureEntities(
JClosedWorldBuilder closedWorldBuilder,
Map<MemberEntity, ClosureScopeModel> closureModels,
ClosureRtiNeed rtiNeed,
List<FunctionEntity> callMethods,
) {
void processModel(MemberEntity member, ClosureScopeModel model) {
Map<ir.VariableDeclaration, JContextField> allBoxedVariables = _elementMap
.makeContextContainer(model.scopeInfo!, member);
_scopeMap[member] = JsScopeInfo.from(
allBoxedVariables,
model.scopeInfo!,
member.enclosingClass,
);
model.capturedScopesMap.forEach((
ir.Node node,
KernelCapturedScope scope,
) {
Map<ir.VariableDeclaration, JContextField> boxedVariables = _elementMap
.makeContextContainer(scope, member);
_updateScopeBasedOnRtiNeed(scope, rtiNeed, member);
if (scope is KernelCapturedLoopScope) {
_capturedScopesMap[node as ir.TreeNode] = JsCapturedLoopScope.from(
boxedVariables,
scope,
member.enclosingClass,
);
} else {
_capturedScopesMap[node as ir.TreeNode] = JsCapturedScope.from(
boxedVariables,
scope,
member.enclosingClass,
);
}
allBoxedVariables.addAll(boxedVariables);
});
Map<ir.LocalFunction, KernelScopeInfo> closuresToGenerate =
model.closuresToGenerate;
for (ir.LocalFunction node in closuresToGenerate.keys) {
ir.FunctionNode functionNode = node.function;
JsClosureClassInfo closureClassInfo = _produceSyntheticElements(
closedWorldBuilder,
member,
functionNode,
closuresToGenerate[node]!,
allBoxedVariables,
rtiNeed,
createSignatureMethod: rtiNeed.localFunctionNeedsSignature(
functionNode.parent as ir.LocalFunction,
),
);
// Add also for the call method.
_scopeMap[closureClassInfo.callMethod!] = closureClassInfo;
if (closureClassInfo.signatureMethod != null) {
_scopeMap[closureClassInfo.signatureMethod!] = closureClassInfo;
// Set up capturedScope for signature method. This is distinct from
// _capturedScopesMap because there is no corresponding ir.Node for
// the signature.
if (rtiNeed.localFunctionNeedsSignature(
functionNode.parent as ir.LocalFunction,
) &&
model.capturedScopesMap[functionNode] != null) {
KernelCapturedScope capturedScope =
model.capturedScopesMap[functionNode]!;
assert(capturedScope is! KernelCapturedLoopScope);
KernelCapturedScope signatureCapturedScope =
KernelCapturedScope.forSignature(capturedScope);
_updateScopeBasedOnRtiNeed(signatureCapturedScope, rtiNeed, member);
_capturedScopeForSignatureMap[closureClassInfo.signatureMethod!] =
JsCapturedScope.from(
{},
signatureCapturedScope,
member.enclosingClass,
);
}
}
callMethods.add(closureClassInfo.callMethod!);
}
}
closureModels.forEach((MemberEntity member, ClosureScopeModel model) {
_reporter.withCurrentElement(member, () {
processModel(member, model);
});
});
return ClosureDataImpl(
_elementMap,
_scopeMap,
_capturedScopesMap,
_capturedScopeForSignatureMap,
_localClosureRepresentationMap,
_enclosingMembers,
);
}
/// Given what variables are captured at each point, construct closure classes
/// with fields containing the captured variables to replicate the Dart
/// closure semantics in JS. If this closure captures any variables (meaning
/// the closure accesses a variable that gets accessed at some point), then
/// boxForCapturedVariables stores the local context for those variables.
/// If no variables are captured, this parameter is null.
JsClosureClassInfo _produceSyntheticElements(
JClosedWorldBuilder closedWorldBuilder,
MemberEntity member,
ir.FunctionNode node,
KernelScopeInfo info,
Map<ir.VariableDeclaration, JContextField> boxedVariables,
ClosureRtiNeed rtiNeed, {
required bool createSignatureMethod,
}) {
_updateScopeBasedOnRtiNeed(info, rtiNeed, member);
JsClosureClassInfo closureClassInfo = closedWorldBuilder.buildClosureClass(
member,
node,
member.library as JLibrary,
boxedVariables,
info,
createSignatureMethod: createSignatureMethod,
);
// We want the original declaration where that function is used to point
// to the correct closure class.
_enclosingMembers[closureClassInfo.callMethod!] = member;
if (closureClassInfo.signatureMethod != null) {
_enclosingMembers[closureClassInfo.signatureMethod!] = member;
}
if (node.parent is ir.Member) {
assert(_elementMap.getMember(node.parent as ir.Member) == member);
} else {
assert(node.parent is ir.LocalFunction);
_localClosureRepresentationMap[node.parent as ir.LocalFunction] =
closureClassInfo;
}
return closureClassInfo;
}
}
class JsScopeInfo extends ScopeInfo {
/// Tag used for identifying serialized [JsScopeInfo] objects in a
/// debugging data stream.
static const String tag = 'scope-info';
final Iterable<ir.VariableDeclaration> _localsUsedInTryOrSync;
Set<Local>? _localsUsedInTryOrSyncCache;
@override
final Local? thisLocal;
final Map<ir.VariableDeclaration, JContextField> _boxedVariables;
Map<Local, JContextField>? _boxedVariablesCache;
JsScopeInfo.internal(
this._localsUsedInTryOrSync,
this.thisLocal,
this._boxedVariables,
);
JsScopeInfo.from(
this._boxedVariables,
KernelScopeInfo info,
ClassEntity? enclosingClass,
) : thisLocal = info.hasThisLocal ? ThisLocal(enclosingClass!) : null,
_localsUsedInTryOrSync = info.localsUsedInTryOrSync;
void _ensureBoxedVariableCache(KernelToLocalsMap localsMap) {
if (_boxedVariablesCache == null) {
if (_boxedVariables.isEmpty) {
_boxedVariablesCache = const {};
} else {
final cache = <Local, JContextField>{};
_boxedVariables.forEach((
ir.VariableDeclaration node,
JContextField field,
) {
cache[localsMap.getLocalVariable(node)] = field;
});
_boxedVariablesCache = cache;
}
}
}
@override
void forEachBoxedVariable(
KernelToLocalsMap localsMap,
void Function(Local local, FieldEntity field) f,
) {
_ensureBoxedVariableCache(localsMap);
_boxedVariablesCache!.forEach(f);
}
@override
bool localIsUsedInTryOrSync(KernelToLocalsMap localsMap, Local variable) {
if (_localsUsedInTryOrSyncCache == null) {
if (_localsUsedInTryOrSync.isEmpty) {
_localsUsedInTryOrSyncCache = const {};
} else {
_localsUsedInTryOrSyncCache = {};
for (ir.VariableDeclaration node in _localsUsedInTryOrSync) {
_localsUsedInTryOrSyncCache!.add(localsMap.getLocalVariable(node));
}
}
}
return _localsUsedInTryOrSyncCache!.contains(variable);
}
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('this=$thisLocal,');
sb.write('localsUsedInTryOrSync={${_localsUsedInTryOrSync.join(', ')}}');
return sb.toString();
}
@override
bool isBoxedVariable(KernelToLocalsMap localsMap, Local variable) {
_ensureBoxedVariableCache(localsMap);
return _boxedVariablesCache!.containsKey(variable);
}
factory JsScopeInfo.readFromDataSource(DataSourceReader source) {
source.begin(tag);
Iterable<ir.VariableDeclaration> localsUsedInTryOrSync = source
.readTreeNodes<ir.VariableDeclaration>();
Local? thisLocal = source.readLocalOrNull();
Map<ir.VariableDeclaration, JContextField> boxedVariables = source
.readTreeNodeMap<ir.VariableDeclaration, JContextField>(
() => source.readMember() as JContextField,
);
source.end(tag);
if (boxedVariables.isEmpty) boxedVariables = const {};
return JsScopeInfo.internal(
localsUsedInTryOrSync,
thisLocal,
boxedVariables,
);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(ScopeInfoKind.scopeInfo);
sink.begin(tag);
sink.writeTreeNodes(_localsUsedInTryOrSync);
sink.writeLocalOrNull(thisLocal);
sink.writeTreeNodeMap(_boxedVariables, sink.writeMember);
sink.end(tag);
}
}
class JsCapturedScope extends JsScopeInfo implements CapturedScope {
/// Tag used for identifying serialized [JsCapturedScope] objects in a
/// debugging data stream.
static const String tag = 'captured-scope';
@override
final Local? contextBox;
JsCapturedScope.internal(
super.localsUsedInTryOrSync,
super.thisLocal,
super.boxedVariables,
this.contextBox,
) : super.internal();
JsCapturedScope.from(
super.boxedVariables,
super.capturedScope,
super.enclosingClass,
) : contextBox = boxedVariables.isNotEmpty
? boxedVariables.values.first.box
: null,
super.from();
@override
bool get requiresContextBox => _boxedVariables.isNotEmpty;
factory JsCapturedScope.readFromDataSource(DataSourceReader source) {
source.begin(tag);
Iterable<ir.VariableDeclaration> localsUsedInTryOrSync = source
.readTreeNodes<ir.VariableDeclaration>();
Local? thisLocal = source.readLocalOrNull();
Map<ir.VariableDeclaration, JContextField> boxedVariables = source
.readTreeNodeMap<ir.VariableDeclaration, JContextField>(
() => source.readMember() as JContextField,
);
Local? context = source.readLocalOrNull();
source.end(tag);
return JsCapturedScope.internal(
localsUsedInTryOrSync,
thisLocal,
boxedVariables,
context,
);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(ScopeInfoKind.capturedScope);
sink.begin(tag);
sink.writeTreeNodes(_localsUsedInTryOrSync);
sink.writeLocalOrNull(thisLocal);
sink.writeTreeNodeMap(_boxedVariables, sink.writeMember);
sink.writeLocalOrNull(contextBox);
sink.end(tag);
}
}
class JsCapturedLoopScope extends JsCapturedScope implements CapturedLoopScope {
/// Tag used for identifying serialized [JsCapturedLoopScope] objects in a
/// debugging data stream.
static const String tag = 'captured-loop-scope';
final List<ir.VariableDeclaration> _boxedLoopVariables;
JsCapturedLoopScope.internal(
super.localsUsedInTryOrSync,
super.thisLocal,
super.boxedVariables,
super.context,
this._boxedLoopVariables,
) : super.internal();
JsCapturedLoopScope.from(
super.boxedVariables,
KernelCapturedLoopScope super.capturedScope,
super.enclosingClass,
) : _boxedLoopVariables = capturedScope.boxedLoopVariables,
super.from();
@override
bool get hasBoxedLoopVariables => _boxedLoopVariables.isNotEmpty;
factory JsCapturedLoopScope.readFromDataSource(DataSourceReader source) {
source.begin(tag);
Iterable<ir.VariableDeclaration> localsUsedInTryOrSync = source
.readTreeNodes<ir.VariableDeclaration>();
Local? thisLocal = source.readLocalOrNull();
Map<ir.VariableDeclaration, JContextField> boxedVariables = source
.readTreeNodeMap<ir.VariableDeclaration, JContextField>(
() => source.readMember() as JContextField,
);
Local? context = source.readLocalOrNull();
List<ir.VariableDeclaration> boxedLoopVariables = source
.readTreeNodes<ir.VariableDeclaration>();
source.end(tag);
return JsCapturedLoopScope.internal(
localsUsedInTryOrSync,
thisLocal,
boxedVariables,
context,
boxedLoopVariables,
);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(ScopeInfoKind.capturedLoopScope);
sink.begin(tag);
sink.writeTreeNodes(_localsUsedInTryOrSync);
sink.writeLocalOrNull(thisLocal);
sink.writeTreeNodeMap(_boxedVariables, sink.writeMember);
sink.writeLocalOrNull(contextBox);
sink.writeTreeNodes(_boxedLoopVariables);
sink.end(tag);
}
@override
List<Local> getBoxedLoopVariables(KernelToLocalsMap localsMap) {
List<Local> locals = [];
for (ir.VariableDeclaration boxedLoopVariable in _boxedLoopVariables) {
locals.add(localsMap.getLocalVariable(boxedLoopVariable));
}
return locals;
}
}
// TODO(johnniwinther): Add unittest for the computed [ClosureClass].
class JsClosureClassInfo extends JsScopeInfo
implements ClosureRepresentationInfo {
/// Tag used for identifying serialized [JsClosureClassInfo] objects in a
/// debugging data stream.
static const String tag = 'closure-representation-info';
@override
JFunction? callMethod;
@override
JSignatureMethod? signatureMethod;
/// The local used for this closure, if it is an anonymous closure, i.e.
/// an `ir.FunctionExpression`.
final Local? _closureEntity;
/// The local variable that defines this closure, if it is a local function
/// declaration.
final ir.VariableDeclaration? _closureEntityVariable;
@override
final JClass closureClassEntity;
final Map<ir.VariableDeclaration, JField> _variableToFieldMap;
final Map<JTypeVariable, JField> _typeVariableToFieldMap;
final Map<Local, JField> _localToFieldMap;
Map<JField, Local>? _fieldToLocalsMap;
JsClosureClassInfo.internal(
super.localsUsedInTryOrSync,
super.thisLocal,
super.boxedVariables,
this.callMethod,
this.signatureMethod,
this._closureEntity,
this._closureEntityVariable,
this.closureClassEntity,
this._variableToFieldMap,
this._typeVariableToFieldMap,
this._localToFieldMap,
) : super.internal();
JsClosureClassInfo.fromScopeInfo(
this.closureClassEntity,
ir.FunctionNode closureSourceNode,
Map<ir.VariableDeclaration, JContextField> boxedVariables,
KernelScopeInfo info,
ClassEntity? enclosingClass,
this._closureEntity,
this._closureEntityVariable,
) : _variableToFieldMap = {},
_typeVariableToFieldMap = {},
_localToFieldMap = {},
super.from(boxedVariables, info, enclosingClass);
factory JsClosureClassInfo.readFromDataSource(DataSourceReader source) {
source.begin(tag);
Iterable<ir.VariableDeclaration> localsUsedInTryOrSync = source
.readTreeNodes<ir.VariableDeclaration>();
Local? thisLocal = source.readLocalOrNull();
Map<ir.VariableDeclaration, JContextField> boxedVariables = source
.readTreeNodeMap<ir.VariableDeclaration, JContextField>(
() => source.readMember() as JContextField,
);
JFunction callMethod = source.readMember() as JFunction;
JSignatureMethod? signatureMethod =
source.readMemberOrNull() as JSignatureMethod?;
Local? closureEntity = source.readLocalOrNull();
ir.VariableDeclaration? closureEntityVariable =
source.readTreeNodeOrNull() as ir.VariableDeclaration?;
JClass closureClassEntity = source.readClass() as JClass;
Map<ir.VariableDeclaration, JField> localToFieldMap = source
.readTreeNodeMap<ir.VariableDeclaration, JField>(
() => source.readMember() as JField,
);
Map<JTypeVariable, JField> typeVariableToFieldMap = source
.readTypeVariableMap<JTypeVariable, JField>(
() => source.readMember() as JField,
);
Map<Local, JField> thisLocalToFieldMap = source.readLocalMap(
() => source.readMember() as JField,
);
source.end(tag);
if (boxedVariables.isEmpty) boxedVariables = const {};
if (localToFieldMap.isEmpty) localToFieldMap = const {};
return JsClosureClassInfo.internal(
localsUsedInTryOrSync,
thisLocal,
boxedVariables,
callMethod,
signatureMethod,
closureEntity,
closureEntityVariable,
closureClassEntity,
localToFieldMap,
typeVariableToFieldMap,
thisLocalToFieldMap,
);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(ScopeInfoKind.closureRepresentationInfo);
sink.begin(tag);
sink.writeTreeNodes(_localsUsedInTryOrSync);
sink.writeLocalOrNull(thisLocal);
sink.writeTreeNodeMap(_boxedVariables, sink.writeMember);
sink.writeMember(callMethod!);
sink.writeMemberOrNull(signatureMethod);
sink.writeLocalOrNull(_closureEntity);
sink.writeTreeNodeOrNull(_closureEntityVariable);
sink.writeClass(closureClassEntity);
sink.writeTreeNodeMap(_variableToFieldMap, sink.writeMember);
sink.writeTypeVariableMap(_typeVariableToFieldMap, sink.writeMember);
sink.writeLocalMap(_localToFieldMap, sink.writeMember);
sink.end(tag);
}
bool hasFieldForLocal(Local local) => _localToFieldMap.containsKey(local);
void registerFieldForLocal(Local local, JField field) {
assert(_fieldToLocalsMap == null);
_localToFieldMap[local] = field;
}
void registerFieldForVariable(ir.VariableDeclaration node, JField field) {
assert(_fieldToLocalsMap == null);
_variableToFieldMap[node] = field;
}
bool hasFieldForTypeVariable(JTypeVariable typeVariable) =>
_typeVariableToFieldMap.containsKey(typeVariable);
void registerFieldForTypeVariable(JTypeVariable typeVariable, JField field) {
assert(_fieldToLocalsMap == null);
_typeVariableToFieldMap[typeVariable] = field;
}
void registerFieldForBoxedVariable(
ir.VariableDeclaration node,
JField field,
) {
assert(_boxedVariablesCache == null);
_boxedVariables[node] = field as JContextField;
}
void _ensureFieldToLocalsMap(KernelToLocalsMap localsMap) {
if (_fieldToLocalsMap == null) {
_fieldToLocalsMap = {};
_variableToFieldMap.forEach((ir.VariableDeclaration node, JField field) {
_fieldToLocalsMap![field] = localsMap.getLocalVariable(node);
});
_typeVariableToFieldMap.forEach((
TypeVariableEntity typeVariable,
JField field,
) {
_fieldToLocalsMap![field] = localsMap.getLocalTypeVariableEntity(
typeVariable,
);
});
_localToFieldMap.forEach((Local local, JField field) {
_fieldToLocalsMap![field] = local;
});
if (_fieldToLocalsMap!.isEmpty) {
_fieldToLocalsMap = const {};
}
}
}
@override
Local getLocalForField(KernelToLocalsMap localsMap, FieldEntity field) {
_ensureFieldToLocalsMap(localsMap);
return _fieldToLocalsMap![field]!;
}
@override
FieldEntity? get thisFieldEntity => _localToFieldMap[thisLocal];
@override
void forEachFreeVariable(
KernelToLocalsMap localsMap,
void Function(Local variable, JField field) f,
) {
_ensureFieldToLocalsMap(localsMap);
_ensureBoxedVariableCache(localsMap);
_fieldToLocalsMap!.forEach((JField field, Local local) {
f(local, field);
});
_boxedVariablesCache!.forEach(f);
}
@override
bool get isClosure => true;
@override
Local? getClosureEntity(KernelToLocalsMap localsMap) {
return _closureEntityVariable != null
? localsMap.getLocalVariable(_closureEntityVariable)
: _closureEntity;
}
}
abstract class ClosureRtiNeed {
bool classNeedsTypeArguments(ClassEntity cls);
bool methodNeedsTypeArguments(FunctionEntity method);
bool methodNeedsSignature(MemberEntity method);
bool localFunctionNeedsTypeArguments(ir.LocalFunction node);
bool localFunctionNeedsSignature(ir.LocalFunction node);
bool selectorNeedsTypeArguments(Selector selector);
bool instantiationNeedsTypeArguments(
FunctionType? functionType,
int typeArgumentCount,
);
}
/// A container for variables declared in a particular scope that are accessed
/// elsewhere.
// TODO(johnniwinther): Don't implement JClass. This isn't actually a class.
class JContext extends JClass {
/// Tag used for identifying serialized [JContext] objects in a debugging data
/// stream.
static const String tag = 'context';
JContext(LibraryEntity library, String name)
: super(library as JLibrary, name, isAbstract: false);
factory JContext.readFromDataSource(DataSourceReader source) {
source.begin(tag);
JLibrary library = source.readLibrary() as JLibrary;
String name = source.readString();
source.end(tag);
return JContext(library, name);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JClassKind.context);
sink.begin(tag);
sink.writeLibrary(library);
sink.writeString(name);
sink.end(tag);
}
@override
bool get isClosure => false;
@override
String toString() => '${jsElementPrefix}context($name)';
}
/// A variable that has been "boxed" to prevent name shadowing with the original
/// variable and ensure that this variable is updated/read with the most recent
/// value.
class JContextField extends JField {
/// Tag used for identifying serialized [JContextField] objects in a debugging
/// data stream.
static const String tag = 'context-field';
final BoxLocal box;
JContextField(String name, this.box, {required bool isConst})
: super(
box.container.library as JLibrary,
box.container as JClass,
Name(name, box.container.library.canonicalUri),
isStatic: false,
isAssignable: true,
isConst: isConst,
);
factory JContextField.readFromDataSource(DataSourceReader source) {
source.begin(tag);
String name = source.readString();
final enclosingClass = source.readClass() as JClass;
bool isConst = source.readBool();
source.end(tag);
return JContextField(name, BoxLocal(enclosingClass), isConst: isConst);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JMemberKind.contextField);
sink.begin(tag);
sink.writeString(name);
sink.writeClass(enclosingClass!);
sink.writeBool(isConst);
sink.end(tag);
}
// TODO(johnniwinther): Remove these anomalies. Maybe by separating the
// J-entities from the K-entities.
@override
bool get isInstanceMember => false;
@override
bool get isTopLevel => false;
@override
bool get isStatic => false;
}
class JClosureClass extends JClass {
/// Tag used for identifying serialized [JClosureClass] objects in a
/// debugging data stream.
static const String tag = 'closure-class';
JClosureClass(super.library, super.name) : super(isAbstract: false);
factory JClosureClass.readFromDataSource(DataSourceReader source) {
source.begin(tag);
JLibrary library = source.readLibrary() as JLibrary;
String name = source.readString();
source.end(tag);
return JClosureClass(library, name);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JClassKind.closure);
sink.begin(tag);
sink.writeLibrary(library);
sink.writeString(name);
sink.end(tag);
}
@override
bool get isClosure => true;
@override
String toString() => '${jsElementPrefix}closure_class($name)';
}
class AnonymousClosureLocal implements Local {
final JClosureClass closureClass;
AnonymousClosureLocal(this.closureClass);
@override
String get name => '';
@override
int get hashCode => closureClass.hashCode * 13;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! AnonymousClosureLocal) return false;
return closureClass == other.closureClass;
}
@override
String toString() =>
'${jsElementPrefix}anonymous_closure_local(${closureClass.name})';
}
class JClosureField extends JField implements PrivatelyNamedJSEntity {
/// Tag used for identifying serialized [JClosureClass] objects in a
/// debugging data stream.
static const String tag = 'closure-field';
@override
final String declaredName;
JClosureField(
String name,
JsClosureClassInfo containingClass,
String declaredName, {
required bool isConst,
required bool isAssignable,
}) : this.internal(
containingClass.closureClassEntity.library,
containingClass.closureClassEntity as JClosureClass,
Name(name, containingClass.closureClassEntity.library.canonicalUri),
declaredName,
isAssignable: isAssignable,
isConst: isConst,
);
JClosureField.internal(
super.library,
JClosureClass super.enclosingClass,
super.memberName,
this.declaredName, {
required super.isConst,
required super.isAssignable,
}) : super(isStatic: false);
factory JClosureField.readFromDataSource(DataSourceReader source) {
source.begin(tag);
final cls = source.readClass() as JClosureClass;
String name = source.readString();
String declaredName = source.readString();
bool isConst = source.readBool();
bool isAssignable = source.readBool();
source.end(tag);
return JClosureField.internal(
cls.library,
cls,
Name(name, cls.library.canonicalUri),
declaredName,
isAssignable: isAssignable,
isConst: isConst,
);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JMemberKind.closureField);
sink.begin(tag);
sink.writeClass(enclosingClass!);
sink.writeString(name);
sink.writeString(declaredName);
sink.writeBool(isConst);
sink.writeBool(isAssignable);
sink.end(tag);
}
@override
Entity get rootOfScope => enclosingClass!;
}
class ContextClassData implements JClassData {
/// Tag used for identifying serialized [ContextClassData] objects in a
/// debugging data stream.
static const String tag = 'context-class-data';
@override
final ClassDefinition definition;
@override
final InterfaceType? thisType;
@override
final OrderedTypeSet orderedTypeSet;
@override
final InterfaceType? supertype;
ContextClassData(
this.definition,
this.thisType,
this.supertype,
this.orderedTypeSet,
);
factory ContextClassData.readFromDataSource(DataSourceReader source) {
source.begin(tag);
ClassDefinition definition = ClassDefinition.readFromDataSource(source);
InterfaceType thisType = source.readDartType() as InterfaceType;
InterfaceType supertype = source.readDartType() as InterfaceType;
OrderedTypeSet orderedTypeSet = OrderedTypeSet.readFromDataSource(source);
source.end(tag);
return ContextClassData(definition, thisType, supertype, orderedTypeSet);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JClassDataKind.context);
sink.begin(tag);
definition.writeToDataSink(sink);
sink.writeDartType(thisType!);
sink.writeDartType(supertype!);
orderedTypeSet.writeToDataSink(sink);
sink.end(tag);
}
@override
bool get isMixinApplication => false;
@override
bool get isEnumClass => false;
@override
FunctionType? get callType => null;
@override
List<InterfaceType> get interfaces => const <InterfaceType>[];
@override
InterfaceType? get mixedInType => null;
@override
InterfaceType? get jsInteropType => thisType;
@override
InterfaceType? get rawType => thisType;
@override
InterfaceType? get instantiationToBounds => thisType;
@override
List<Variance> getVariances() => [];
}
class ClosureClassData extends ContextClassData {
/// Tag used for identifying serialized [ClosureClassData] objects in a
/// debugging data stream.
static const String tag = 'closure-class-data';
@override
FunctionType? callType;
ClosureClassData(
super.definition,
super.thisType,
super.supertype,
super.orderedTypeSet,
);
factory ClosureClassData.readFromDataSource(DataSourceReader source) {
source.begin(tag);
ClassDefinition definition = ClassDefinition.readFromDataSource(source);
InterfaceType thisType = source.readDartType() as InterfaceType;
InterfaceType supertype = source.readDartType() as InterfaceType;
OrderedTypeSet orderedTypeSet = OrderedTypeSet.readFromDataSource(source);
FunctionType callType = source.readDartType() as FunctionType;
source.end(tag);
return ClosureClassData(definition, thisType, supertype, orderedTypeSet)
..callType = callType;
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JClassDataKind.closure);
sink.begin(tag);
definition.writeToDataSink(sink);
sink.writeDartType(thisType!);
sink.writeDartType(supertype!);
orderedTypeSet.writeToDataSink(sink);
sink.writeDartType(callType!);
sink.end(tag);
}
}
abstract class ClosureMemberData implements JMemberData {
@override
final MemberDefinition definition;
final InterfaceType? memberThisType;
ClosureMemberData(this.definition, this.memberThisType);
@override
InterfaceType? getMemberThisType(covariant JsToElementMap elementMap) {
return memberThisType;
}
}
class ClosureFunctionData extends ClosureMemberData
with FunctionDataTypeVariablesMixin, FunctionDataForEachParameterMixin
implements FunctionData {
/// Tag used for identifying serialized [ClosureFunctionData] objects in a
/// debugging data stream.
static const String tag = 'closure-function-data';
final FunctionType functionType;
@override
ir.FunctionNode get functionNode => _functionNode.loaded();
final Deferrable<ir.FunctionNode> _functionNode;
@override
final ClassTypeVariableAccess classTypeVariableAccess;
ClosureFunctionData(
super.definition,
super.memberThisType,
this.functionType,
ir.FunctionNode functionNode,
this.classTypeVariableAccess,
) : _functionNode = Deferrable.eager(functionNode);
ClosureFunctionData._deserialized(
super.definition,
super.memberThisType,
this.functionType,
this._functionNode,
this.classTypeVariableAccess,
);
static ir.FunctionNode _readFunctionNode(DataSourceReader source) {
return source.readTreeNode() as ir.FunctionNode;
}
factory ClosureFunctionData.readFromDataSource(DataSourceReader source) {
source.begin(tag);
ClosureMemberDefinition definition =
MemberDefinition.readFromDataSource(source) as ClosureMemberDefinition;
InterfaceType? memberThisType =
source.readDartTypeOrNull() as InterfaceType?;
FunctionType functionType = source.readDartType() as FunctionType;
Deferrable<ir.FunctionNode> functionNode = source.readDeferrable(
_readFunctionNode,
);
ClassTypeVariableAccess classTypeVariableAccess = source.readEnum(
ClassTypeVariableAccess.values,
);
source.end(tag);
return ClosureFunctionData._deserialized(
definition,
memberThisType,
functionType,
functionNode,
classTypeVariableAccess,
);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JMemberDataKind.closureFunction);
sink.begin(tag);
definition.writeToDataSink(sink);
sink.writeDartTypeOrNull(memberThisType);
sink.writeDartType(functionType);
sink.writeDeferrable(() => sink.writeTreeNode(functionNode));
sink.writeEnum(classTypeVariableAccess);
sink.end(tag);
}
@override
FunctionType getFunctionType(IrToElementMap elementMap) {
return functionType;
}
}
class ClosureFieldData extends ClosureMemberData implements JFieldData {
/// Tag used for identifying serialized [ClosureFieldData] objects in a
/// debugging data stream.
static const String tag = 'closure-field-data';
DartType? _type;
ClosureFieldData(super.definition, super.memberThisType);
factory ClosureFieldData.readFromDataSource(DataSourceReader source) {
source.begin(tag);
MemberDefinition definition = MemberDefinition.readFromDataSource(source);
InterfaceType? memberThisType =
source.readDartTypeOrNull() as InterfaceType?;
source.end(tag);
return ClosureFieldData(definition, memberThisType);
}
@override
void writeToDataSink(DataSinkWriter sink) {
sink.writeEnum(JMemberDataKind.closureField);
sink.begin(tag);
definition.writeToDataSink(sink);
sink.writeDartTypeOrNull(memberThisType);
sink.end(tag);
}
@override
DartType getFieldType(IrToElementMap elementMap) {
if (_type != null) return _type!;
ir.Node sourceNode = definition.node;
ir.DartType type;
if (sourceNode is ir.Class) {
type = sourceNode.getThisType(
elementMap.coreTypes,
sourceNode.enclosingLibrary.nonNullable,
);
} else if (sourceNode is ir.VariableDeclaration) {
type = sourceNode.type;
} else if (sourceNode is ir.Field) {
type = sourceNode.type;
} else if (sourceNode is ir.TypeLiteral) {
type = sourceNode.type;
} else if (sourceNode is ir.Typedef) {
type = sourceNode.type!;
} else if (sourceNode is ir.TypeParameter) {
type = sourceNode.bound;
} else {
failedAt(
definition.location,
'Unexpected node type $sourceNode in '
'ClosureFieldData.getFieldType',
);
}
return _type = elementMap.getDartType(type);
}
@override
bool get isCovariantByDeclaration => false;
@override
ClassTypeVariableAccess get classTypeVariableAccess =>
ClassTypeVariableAccess.none;
}