| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// This library defines individual world impacts. |
| /// |
| /// We call these building blocks `uses`. Each `use` is a single impact of the |
| /// world. Some example uses are: |
| /// |
| /// * an invocation of a top level function |
| /// * a call to the `foo()` method on an unknown class. |
| /// * an instantiation of class T |
| /// |
| /// The different compiler stages combine these uses into `WorldImpact` objects, |
| /// which are later used to construct a closed-world understanding of the |
| /// program. |
| library; |
| |
| import 'package:kernel/ast.dart' as ir; |
| |
| import '../common.dart'; |
| import '../constants/values.dart'; |
| import '../elements/types.dart'; |
| import '../elements/entities.dart'; |
| import '../inferrer/abstract_value_domain.dart'; |
| import '../serialization/serialization.dart'; |
| import '../js_model/closure.dart' show JContextField; |
| import '../util/enumset.dart'; |
| import '../util/util.dart' show equalElements, Hashing; |
| import 'call_structure.dart' show CallStructure; |
| import 'selector.dart' show Selector; |
| import 'world_impact.dart'; |
| |
| enum DynamicUseKind { invoke, get, set } |
| |
| /// The use of a dynamic property. [selector] defined the name and kind of the |
| /// property and [receiverConstraint] defines the known constraint for the |
| /// object on which the property is accessed. |
| class DynamicUse { |
| static const String tag = 'dynamic-use'; |
| |
| final Selector selector; |
| final Object? receiverConstraint; |
| final List<DartType>? _typeArguments; |
| |
| DynamicUse(this.selector, this.receiverConstraint, this._typeArguments) |
| : assert( |
| selector.callStructure.typeArgumentCount == |
| (_typeArguments?.length ?? 0), |
| "Type argument count mismatch. Selector has " |
| "${selector.callStructure.typeArgumentCount} but " |
| "${_typeArguments?.length ?? 0} were passed.", |
| ); |
| |
| DynamicUse withReceiverConstraint(Object? otherReceiverConstraint) { |
| if (otherReceiverConstraint == receiverConstraint) { |
| return this; |
| } |
| return DynamicUse(selector, otherReceiverConstraint, _typeArguments); |
| } |
| |
| factory DynamicUse.readFromDataSource(DataSourceReader source) { |
| source.begin(tag); |
| Selector selector = Selector.readFromDataSource(source); |
| bool hasConstraint = source.readBool(); |
| Object? receiverConstraint; |
| if (hasConstraint) { |
| receiverConstraint = source.readAbstractValue(); |
| } |
| List<DartType>? typeArguments = source.readDartTypesOrNull(); |
| source.end(tag); |
| return DynamicUse(selector, receiverConstraint, typeArguments); |
| } |
| |
| void writeToDataSink(DataSinkWriter sink) { |
| sink.begin(tag); |
| selector.writeToDataSink(sink); |
| var constraint = receiverConstraint; |
| sink.writeBool(constraint != null); |
| if (constraint != null) { |
| if (constraint is AbstractValue) { |
| sink.writeAbstractValue(constraint); |
| } else { |
| throw UnsupportedError("Unsupported receiver constraint: $constraint"); |
| } |
| } |
| sink.writeDartTypesOrNull(_typeArguments); |
| sink.end(tag); |
| } |
| |
| /// Short textual representation use for testing. |
| String get shortText { |
| StringBuffer sb = StringBuffer(); |
| if (receiverConstraint != null) { |
| final constraint = receiverConstraint; |
| if (constraint is ClassEntity) { |
| sb.write(constraint.name); |
| } else { |
| sb.write(constraint); |
| } |
| sb.write('.'); |
| } |
| sb.write(selector.name); |
| if (typeArguments.isNotEmpty) { |
| sb.write('<'); |
| sb.write(typeArguments.join(',')); |
| sb.write('>'); |
| } |
| if (selector.isCall) { |
| sb.write(selector.callStructure.shortText); |
| } else if (selector.isSetter) { |
| sb.write('='); |
| } |
| return sb.toString(); |
| } |
| |
| DynamicUseKind get kind { |
| if (selector.isGetter) { |
| return DynamicUseKind.get; |
| } else if (selector.isSetter) { |
| return DynamicUseKind.set; |
| } else { |
| return DynamicUseKind.invoke; |
| } |
| } |
| |
| List<DartType> get typeArguments => _typeArguments ?? const []; |
| |
| @override |
| int get hashCode => Hashing.listHash( |
| typeArguments, |
| Hashing.objectsHash(selector, receiverConstraint), |
| ); |
| |
| @override |
| bool operator ==(other) { |
| if (identical(this, other)) return true; |
| if (other is! DynamicUse) return false; |
| return selector == other.selector && |
| receiverConstraint == other.receiverConstraint && |
| equalElements(typeArguments, other.typeArguments); |
| } |
| |
| @override |
| String toString() => '$selector,$receiverConstraint,$typeArguments'; |
| } |
| |
| enum StaticUseKind { |
| staticTearOff, |
| superTearOff, |
| superFieldSet, |
| superGet, |
| superSetterSet, |
| superInvoke, |
| instanceFieldGet, |
| instanceFieldSet, |
| closure, |
| closureCall, |
| callMethod, |
| constructorInvoke, |
| constConstructorInvoke, |
| directInvoke, |
| inlining, |
| staticInvoke, |
| staticGet, |
| staticSet, |
| fieldInit, |
| fieldConstantInit, |
| weakStaticTearOff, |
| } |
| |
| enum _StaticUseFlag { |
| type, |
| callStructure, |
| deferredImport, |
| constant, |
| typeArguments, |
| } |
| |
| /// Statically known use of an [Entity]. |
| // TODO(johnniwinther): Create backend-specific implementations with better |
| // invariants. |
| class StaticUse { |
| static const String tag = 'static-use'; |
| |
| final Entity element; |
| final StaticUseKind kind; |
| @override |
| final int hashCode; |
| |
| static final Map<StaticUse, StaticUse> _cache = {}; |
| |
| static void clearCache() => _cache.clear(); |
| |
| static StaticUse internal( |
| Entity element, |
| StaticUseKind kind, { |
| InterfaceType? type, |
| CallStructure? callStructure, |
| ImportEntity? deferredImport, |
| ConstantValue? constant, |
| List<DartType>? typeArguments, |
| }) { |
| StaticUse use; |
| if (type == null && |
| callStructure == null && |
| deferredImport == null && |
| constant == null && |
| typeArguments == null) { |
| use = StaticUse._(element, kind); |
| } else { |
| use = _ExtendedStaticUse._( |
| element, |
| kind, |
| type: type, |
| callStructure: callStructure, |
| deferredImport: deferredImport, |
| typeArguments: typeArguments, |
| constant: constant, |
| ); |
| } |
| return _cache[use] ??= use; |
| } |
| |
| /// Use the [StaticUse.internal] factory to ensure canonicalization. |
| StaticUse._(this.element, this.kind, {int? hashCode}) |
| : hashCode = hashCode ?? Hashing.objectHash(element, kind.hashCode); |
| |
| factory StaticUse.readFromDataSource(DataSourceReader source) { |
| source.begin(tag); |
| MemberEntity element = source.readMember(); |
| StaticUseKind kind = source.readEnum(StaticUseKind.values); |
| final bitMask = EnumSet<_StaticUseFlag>.fromRawBits(source.readInt()); |
| InterfaceType? type; |
| CallStructure? callStructure; |
| ImportEntity? deferredImport; |
| ConstantValue? constant; |
| List<DartType>? typeArguments; |
| |
| if (bitMask.contains(_StaticUseFlag.type)) { |
| type = source.readDartType() as InterfaceType; |
| } |
| if (bitMask.contains(_StaticUseFlag.callStructure)) { |
| callStructure = CallStructure.readFromDataSource(source); |
| } |
| if (bitMask.contains(_StaticUseFlag.deferredImport)) { |
| deferredImport = source.readImport(); |
| } |
| if (bitMask.contains(_StaticUseFlag.constant)) { |
| constant = source.readConstant(); |
| } |
| if (bitMask.contains(_StaticUseFlag.typeArguments)) { |
| typeArguments = source.readDartTypes(); |
| } |
| source.end(tag); |
| return StaticUse.internal( |
| element, |
| kind, |
| type: type, |
| callStructure: callStructure, |
| deferredImport: deferredImport, |
| constant: constant, |
| typeArguments: typeArguments, |
| ); |
| } |
| |
| void writeToDataSink(DataSinkWriter sink) { |
| sink.begin(tag); |
| sink.writeMember(element as MemberEntity); |
| sink.writeEnum(kind); |
| final bitMask = EnumSet<_StaticUseFlag>.fromValues([ |
| if (type != null) _StaticUseFlag.type, |
| if (callStructure != null) _StaticUseFlag.callStructure, |
| if (deferredImport != null) _StaticUseFlag.deferredImport, |
| if (constant != null) _StaticUseFlag.constant, |
| if (typeArguments != null) _StaticUseFlag.typeArguments, |
| ]); |
| sink.writeInt(bitMask.mask.bits); |
| if (type != null) { |
| sink.writeDartType(type!); |
| } |
| if (callStructure != null) { |
| callStructure!.writeToDataSink(sink); |
| } |
| if (deferredImport != null) { |
| sink.writeImport(deferredImport!); |
| } |
| if (constant != null) { |
| sink.writeConstant(constant!); |
| } |
| if (typeArguments != null) { |
| sink.writeDartTypes(typeArguments!); |
| } |
| sink.end(tag); |
| } |
| |
| bool _checkGenericInvariants() => true; |
| |
| CallStructure? get callStructure => null; |
| |
| ConstantValue? get constant => null; |
| |
| ImportEntity? get deferredImport => null; |
| |
| InterfaceType? get type => null; |
| |
| List<DartType>? get typeArguments => null; |
| |
| @override |
| bool operator ==(other) { |
| if (identical(this, other)) return true; |
| if (other is _ExtendedStaticUse) return false; |
| return other is StaticUse && element == other.element && kind == other.kind; |
| } |
| |
| @override |
| String toString() => 'StaticUse($element,$kind)'; |
| |
| /// Short textual representation use for testing. |
| String get shortText { |
| StringBuffer sb = StringBuffer(); |
| switch (kind) { |
| case StaticUseKind.instanceFieldSet: |
| case StaticUseKind.superFieldSet: |
| case StaticUseKind.superSetterSet: |
| case StaticUseKind.staticSet: |
| sb.write('set:'); |
| break; |
| case StaticUseKind.fieldInit: |
| sb.write('init:'); |
| break; |
| case StaticUseKind.closure: |
| sb.write('def:'); |
| break; |
| case StaticUseKind.staticTearOff: |
| case StaticUseKind.superTearOff: |
| case StaticUseKind.superGet: |
| case StaticUseKind.superInvoke: |
| case StaticUseKind.instanceFieldGet: |
| case StaticUseKind.closureCall: |
| case StaticUseKind.callMethod: |
| case StaticUseKind.constructorInvoke: |
| case StaticUseKind.constConstructorInvoke: |
| case StaticUseKind.directInvoke: |
| case StaticUseKind.inlining: |
| case StaticUseKind.staticInvoke: |
| case StaticUseKind.staticGet: |
| case StaticUseKind.fieldConstantInit: |
| case StaticUseKind.weakStaticTearOff: |
| break; |
| } |
| final member = element; |
| if (member is MemberEntity) { |
| if (member.enclosingClass != null) { |
| sb.write(member.enclosingClass!.name); |
| sb.write('.'); |
| } |
| } |
| if (member.name == null) { |
| sb.write('<anonymous>'); |
| } else { |
| sb.write(member.name); |
| } |
| if (typeArguments != null && typeArguments!.isNotEmpty) { |
| sb.write('<'); |
| sb.write(typeArguments!.join(',')); |
| sb.write('>'); |
| } |
| final callStructureLocal = callStructure; |
| if (callStructureLocal != null) { |
| sb.write('('); |
| sb.write(callStructureLocal.positionalArgumentCount); |
| if (callStructureLocal.namedArgumentCount > 0) { |
| sb.write(','); |
| sb.write(callStructureLocal.getOrderedNamedArguments().join(',')); |
| } |
| sb.write(')'); |
| } |
| if (deferredImport != null) { |
| sb.write('{'); |
| sb.write(deferredImport!.name); |
| sb.write('}'); |
| } |
| if (constant != null) { |
| sb.write('='); |
| sb.write(constant!.toStructuredText(null)); |
| } |
| return sb.toString(); |
| } |
| |
| /// Invocation of a static or top-level [element] with the given |
| /// [callStructure]. |
| factory StaticUse.staticInvoke( |
| FunctionEntity element, |
| CallStructure callStructure, [ |
| List<DartType>? typeArguments, |
| ImportEntity? deferredImport, |
| ]) { |
| assert( |
| element.isStatic || element.isTopLevel, |
| failedAt( |
| element, |
| "Static invoke element $element must be a top-level " |
| "or static method.", |
| ), |
| ); |
| assert( |
| element.isFunction, |
| failedAt(element, "Static get element $element must be a function."), |
| ); |
| StaticUse staticUse = StaticUse.internal( |
| element, |
| StaticUseKind.staticInvoke, |
| callStructure: callStructure, |
| typeArguments: typeArguments, |
| deferredImport: deferredImport, |
| ); |
| assert(staticUse._checkGenericInvariants()); |
| return staticUse; |
| } |
| |
| /// Closurization of a static or top-level function [element]. |
| factory StaticUse.staticTearOff( |
| FunctionEntity element, [ |
| ImportEntity? deferredImport, |
| ]) { |
| assert( |
| element.isStatic || element.isTopLevel, |
| failedAt( |
| element, |
| "Static tear-off element $element must be a top-level " |
| "or static method.", |
| ), |
| ); |
| assert( |
| element.isFunction, |
| failedAt(element, "Static get element $element must be a function."), |
| ); |
| return StaticUse.internal( |
| element, |
| StaticUseKind.staticTearOff, |
| deferredImport: deferredImport, |
| ); |
| } |
| |
| /// Weak reference to a tear-off of a static or top-level function [element]. |
| factory StaticUse.weakStaticTearOff( |
| FunctionEntity element, [ |
| ImportEntity? deferredImport, |
| ]) { |
| assert( |
| element.isStatic || element.isTopLevel, |
| failedAt( |
| element, |
| "Weak tear-off element $element must be a top-level " |
| "or static method.", |
| ), |
| ); |
| return StaticUse.internal( |
| element, |
| StaticUseKind.weakStaticTearOff, |
| deferredImport: deferredImport, |
| ); |
| } |
| |
| /// Read access of a static or top-level field or getter [element]. |
| factory StaticUse.staticGet( |
| MemberEntity element, [ |
| ImportEntity? deferredImport, |
| ]) { |
| assert( |
| element.isStatic || element.isTopLevel, |
| failedAt( |
| element, |
| "Static get element $element must be a top-level " |
| "or static field or getter.", |
| ), |
| ); |
| assert( |
| element is FieldEntity || element.isGetter, |
| failedAt( |
| element, |
| "Static get element $element must be a field or a getter.", |
| ), |
| ); |
| return StaticUse.internal( |
| element, |
| StaticUseKind.staticGet, |
| deferredImport: deferredImport, |
| ); |
| } |
| |
| /// Write access of a static or top-level field or setter [element]. |
| factory StaticUse.staticSet( |
| MemberEntity element, [ |
| ImportEntity? deferredImport, |
| ]) { |
| assert( |
| element.isStatic || element.isTopLevel, |
| failedAt( |
| element, |
| "Static set element $element " |
| "must be a top-level or static method.", |
| ), |
| ); |
| assert( |
| (element is FieldEntity && element.isAssignable) || element.isSetter, |
| failedAt( |
| element, |
| "Static set element $element must be a field or a setter.", |
| ), |
| ); |
| return StaticUse.internal( |
| element, |
| StaticUseKind.staticSet, |
| deferredImport: deferredImport, |
| ); |
| } |
| |
| /// Invocation of the lazy initializer for a static or top-level field |
| /// [element]. |
| factory StaticUse.staticInit(FieldEntity element) { |
| assert( |
| element.isStatic || element.isTopLevel, |
| failedAt( |
| element, |
| "Static init element $element must be a top-level " |
| "or static method.", |
| ), |
| ); |
| return StaticUse.internal(element, StaticUseKind.fieldInit); |
| } |
| |
| /// Invocation of a super method [element] with the given [callStructure]. |
| factory StaticUse.superInvoke( |
| FunctionEntity element, |
| CallStructure callStructure, [ |
| List<DartType>? typeArguments, |
| ]) { |
| assert( |
| element.isInstanceMember, |
| failedAt( |
| element, |
| "Super invoke element $element must be an instance method.", |
| ), |
| ); |
| StaticUse staticUse = StaticUse.internal( |
| element, |
| StaticUseKind.superInvoke, |
| callStructure: callStructure, |
| typeArguments: typeArguments, |
| ); |
| assert(staticUse._checkGenericInvariants()); |
| return staticUse; |
| } |
| |
| /// Read access of a super field or getter [element]. |
| factory StaticUse.superGet(MemberEntity element) { |
| assert( |
| element.isInstanceMember, |
| failedAt( |
| element, |
| "Super get element $element must be an instance method.", |
| ), |
| ); |
| assert( |
| element is FieldEntity || element.isGetter, |
| failedAt( |
| element, |
| "Super get element $element must be a field or a getter.", |
| ), |
| ); |
| return StaticUse.internal(element, StaticUseKind.superGet); |
| } |
| |
| /// Write access of a super field [element]. |
| factory StaticUse.superFieldSet(FieldEntity element) { |
| assert( |
| element.isInstanceMember, |
| failedAt( |
| element, |
| "Super set element $element must be an instance method.", |
| ), |
| ); |
| return StaticUse.internal(element, StaticUseKind.superFieldSet); |
| } |
| |
| /// Write access of a super setter [element]. |
| factory StaticUse.superSetterSet(FunctionEntity element) { |
| assert( |
| element.isInstanceMember, |
| failedAt( |
| element, |
| "Super set element $element must be an instance method.", |
| ), |
| ); |
| assert( |
| element.isSetter, |
| failedAt(element, "Super set element $element must be a setter."), |
| ); |
| return StaticUse.internal(element, StaticUseKind.superSetterSet); |
| } |
| |
| /// Closurization of a super method [element]. |
| factory StaticUse.superTearOff(FunctionEntity element) { |
| assert( |
| element.isInstanceMember && element.isFunction, |
| failedAt( |
| element, |
| "Super invoke element $element must be an instance method.", |
| ), |
| ); |
| return StaticUse.internal(element, StaticUseKind.superTearOff); |
| } |
| |
| /// Invocation of a constructor [element] through a this or super |
| /// constructor call with the given [callStructure]. |
| factory StaticUse.superConstructorInvoke( |
| ConstructorEntity element, |
| CallStructure callStructure, |
| ) { |
| assert( |
| element.isGenerativeConstructor, |
| failedAt( |
| element, |
| "Constructor invoke element $element must be a " |
| "generative constructor.", |
| ), |
| ); |
| return StaticUse.internal( |
| element, |
| StaticUseKind.staticInvoke, |
| callStructure: callStructure, |
| ); |
| } |
| |
| /// Invocation of a constructor (body) [element] through a this or super |
| /// constructor call with the given [callStructure]. |
| factory StaticUse.constructorBodyInvoke( |
| ConstructorBodyEntity element, |
| CallStructure callStructure, |
| ) { |
| return StaticUse.internal( |
| element, |
| StaticUseKind.staticInvoke, |
| callStructure: callStructure, |
| ); |
| } |
| |
| /// Direct invocation of a generator (body) [element], as a static call or |
| /// through a this or super constructor call. |
| factory StaticUse.generatorBodyInvoke(FunctionEntity element) { |
| return StaticUse.internal( |
| element, |
| StaticUseKind.staticInvoke, |
| callStructure: CallStructure.noArgs, |
| ); |
| } |
| |
| /// Direct invocation of a method [element] with the given [callStructure]. |
| factory StaticUse.directInvoke( |
| FunctionEntity element, |
| CallStructure callStructure, |
| List<DartType>? typeArguments, |
| ) { |
| assert( |
| element.isInstanceMember, |
| failedAt( |
| element, |
| "Direct invoke element $element must be an instance member.", |
| ), |
| ); |
| assert( |
| element.isFunction, |
| failedAt(element, "Direct invoke element $element must be a method."), |
| ); |
| StaticUse staticUse = StaticUse.internal( |
| element, |
| StaticUseKind.directInvoke, |
| callStructure: callStructure, |
| typeArguments: typeArguments, |
| ); |
| assert(staticUse._checkGenericInvariants()); |
| return staticUse; |
| } |
| |
| /// Direct read access of a field or getter [element]. |
| factory StaticUse.directGet(MemberEntity element) { |
| assert( |
| element.isInstanceMember, |
| failedAt( |
| element, |
| "Direct get element $element must be an instance member.", |
| ), |
| ); |
| assert( |
| element is FieldEntity || element.isGetter, |
| failedAt( |
| element, |
| "Direct get element $element must be a field or a getter.", |
| ), |
| ); |
| return StaticUse.internal(element, StaticUseKind.staticGet); |
| } |
| |
| /// Direct write access of a field [element]. |
| factory StaticUse.directSet(FieldEntity element) { |
| assert( |
| element.isInstanceMember, |
| failedAt( |
| element, |
| "Direct set element $element must be an instance member.", |
| ), |
| ); |
| return StaticUse.internal(element, StaticUseKind.staticSet); |
| } |
| |
| /// Constructor invocation of [element] with the given [callStructure]. |
| factory StaticUse.constructorInvoke( |
| ConstructorEntity element, |
| CallStructure callStructure, |
| ) { |
| return StaticUse.internal( |
| element, |
| StaticUseKind.staticInvoke, |
| callStructure: callStructure, |
| ); |
| } |
| |
| /// Constructor invocation of [element] with the given [callStructure] on |
| /// [type]. |
| factory StaticUse.typedConstructorInvoke( |
| ConstructorEntity element, |
| CallStructure callStructure, |
| InterfaceType type, |
| ImportEntity? deferredImport, |
| ) { |
| return StaticUse.internal( |
| element, |
| StaticUseKind.constructorInvoke, |
| type: type, |
| callStructure: callStructure, |
| deferredImport: deferredImport, |
| ); |
| } |
| |
| /// Constant constructor invocation of [element] with the given |
| /// [callStructure] on [type]. |
| factory StaticUse.constConstructorInvoke( |
| ConstructorEntity element, |
| CallStructure callStructure, |
| InterfaceType type, |
| ImportEntity? deferredImport, |
| ) { |
| return StaticUse.internal( |
| element, |
| StaticUseKind.constConstructorInvoke, |
| type: type, |
| callStructure: callStructure, |
| deferredImport: deferredImport, |
| ); |
| } |
| |
| /// Initialization of an instance field [element]. |
| factory StaticUse.fieldInit(FieldEntity element) { |
| assert( |
| element.isInstanceMember, |
| failedAt( |
| element, |
| "Field init element $element must be an instance field.", |
| ), |
| ); |
| return StaticUse.internal(element, StaticUseKind.fieldInit); |
| } |
| |
| /// Constant initialization of an instance field [element]. |
| factory StaticUse.fieldConstantInit( |
| FieldEntity element, |
| ConstantValue constant, |
| ) { |
| assert( |
| element.isInstanceMember, |
| failedAt( |
| element, |
| "Field init element $element must be an instance field.", |
| ), |
| ); |
| return StaticUse.internal( |
| element, |
| StaticUseKind.fieldConstantInit, |
| constant: constant, |
| ); |
| } |
| |
| /// Read access of an instance field or boxed field [element]. |
| factory StaticUse.fieldGet(FieldEntity element) { |
| assert( |
| element.isInstanceMember || element is JContextField, |
| failedAt( |
| element, |
| "Field init element $element must be an instance or boxed field.", |
| ), |
| ); |
| return StaticUse.internal(element, StaticUseKind.instanceFieldGet); |
| } |
| |
| /// Write access of an instance field or boxed field [element]. |
| factory StaticUse.fieldSet(FieldEntity element) { |
| assert( |
| element.isInstanceMember || element is JContextField, |
| failedAt( |
| element, |
| "Field init element $element must be an instance or boxed field.", |
| ), |
| ); |
| return StaticUse.internal(element, StaticUseKind.instanceFieldSet); |
| } |
| |
| /// Read of a local function [element]. |
| factory StaticUse.closure(Local element) { |
| return StaticUse.internal(element, StaticUseKind.closure); |
| } |
| |
| /// An invocation of a local function [element] with the provided |
| /// [callStructure] and [typeArguments]. |
| factory StaticUse.closureCall( |
| Local element, |
| CallStructure callStructure, |
| List<DartType>? typeArguments, |
| ) { |
| StaticUse staticUse = StaticUse.internal( |
| element, |
| StaticUseKind.closureCall, |
| callStructure: callStructure, |
| typeArguments: typeArguments, |
| ); |
| assert(staticUse._checkGenericInvariants()); |
| return staticUse; |
| } |
| |
| /// Read of a call [method] on a closureClass. |
| factory StaticUse.callMethod(FunctionEntity method) { |
| return StaticUse.internal(method, StaticUseKind.callMethod); |
| } |
| |
| /// Implicit method/constructor invocation of [element] created by the |
| /// backend. |
| factory StaticUse.implicitInvoke(FunctionEntity element) { |
| return StaticUse.internal( |
| element, |
| StaticUseKind.staticInvoke, |
| callStructure: element.parameterStructure.callStructure, |
| ); |
| } |
| |
| /// Inlining of [element]. |
| factory StaticUse.constructorInlining( |
| ConstructorEntity element, |
| InterfaceType? instanceType, |
| ) { |
| return StaticUse.internal( |
| element, |
| StaticUseKind.inlining, |
| type: instanceType, |
| ); |
| } |
| |
| /// Inlining of [element]. |
| factory StaticUse.methodInlining( |
| FunctionEntity element, |
| List<DartType>? typeArguments, |
| ) { |
| return StaticUse.internal( |
| element, |
| StaticUseKind.inlining, |
| typeArguments: typeArguments, |
| ); |
| } |
| } |
| |
| /// A [StaticUse] which has additional data beyond its [element] and [kind]. |
| /// |
| /// This is only used when one or more of these exta fields are specified in |
| /// order to keep the representation of [StaticUse] compact when possible. |
| class _ExtendedStaticUse extends StaticUse { |
| @override |
| final InterfaceType? type; |
| @override |
| final CallStructure? callStructure; |
| @override |
| final ImportEntity? deferredImport; |
| @override |
| final ConstantValue? constant; |
| @override |
| final List<DartType>? typeArguments; |
| |
| /// Use the [StaticUse.internal] factory to ensure canonicalization. |
| _ExtendedStaticUse._( |
| super.element, |
| super.kind, { |
| this.type, |
| this.callStructure, |
| this.deferredImport, |
| this.constant, |
| this.typeArguments, |
| }) : super._( |
| hashCode: Hashing.listHash([ |
| element, |
| kind, |
| type, |
| Hashing.listHash(typeArguments), |
| callStructure, |
| deferredImport, |
| constant, |
| ]), |
| ); |
| |
| @override |
| bool _checkGenericInvariants() { |
| assert( |
| (callStructure?.typeArgumentCount ?? 0) == (typeArguments?.length ?? 0), |
| failedAt( |
| element, |
| "Type argument count mismatch. Call structure has " |
| "${callStructure?.typeArgumentCount ?? 0} but " |
| "${typeArguments?.length ?? 0} were passed in $this.", |
| ), |
| ); |
| return true; |
| } |
| |
| @override |
| // ignore: hash_and_equals |
| bool operator ==(other) { |
| if (identical(this, other)) return true; |
| // Subtypes of StaticUse are normalized so we can just compare against this |
| // specific subtype. |
| return other is _ExtendedStaticUse && |
| element == other.element && |
| kind == other.kind && |
| type == other.type && |
| callStructure == other.callStructure && |
| equalElements(typeArguments, other.typeArguments) && |
| deferredImport == other.deferredImport && |
| constant == other.constant; |
| } |
| |
| @override |
| String toString() => |
| 'StaticUse($element,$kind,$type,$typeArguments,$callStructure)'; |
| } |
| |
| enum TypeUseKind { |
| isCheck, |
| asCast, |
| catchType, |
| typeLiteral, |
| instantiation, |
| nativeInstantiation, |
| constInstantiation, |
| recordInstantiation, |
| constructorReference, |
| implicitCast, |
| parameterCheck, |
| rtiValue, |
| typeArgument, |
| namedTypeVariable, |
| typeVariableBoundCheck, |
| } |
| |
| /// Use of a [DartType]. |
| class TypeUse { |
| static const String tag = 'type-use'; |
| |
| final DartType type; |
| final TypeUseKind kind; |
| @override |
| final int hashCode; |
| final ImportEntity? deferredImport; |
| |
| TypeUse.internal(this.type, this.kind, [this.deferredImport]) |
| : hashCode = Hashing.objectsHash(type, kind, deferredImport); |
| |
| factory TypeUse.readFromDataSource(DataSourceReader source) { |
| source.begin(tag); |
| DartType type = source.readDartType(); |
| TypeUseKind kind = source.readEnum(TypeUseKind.values); |
| ImportEntity? deferredImport = source.readImportOrNull(); |
| source.end(tag); |
| return TypeUse.internal(type, kind, deferredImport); |
| } |
| |
| void writeToDataSink(DataSinkWriter sink) { |
| sink.begin(tag); |
| sink.writeDartType(type); |
| sink.writeEnum(kind); |
| sink.writeImportOrNull(deferredImport); |
| sink.end(tag); |
| } |
| |
| /// Short textual representation use for testing. |
| String get shortText { |
| StringBuffer sb = StringBuffer(); |
| switch (kind) { |
| case TypeUseKind.isCheck: |
| sb.write('is:'); |
| break; |
| case TypeUseKind.asCast: |
| sb.write('as:'); |
| break; |
| case TypeUseKind.catchType: |
| sb.write('catch:'); |
| break; |
| case TypeUseKind.typeLiteral: |
| sb.write('lit:'); |
| break; |
| case TypeUseKind.instantiation: |
| sb.write('inst:'); |
| break; |
| case TypeUseKind.constInstantiation: |
| sb.write('const:'); |
| break; |
| case TypeUseKind.recordInstantiation: |
| sb.write('record:'); |
| break; |
| case TypeUseKind.constructorReference: |
| sb.write('constructor:'); |
| break; |
| case TypeUseKind.nativeInstantiation: |
| sb.write('native:'); |
| break; |
| case TypeUseKind.implicitCast: |
| sb.write('impl:'); |
| break; |
| case TypeUseKind.parameterCheck: |
| sb.write('param:'); |
| break; |
| case TypeUseKind.rtiValue: |
| sb.write('rti:'); |
| break; |
| case TypeUseKind.typeArgument: |
| sb.write('typeArg:'); |
| break; |
| case TypeUseKind.namedTypeVariable: |
| sb.write('named:'); |
| break; |
| case TypeUseKind.typeVariableBoundCheck: |
| sb.write('bound:'); |
| break; |
| } |
| sb.write(type); |
| if (deferredImport != null) { |
| sb.write('{'); |
| sb.write(deferredImport!.name); |
| sb.write('}'); |
| } |
| return sb.toString(); |
| } |
| |
| /// [type] used in an is check, like `e is T` or `e is! T`. |
| factory TypeUse.isCheck(DartType type) { |
| return TypeUse.internal(type, TypeUseKind.isCheck); |
| } |
| |
| /// [type] used in an as cast, like `e as T`. |
| factory TypeUse.asCast(DartType type) { |
| return TypeUse.internal(type, TypeUseKind.asCast); |
| } |
| |
| /// [type] used as a parameter type or field type in Dart 2, like `T` in: |
| /// |
| /// method(T t) {} |
| /// T field; |
| /// |
| factory TypeUse.parameterCheck(DartType type) { |
| return TypeUse.internal(type, TypeUseKind.parameterCheck); |
| } |
| |
| /// [type] used in an implicit cast in Dart 2, like `T` in |
| /// |
| /// dynamic foo = Object(); |
| /// T bar = foo; // Implicitly `T bar = foo as T`. |
| /// |
| factory TypeUse.implicitCast(DartType type) { |
| return TypeUse.internal(type, TypeUseKind.implicitCast); |
| } |
| |
| /// [type] used in a on type catch clause, like `try {} on T catch (e) {}`. |
| factory TypeUse.catchType(DartType type) { |
| return TypeUse.internal(type, TypeUseKind.catchType); |
| } |
| |
| /// [type] used as a type literal, like `foo() => T;`. |
| factory TypeUse.typeLiteral(DartType type, ImportEntity? deferredImport) { |
| return TypeUse.internal(type, TypeUseKind.typeLiteral, deferredImport); |
| } |
| |
| /// [type] used in an instantiation, like `new T();`. |
| factory TypeUse.instantiation(InterfaceType type) { |
| return TypeUse.internal(type, TypeUseKind.instantiation); |
| } |
| |
| /// [type] used in a constant instantiation, like `const T();`. |
| factory TypeUse.constInstantiation( |
| InterfaceType type, |
| ImportEntity? deferredImport, |
| ) { |
| return TypeUse.internal( |
| type, |
| TypeUseKind.constInstantiation, |
| deferredImport, |
| ); |
| } |
| |
| /// [type] used in a native instantiation. |
| factory TypeUse.nativeInstantiation(InterfaceType type) { |
| return TypeUse.internal(type, TypeUseKind.nativeInstantiation); |
| } |
| |
| /// [type] used in a record instantiation, like `(1, 2)` or `const (1, 2)`. |
| factory TypeUse.recordInstantiation(RecordType type) { |
| return TypeUse.internal(type, TypeUseKind.recordInstantiation); |
| } |
| |
| /// [type] used as a direct RTI value. |
| factory TypeUse.constTypeLiteral(DartType type) { |
| return TypeUse.internal(type, TypeUseKind.rtiValue); |
| } |
| |
| /// [type] constructor used, for example in a `instanceof` check. |
| factory TypeUse.constructorReference(DartType type) { |
| return TypeUse.internal(type, TypeUseKind.constructorReference); |
| } |
| |
| /// [type] used directly as a type argument. |
| /// |
| /// The happens during optimization where a type variable can be replaced by |
| /// an invariable type argument derived from a constant receiver. |
| factory TypeUse.typeArgument(DartType type) { |
| return TypeUse.internal(type, TypeUseKind.typeArgument); |
| } |
| |
| /// [type] used as a named type variable in a recipe. |
| factory TypeUse.namedTypeVariable(TypeVariableType type) => |
| TypeUse.internal(type, TypeUseKind.namedTypeVariable); |
| |
| /// [type] used as a bound on a type variable. |
| factory TypeUse.typeVariableBoundCheck(DartType type) => |
| TypeUse.internal(type, TypeUseKind.typeVariableBoundCheck); |
| |
| @override |
| bool operator ==(other) { |
| if (identical(this, other)) return true; |
| if (other is! TypeUse) return false; |
| return type == other.type && kind == other.kind; |
| } |
| |
| @override |
| String toString() => 'TypeUse($type,$kind)'; |
| } |
| |
| /// Use of a [ConstantValue]. |
| class ConstantUse { |
| static const String tag = 'constant-use'; |
| |
| final ConstantValue value; |
| |
| ConstantUse._(this.value); |
| |
| factory ConstantUse.readFromDataSource(DataSourceReader source) { |
| source.begin(tag); |
| ConstantValue value = source.readConstant(); |
| source.end(tag); |
| return ConstantUse._(value); |
| } |
| |
| void writeToDataSink(DataSinkWriter sink) { |
| sink.begin(tag); |
| sink.writeConstant(value); |
| sink.end(tag); |
| } |
| |
| /// Short textual representation use for testing. |
| String get shortText { |
| return value.toDartText(null); |
| } |
| |
| /// Constant used as the initial value of a field. |
| ConstantUse.init(ConstantValue value) : this._(value); |
| |
| /// Type constant used for registration of custom elements. |
| ConstantUse.customElements(TypeConstantValue value) : this._(value); |
| |
| /// Constant literal used in code. |
| ConstantUse.literal(ConstantValue value) : this._(value); |
| |
| /// Deferred constant used in code. |
| ConstantUse.deferred(DeferredGlobalConstantValue value) : this._(value); |
| |
| @override |
| bool operator ==(other) { |
| if (identical(this, other)) return true; |
| if (other is! ConstantUse) return false; |
| return value == other.value; |
| } |
| |
| @override |
| int get hashCode => value.hashCode; |
| |
| @override |
| String toString() => 'ConstantUse(${value.toStructuredText(null)})'; |
| } |
| |
| /// Conditional impact and Kernel nodes for replacement if it isn't applied. |
| /// |
| /// If one of [original], [replacement], or [replacementImpact] is provided, the |
| /// others must also be provided. |
| class ConditionalUse { |
| /// If any of the members in this list are reachable from the program then |
| /// these conditions are considered "satisfied", [original] is kept in the |
| /// Kernel tree and [impact] is applied to the world. Otherwise [original] is |
| /// replaced with [replacement] in the Kernel tree and [replacementImpact] is |
| /// instead applied to the world. |
| final List<MemberEntity> originalConditions; |
| |
| /// The node to replace if [originalConditions] are not satisfied. |
| /// [impact] is the impact implied by this node. |
| final ir.TreeNode? original; |
| |
| /// The node to replace [original] with is [originalConditions] are not |
| /// satisfied. |
| final ir.TreeNode? replacement; |
| |
| /// The impact to apply for [original] if [originalConditions] are satisfied. |
| final WorldImpact impact; |
| |
| /// The impact to apply for [replacement] if [originalConditions] are not |
| /// satisifed. |
| final WorldImpact? replacementImpact; |
| |
| ConditionalUse.noReplacement({ |
| required this.impact, |
| required this.originalConditions, |
| }) : original = null, |
| replacement = null, |
| replacementImpact = null, |
| assert(originalConditions.isNotEmpty); |
| |
| ConditionalUse.withReplacement({ |
| required this.original, |
| required this.replacement, |
| required this.replacementImpact, |
| required this.impact, |
| required this.originalConditions, |
| }) : assert(originalConditions.isNotEmpty); |
| } |