| // 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 '../ordered_typeset.dart'; | |
| import '../serialization/deferrable.dart'; | |
| import '../serialization/serialization.dart'; | |
| import '../universe/selector.dart'; | |
| import 'class_type_variable_access.dart'; | |
| import 'element_map.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.Variable, 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.Variable, 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.Variable, 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.Variable> _localsUsedInTryOrSync; | |
| Set<Local>? _localsUsedInTryOrSyncCache; | |
| @override | |
| final Local? thisLocal; | |
| final Map<ir.Variable, 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.Variable 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.Variable 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.Variable> localsUsedInTryOrSync = source | |
| .readTreeNodes<ir.Variable>(); | |
| Local? thisLocal = source.readLocalOrNull(); | |
| Map<ir.Variable, JContextField> boxedVariables = source | |
| .readTreeNodeMap<ir.Variable, 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.Variable> localsUsedInTryOrSync = source | |
| .readTreeNodes<ir.Variable>(); | |
| Local? thisLocal = source.readLocalOrNull(); | |
| Map<ir.Variable, JContextField> boxedVariables = source | |
| .readTreeNodeMap<ir.Variable, 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.Variable> _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.Variable> localsUsedInTryOrSync = source | |
| .readTreeNodes<ir.Variable>(); | |
| Local? thisLocal = source.readLocalOrNull(); | |
| Map<ir.Variable, JContextField> boxedVariables = source | |
| .readTreeNodeMap<ir.Variable, JContextField>( | |
| () => source.readMember() as JContextField, | |
| ); | |
| Local? context = source.readLocalOrNull(); | |
| List<ir.Variable> boxedLoopVariables = source.readTreeNodes<ir.Variable>(); | |
| 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.Variable 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.Variable? _closureEntityVariable; | |
| @override | |
| final JClass closureClassEntity; | |
| final Map<ir.Variable, 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.Variable, 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.Variable> localsUsedInTryOrSync = source | |
| .readTreeNodes<ir.Variable>(); | |
| Local? thisLocal = source.readLocalOrNull(); | |
| Map<ir.Variable, JContextField> boxedVariables = source | |
| .readTreeNodeMap<ir.Variable, JContextField>( | |
| () => source.readMember() as JContextField, | |
| ); | |
| JFunction callMethod = source.readMember() as JFunction; | |
| JSignatureMethod? signatureMethod = | |
| source.readMemberOrNull() as JSignatureMethod?; | |
| Local? closureEntity = source.readLocalOrNull(); | |
| ir.Variable? closureEntityVariable = | |
| source.readTreeNodeOrNull() as ir.Variable?; | |
| JClass closureClassEntity = source.readClass() as JClass; | |
| Map<ir.Variable, JField> localToFieldMap = source | |
| .readTreeNodeMap<ir.Variable, 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.Variable 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.Variable node, JField field) { | |
| assert(_boxedVariablesCache == null); | |
| _boxedVariables[node] = field as JContextField; | |
| } | |
| void _ensureFieldToLocalsMap(KernelToLocalsMap localsMap) { | |
| if (_fieldToLocalsMap == null) { | |
| _fieldToLocalsMap = {}; | |
| _variableToFieldMap.forEach((ir.Variable 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 super.isConst}) | |
| : super( | |
| box.container.library as JLibrary, | |
| box.container as JClass, | |
| Name(name, box.container.library.canonicalUri), | |
| isStatic: false, | |
| isAssignable: true, | |
| ); | |
| 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.Variable) { | |
| 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; | |
| } |