blob: d30f0c453f2b7e6e656035a08199775a9cf59b2c [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:math' as math;
import '../common.dart';
import '../constants/values.dart';
import '../elements/entities.dart';
import '../js_model/closure.dart' show JContextField;
import '../serialization/serialization.dart';
import '../util/enumset.dart';
import 'call_structure.dart';
abstract class AbstractUsage<E extends Enum> {
EnumSet<E> _pendingUse = EnumSet.empty();
AbstractUsage.cloned(this._pendingUse);
AbstractUsage() {
_pendingUse = _originalUse;
}
/// Returns the uses of [entity] that have been registered.
EnumSet<E> get _appliedUse => _originalUse.setMinus(_pendingUse);
EnumSet<E> get _originalUse;
/// `true` if the [_appliedUse] is non-empty.
bool get hasUse => _appliedUse.isNotEmpty;
/// Returns `true` if [other] has the same original and pending usage as this.
bool hasSameUsage(AbstractUsage<E> other) {
if (identical(this, other)) return true;
return _originalUse == other._originalUse &&
_pendingUse == other._pendingUse;
}
}
/// Registry for the observed use of a member [entity] in the open world.
abstract class MemberUsage extends AbstractUsage<MemberUse> {
final MemberEntity entity;
MemberUsage.internal(this.entity) : super();
MemberUsage.cloned(this.entity, EnumSet<MemberUse> pendingUse)
: super.cloned(pendingUse);
factory MemberUsage(MemberEntity member, {MemberAccess? potentialAccess}) {
/// Create the set of potential accesses to [member], limited to [original]
/// if provided.
EnumSet<Access> createPotentialAccessSet(EnumSet<Access>? original) {
if (original != null) {
return original;
}
if (member.isTopLevel || member.isStatic || member is ConstructorEntity) {
// TODO(johnniwinther): Track super constructor invocations?
return EnumSet.fromValues([Access.staticAccess]);
} else if (member.isInstanceMember) {
return EnumSet.allValues(Access.values);
} else {
assert(member is JContextField, "Unexpected member: $member");
return EnumSet.empty();
}
}
/// Create the set of potential read accesses to [member], limited to reads
/// in [potentialAccess] if provided.
EnumSet<Access> createPotentialReads() {
return createPotentialAccessSet(potentialAccess?.reads);
}
/// Create the set of potential write accesses to [member], limited to
/// writes in [potentialAccess] if provided.
EnumSet<Access> createPotentialWrites() {
return createPotentialAccessSet(potentialAccess?.writes);
}
/// Create the set of potential invocation accesses to [member], limited to
/// invocations in [potentialAccess] if provided.
EnumSet<Access> createPotentialInvokes() {
return createPotentialAccessSet(potentialAccess?.invokes);
}
if (member is FieldEntity) {
if (member.isAssignable) {
return FieldUsage(
member,
potentialReads: createPotentialReads(),
potentialWrites: createPotentialWrites(),
potentialInvokes: createPotentialInvokes(),
);
} else {
return FieldUsage(
member,
potentialReads: createPotentialReads(),
potentialWrites: EnumSet.empty(),
potentialInvokes: createPotentialInvokes(),
);
}
} else if (member is FunctionEntity) {
if (member.isGetter) {
return PropertyUsage(
member,
potentialReads: createPotentialReads(),
potentialWrites: EnumSet.empty(),
potentialInvokes: createPotentialInvokes(),
);
} else if (member.isSetter) {
return PropertyUsage(
member,
potentialReads: EnumSet.empty(),
potentialWrites: createPotentialWrites(),
potentialInvokes: EnumSet.empty(),
);
} else if (member is ConstructorEntity) {
return MethodUsage(
member,
potentialReads: EnumSet.empty(),
potentialInvokes: createPotentialInvokes(),
);
} else {
return MethodUsage(
member,
potentialReads: createPotentialReads(),
potentialInvokes: createPotentialInvokes(),
);
}
}
throw failedAt(member, "Unexpected member: $member");
}
/// `true` if [entity] has been initialized.
bool get hasInit => true;
/// The set of constant initial values for a field.
Iterable<ConstantValue>? get initialConstants => null;
/// `true` if [entity] has been read as a value. For a field this is a normal
/// read access, for a function this is a closurization.
bool get hasRead => reads.isNotEmpty;
/// The set of potential read accesses to this member that have not yet
/// been registered.
EnumSet<Access> get potentialReads => const EnumSet.empty();
/// The set of registered read accesses to this member.
EnumSet<Access> get reads => const EnumSet.empty();
/// `true` if a value has been written to [entity].
bool get hasWrite => writes.isNotEmpty;
/// The set of potential write accesses to this member that have not yet
/// been registered.
EnumSet<Access> get potentialWrites => const EnumSet.empty();
/// The set of registered write accesses to this member.
EnumSet<Access> get writes => const EnumSet.empty();
/// `true` if an invocation has been performed on the value [entity]. For a
/// function this is a normal invocation, for a field this is a read access
/// followed by an invocation of the function-like value.
bool get hasInvoke => invokes.isNotEmpty;
/// The set of potential invocation accesses to this member that have not yet
/// been registered.
EnumSet<Access> get potentialInvokes => const EnumSet.empty();
/// The set of registered invocation accesses to this member.
EnumSet<Access> get invokes => const EnumSet.empty();
/// Returns the [ParameterStructure] corresponding to the parameters that are
/// used in invocations of [entity]. For a field, getter or setter this is
/// always `null`.
ParameterStructure? get invokedParameters => null;
/// Whether this member has any potential but unregistered dynamic reads,
/// writes or invocations.
bool get hasPendingDynamicUse =>
hasPendingDynamicInvoke ||
hasPendingDynamicRead ||
hasPendingDynamicWrite;
/// Whether this member has any potential but unregistered dynamic
/// invocations.
bool get hasPendingDynamicInvoke =>
potentialInvokes.contains(Access.dynamicAccess);
/// Whether this member has any potential but unregistered dynamic reads.
bool get hasPendingDynamicRead =>
potentialReads.contains(Access.dynamicAccess);
/// Whether this member has any potential but unregistered dynamic writes.
bool get hasPendingDynamicWrite =>
potentialWrites.contains(Access.dynamicAccess);
/// Registers the [entity] has been initialized and returns the new
/// [MemberUse]s that it caused.
///
/// For a field this is the initial write access, for a function this is a
/// no-op.
EnumSet<MemberUse> init() => MemberUses.none;
/// Registers the [entity] has been initialized with [constant] and returns
/// the new [MemberUse]s that it caused.
///
/// For a field this is the initial write access, for a function this is a
/// no-op.
EnumSet<MemberUse> constantInit(ConstantValue constant) => MemberUses.none;
/// Registers a read of the value of [entity] and returns the new [MemberUse]s
/// that it caused.
///
/// For a field this is a normal read access, for a function this is a
/// closurization.
EnumSet<MemberUse> read(EnumSet<Access> accesses) => MemberUses.none;
/// Registers a write of a value to [entity] and returns the new [MemberUse]s
/// that it caused.
EnumSet<MemberUse> write(EnumSet<Access> accesses) => MemberUses.none;
/// Registers an invocation on the value of [entity] and returns the new
/// [MemberUse]s that it caused.
///
/// For a function this is a normal invocation, for a field this is a read
/// access followed by an invocation of the function-like value.
///
/// If [forceAccesses] is true, the provided [accesses] will be applied to
/// this usage even if there are no matching pending invokes.
EnumSet<MemberUse> invoke(
EnumSet<Access> accesses,
CallStructure callStructure, {
bool forceAccesses = false,
}) => MemberUses.none;
@override
EnumSet<MemberUse> get _originalUse => MemberUses.normalOnly;
@override
int get hashCode => entity.hashCode;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! MemberUsage) return false;
return entity == other.entity;
}
MemberUsage clone();
bool dataEquals(MemberUsage other) {
assert(entity == other.entity);
return hasInit == other.hasInit &&
hasRead == other.hasRead &&
hasInvoke == other.hasInvoke &&
hasWrite == other.hasWrite &&
hasPendingDynamicRead == other.hasPendingDynamicRead &&
hasPendingDynamicWrite == other.hasPendingDynamicWrite &&
hasPendingDynamicInvoke == other.hasPendingDynamicInvoke &&
hasPendingDynamicUse == other.hasPendingDynamicUse &&
_pendingUse == other._pendingUse &&
_appliedUse == other._appliedUse &&
reads == other.reads &&
writes == other.writes &&
invokes == other.invokes &&
potentialReads == other.potentialReads &&
potentialWrites == other.potentialWrites &&
potentialInvokes == other.potentialInvokes &&
invokedParameters == other.invokedParameters;
}
}
/// Member usage tracking for a getter or setter.
class PropertyUsage extends MemberUsage {
@override
EnumSet<Access> potentialReads;
@override
EnumSet<Access> potentialWrites;
@override
EnumSet<Access> potentialInvokes;
@override
EnumSet<Access> reads;
@override
EnumSet<Access> writes;
@override
EnumSet<Access> invokes;
PropertyUsage.cloned(
super.member,
super.pendingUse, {
required this.potentialReads,
required this.potentialWrites,
required this.potentialInvokes,
required this.reads,
required this.writes,
required this.invokes,
}) : super.cloned();
PropertyUsage(
super.member, {
required this.potentialReads,
required this.potentialWrites,
required this.potentialInvokes,
}) : reads = EnumSet.empty(),
writes = EnumSet.empty(),
invokes = EnumSet.empty(),
super.internal();
@override
EnumSet<MemberUse> read(EnumSet<Access> accesses) {
bool alreadyHasRead = hasRead;
reads = reads.union(potentialReads.intersection(accesses));
potentialReads = potentialReads.setMinus(accesses);
if (alreadyHasRead) {
return MemberUses.none;
}
final removed = _pendingUse.intersection(MemberUses.normalOnly);
_pendingUse = _pendingUse.setMinus(MemberUses.normalOnly);
return removed;
}
@override
EnumSet<MemberUse> write(EnumSet<Access> accesses) {
bool alreadyHasWrite = hasWrite;
writes = writes.union(potentialWrites.intersection(accesses));
potentialWrites = potentialWrites.setMinus(accesses);
if (alreadyHasWrite) {
return MemberUses.none;
}
final removed = _pendingUse.intersection(MemberUses.normalOnly);
_pendingUse = _pendingUse.setMinus(MemberUses.normalOnly);
return removed;
}
@override
EnumSet<MemberUse> invoke(
EnumSet<Access> accesses,
CallStructure callStructure, {
bool forceAccesses = false,
}) {
// We use `hasRead` here instead of `hasInvoke` because getters only have
// 'normal use' (they cannot be closurized). This means that invoking an
// already read getter does not result a new member use.
bool alreadyHasRead = hasRead;
reads = reads.union(potentialReads.intersection(Accesses.staticAccess));
potentialReads = potentialReads.setMinus(Accesses.staticAccess);
final removedPotentialInvokes = potentialInvokes.intersection(accesses);
potentialInvokes = potentialInvokes.setMinus(accesses);
if (forceAccesses) {
invokes = invokes.union(accesses);
} else {
invokes = invokes.union(removedPotentialInvokes);
}
if (alreadyHasRead) {
return MemberUses.none;
}
final removed = _pendingUse.intersection(MemberUses.normalOnly);
_pendingUse = _pendingUse.setMinus(MemberUses.normalOnly);
return removed;
}
@override
MemberUsage clone() {
return PropertyUsage.cloned(
entity,
_pendingUse,
potentialReads: potentialReads,
potentialWrites: potentialWrites,
potentialInvokes: potentialInvokes,
reads: reads,
writes: writes,
invokes: invokes,
);
}
@override
String toString() =>
'PropertyUsage($entity,'
'reads=${reads.iterable(Access.values)},'
'writes=${writes.iterable(Access.values)},'
'invokes=${invokes.iterable(Access.values)},'
'potentialReads=${potentialReads.iterable(Access.values)},'
'potentialWrites=${potentialWrites.iterable(Access.values)},'
'potentialInvokes=${potentialInvokes.iterable(Access.values)},'
'pendingUse=${_pendingUse.iterable(MemberUse.values)},'
'initialConstants=${initialConstants?.map((c) => c.toStructuredText(null))})';
}
/// Member usage tracking for a field.
class FieldUsage extends MemberUsage {
@override
bool hasInit;
@override
EnumSet<Access> potentialReads;
@override
EnumSet<Access> potentialWrites;
@override
EnumSet<Access> potentialInvokes;
@override
EnumSet<Access> reads;
@override
EnumSet<Access> invokes;
@override
EnumSet<Access> writes;
List<ConstantValue>? _initialConstants;
FieldUsage.cloned(
FieldEntity super.field,
super.pendingUse, {
required this.potentialReads,
required this.potentialWrites,
required this.potentialInvokes,
required this.hasInit,
required this.reads,
required this.writes,
required this.invokes,
}) : super.cloned();
FieldUsage(
FieldEntity super.field, {
required this.potentialReads,
required this.potentialWrites,
required this.potentialInvokes,
}) : hasInit = false,
reads = EnumSet.empty(),
writes = EnumSet.empty(),
invokes = EnumSet.empty(),
super.internal();
@override
Iterable<ConstantValue> get initialConstants => _initialConstants ?? const [];
@override
EnumSet<MemberUse> init() {
if (hasInit) {
return MemberUses.none;
}
hasInit = true;
final removed = _pendingUse.intersection(MemberUses.normalOnly);
_pendingUse = _pendingUse.setMinus(MemberUses.normalOnly);
return removed;
}
@override
EnumSet<MemberUse> constantInit(ConstantValue constant) {
(_initialConstants ??= []).add(constant);
return init();
}
@override
bool get hasRead => reads.isNotEmpty;
@override
EnumSet<MemberUse> read(EnumSet<Access> accesses) {
bool alreadyHasRead = hasRead;
reads = reads.union(potentialReads.intersection(accesses));
potentialReads = potentialReads.setMinus(accesses);
if (alreadyHasRead) {
return MemberUses.none;
}
final removed = _pendingUse.intersection(MemberUses.normalOnly);
_pendingUse = _pendingUse.setMinus(MemberUses.normalOnly);
return removed;
}
@override
bool get hasWrite => writes.isNotEmpty;
@override
EnumSet<MemberUse> write(EnumSet<Access> accesses) {
bool alreadyHasWrite = hasWrite;
writes = writes.union(potentialWrites.intersection(accesses));
potentialWrites = potentialWrites.setMinus(accesses);
if (alreadyHasWrite) {
return MemberUses.none;
}
final removed = _pendingUse.intersection(MemberUses.normalOnly);
_pendingUse = _pendingUse.setMinus(MemberUses.normalOnly);
return removed;
}
@override
EnumSet<MemberUse> invoke(
EnumSet<Access> accesses,
CallStructure callStructure, {
bool forceAccesses = false,
}) {
// We use `hasRead` here instead of `hasInvoke` because fields only have
// 'normal use' (they cannot be closurized). This means that invoking an
// already read field does not result a new member use.
bool alreadyHasRead = hasRead;
reads = reads.union(potentialReads.intersection(Accesses.staticAccess));
potentialReads = potentialReads.setMinus(Accesses.staticAccess);
final removedPotentialInvokes = potentialInvokes.intersection(accesses);
potentialInvokes = potentialInvokes.setMinus(accesses);
if (forceAccesses) {
invokes = invokes.union(accesses);
} else {
invokes = invokes.union(removedPotentialInvokes);
}
if (alreadyHasRead) {
return MemberUses.none;
}
final removed = _pendingUse.intersection(MemberUses.normalOnly);
_pendingUse = _pendingUse.setMinus(MemberUses.normalOnly);
return removed;
}
@override
MemberUsage clone() {
return FieldUsage.cloned(
entity as FieldEntity,
_pendingUse,
potentialReads: potentialReads,
potentialWrites: potentialWrites,
potentialInvokes: potentialInvokes,
hasInit: hasInit,
reads: reads,
writes: writes,
invokes: invokes,
);
}
@override
String toString() =>
'FieldUsage($entity,hasInit=$hasInit,'
'reads=${reads.iterable(Access.values)},'
'writes=${writes.iterable(Access.values)},'
'invokes=${invokes.iterable(Access.values)},'
'potentialReads=${potentialReads.iterable(Access.values)},'
'potentialWrites=${potentialWrites.iterable(Access.values)},'
'potentialInvokes=${potentialInvokes.iterable(Access.values)},'
'pendingUse=${_pendingUse.iterable(MemberUse.values)},'
'initialConstants=${initialConstants.map((c) => c.toStructuredText(null))})';
}
/// Member usage tracking for a constructor or method.
class MethodUsage extends MemberUsage {
@override
EnumSet<Access> potentialReads;
@override
EnumSet<Access> potentialInvokes;
@override
EnumSet<Access> reads;
@override
EnumSet<Access> invokes;
final ParameterUsage parameterUsage;
MethodUsage.cloned(
super.function,
this.parameterUsage,
super.pendingUse, {
required this.potentialReads,
required this.reads,
required this.potentialInvokes,
required this.invokes,
}) : super.cloned();
MethodUsage(
FunctionEntity super.function, {
required this.potentialReads,
required this.potentialInvokes,
}) : reads = EnumSet.empty(),
invokes = EnumSet.empty(),
parameterUsage = ParameterUsage(function.parameterStructure),
super.internal();
@override
bool get hasInvoke => invokes.isNotEmpty && parameterUsage.hasInvoke;
@override
EnumSet<MemberUse> get _originalUse =>
entity.isInstanceMember ? MemberUses.allInstance : MemberUses.allStatic;
@override
EnumSet<MemberUse> read(EnumSet<Access> accesses) {
bool alreadyHasInvoke = hasInvoke;
bool alreadyHasRead = hasRead;
reads = reads.union(potentialReads.intersection(accesses));
potentialReads = potentialReads.setMinus(accesses);
invokes = invokes.union(
potentialInvokes.intersection(Accesses.dynamicAccess),
);
potentialInvokes = potentialInvokes.setMinus(Accesses.dynamicAccess);
parameterUsage.fullyUse();
if (alreadyHasInvoke) {
if (alreadyHasRead) {
return MemberUses.none;
}
final memberUses = entity.isInstanceMember
? MemberUses.closurizeInstanceOnly
: MemberUses.closurizeStaticOnly;
final removed = _pendingUse.intersection(memberUses);
_pendingUse = _pendingUse.setMinus(memberUses);
return removed;
} else if (alreadyHasRead) {
final removed = _pendingUse.intersection(MemberUses.normalOnly);
_pendingUse = _pendingUse.setMinus(MemberUses.normalOnly);
return removed;
} else {
final memberUses = entity.isInstanceMember
? MemberUses.allInstance
: MemberUses.allStatic;
final removed = _pendingUse.intersection(memberUses);
_pendingUse = _pendingUse.setMinus(memberUses);
return removed;
}
}
@override
EnumSet<MemberUse> invoke(
EnumSet<Access> accesses,
CallStructure callStructure, {
bool forceAccesses = false,
}) {
bool alreadyHasInvoke = hasInvoke;
parameterUsage.invoke(callStructure);
final removedPotentialInvokes = potentialInvokes.intersection(accesses);
potentialInvokes = potentialInvokes.setMinus(accesses);
if (forceAccesses) {
invokes = invokes.union(accesses);
} else {
invokes = invokes.union(removedPotentialInvokes);
}
if (alreadyHasInvoke) {
return MemberUses.none;
} else {
final memberUses = hasRead ? MemberUses.none : MemberUses.normalOnly;
final removed = _pendingUse.intersection(memberUses);
_pendingUse = _pendingUse.setMinus(memberUses);
return removed;
}
}
@override
ParameterStructure? get invokedParameters => parameterUsage.invokedParameters;
@override
bool get hasPendingDynamicInvoke =>
potentialInvokes.contains(Access.dynamicAccess) ||
(invokes.contains(Access.dynamicAccess) && !parameterUsage.isFullyUsed);
@override
MemberUsage clone() {
return MethodUsage.cloned(
entity as FunctionEntity,
parameterUsage.clone(),
_pendingUse,
reads: reads,
potentialReads: potentialReads,
invokes: invokes,
potentialInvokes: potentialInvokes,
);
}
@override
String toString() =>
'MethodUsage($entity,'
'reads=${reads.iterable(Access.values)},'
'invokes=${invokes.iterable(Access.values)},'
'parameterUsage=$parameterUsage,'
'potentialReads=${potentialReads.iterable(Access.values)},'
'potentialInvokes=${potentialInvokes.iterable(Access.values)},'
'pendingUse=${_pendingUse.iterable(MemberUse.values)})';
}
/// Enum class for the possible kind of use of [MemberEntity] objects.
enum MemberUse {
/// Read or write of a field, or invocation of a method.
normal,
/// Tear-off of an instance method.
closurizeInstance,
/// Tear-off of a static method.
closurizeStatic,
}
/// Common [EnumSet]s used for [MemberUse].
class MemberUses {
static const EnumSet<MemberUse> none = EnumSet.fromRawBits(0);
static const EnumSet<MemberUse> normalOnly = EnumSet.fromRawBits(1);
static const EnumSet<MemberUse> closurizeInstanceOnly = EnumSet.fromRawBits(
2,
);
static const EnumSet<MemberUse> closurizeStaticOnly = EnumSet.fromRawBits(4);
static const EnumSet<MemberUse> allInstance = EnumSet.fromRawBits(3);
static const EnumSet<MemberUse> allStatic = EnumSet.fromRawBits(5);
}
typedef MemberUsedCallback =
void Function(MemberEntity member, EnumSet<MemberUse> useSet);
/// Registry for the observed use of a class [entity] in the open world.
// TODO(johnniwinther): Merge this with [InstantiationInfo].
class ClassUsage extends AbstractUsage<ClassUse> {
bool isInstantiated = false;
bool isImplemented = false;
final ClassEntity cls;
ClassUsage(this.cls) : super();
EnumSet<ClassUse> instantiate() {
if (isInstantiated) {
return ClassUses.none;
}
isInstantiated = true;
final removed = _pendingUse.intersection(ClassUses.instantiatedOnly);
_pendingUse = _pendingUse.setMinus(ClassUses.instantiatedOnly);
return removed;
}
EnumSet<ClassUse> implement() {
if (isImplemented) {
return ClassUses.none;
}
isImplemented = true;
final removed = _pendingUse.intersection(ClassUses.implementedOnly);
_pendingUse = _pendingUse.setMinus(ClassUses.implementedOnly);
return removed;
}
@override
EnumSet<ClassUse> get _originalUse => ClassUses.all;
@override
String toString() => '$cls:${_appliedUse.iterable(ClassUse.values)}';
}
/// Enum class for the possible kind of use of [ClassEntity] objects.
enum ClassUse { instantiated, implemented }
/// Common [EnumSet]s used for [ClassUse].
class ClassUses {
static const EnumSet<ClassUse> none = EnumSet.fromRawBits(0);
static const EnumSet<ClassUse> instantiatedOnly = EnumSet.fromRawBits(1);
static const EnumSet<ClassUse> implementedOnly = EnumSet.fromRawBits(2);
static const EnumSet<ClassUse> all = EnumSet.fromRawBits(3);
}
typedef ClassUsedCallback =
void Function(ClassEntity cls, EnumSet<ClassUse> useSet);
/// Object used for tracking parameter use in constructor and method
/// invocations.
class ParameterUsage {
/// The original parameter structure of the method or constructor.
final ParameterStructure _parameterStructure;
/// `true` if the method or constructor has at least one invocation.
bool _hasInvoke = false;
/// The maximum number of (optional) positional parameters provided in
/// invocations of the method or constructor.
///
/// If all positional parameters having been provided this is set to `null`.
int? _providedPositionalParameters;
/// `true` if all type parameters have been provided in at least one
/// invocation of the method or constructor.
late bool _areAllTypeParametersProvided;
/// The set of named parameters that have not yet been provided in any
/// invocation of the method or constructor.
///
/// If all named parameters have been provided this is set to `null`.
Set<String>? _unprovidedNamedParameters;
ParameterUsage(this._parameterStructure) {
_areAllTypeParametersProvided = _parameterStructure.typeParameters == 0;
_providedPositionalParameters =
_parameterStructure.positionalParameters ==
_parameterStructure.requiredPositionalParameters
? null
: 0;
if (_parameterStructure.namedParameters.isNotEmpty) {
_unprovidedNamedParameters = Set<String>.from(
_parameterStructure.namedParameters,
);
}
}
ParameterUsage.cloned(
this._parameterStructure, {
required bool hasInvoke,
required int? providedPositionalParameters,
required bool areAllTypeParametersProvided,
required Set<String>? unprovidedNamedParameters,
}) : _hasInvoke = hasInvoke,
_providedPositionalParameters = providedPositionalParameters,
_areAllTypeParametersProvided = areAllTypeParametersProvided,
_unprovidedNamedParameters = unprovidedNamedParameters;
bool invoke(CallStructure callStructure) {
if (isFullyUsed) return false;
_hasInvoke = true;
bool changed = false;
if (_providedPositionalParameters != null) {
int newProvidedPositionalParameters = math.max(
_providedPositionalParameters!,
callStructure.positionalArgumentCount,
);
changed |=
newProvidedPositionalParameters != _providedPositionalParameters;
_providedPositionalParameters = newProvidedPositionalParameters;
if (_providedPositionalParameters! >=
_parameterStructure.positionalParameters) {
_providedPositionalParameters = null;
}
}
if (_unprovidedNamedParameters != null &&
callStructure.namedArguments.isNotEmpty) {
int providedNamedParametersCount = _unprovidedNamedParameters!.length;
_unprovidedNamedParameters!.removeAll(callStructure.namedArguments);
changed |=
providedNamedParametersCount != _unprovidedNamedParameters!.length;
if (_unprovidedNamedParameters!.isEmpty) {
_unprovidedNamedParameters = null;
}
}
if (!_areAllTypeParametersProvided && callStructure.typeArgumentCount > 0) {
_areAllTypeParametersProvided = true;
changed = true;
}
return changed;
}
bool get hasInvoke => _hasInvoke;
bool get isFullyUsed =>
_hasInvoke &&
_providedPositionalParameters == null &&
_unprovidedNamedParameters == null &&
_areAllTypeParametersProvided;
void fullyUse() {
_hasInvoke = true;
_providedPositionalParameters = null;
_unprovidedNamedParameters = null;
_areAllTypeParametersProvided = true;
}
ParameterStructure? get invokedParameters {
if (!_hasInvoke) return null;
if (isFullyUsed) return _parameterStructure;
return ParameterStructure(
_parameterStructure.requiredPositionalParameters,
_providedPositionalParameters ?? _parameterStructure.positionalParameters,
_unprovidedNamedParameters == null
? _parameterStructure.namedParameters
: _parameterStructure.namedParameters
.where((n) => !_unprovidedNamedParameters!.contains(n))
.toList(),
_parameterStructure.requiredNamedParameters,
_areAllTypeParametersProvided ? _parameterStructure.typeParameters : 0,
);
}
ParameterUsage clone() {
return ParameterUsage.cloned(
_parameterStructure,
hasInvoke: _hasInvoke,
providedPositionalParameters: _providedPositionalParameters,
areAllTypeParametersProvided: _areAllTypeParametersProvided,
unprovidedNamedParameters: _unprovidedNamedParameters?.toSet(),
);
}
@override
String toString() {
return 'ParameterUsage('
'_hasInvoke=$_hasInvoke,'
'_providedPositionalParameters=$_providedPositionalParameters,'
'_areAllTypeParametersProvided=$_areAllTypeParametersProvided,'
'_unprovidedNamedParameters=$_unprovidedNamedParameters)';
}
}
/// Enum for member access kinds use in [MemberUsage] computation during
/// resolution or codegen enqueueing.
enum Access {
/// Statically bound access of a member.
staticAccess,
/// Dynamically bound access of a member.
dynamicAccess,
/// Direct access of a super class member.
superAccess,
}
/// Access sets used for registration of member usage.
class Accesses {
/// Statically bound access of a member.
static const EnumSet<Access> staticAccess = EnumSet.fromRawBits(1);
/// Dynamically bound access of a member. This implies the statically bound
/// access of the member.
static const EnumSet<Access> dynamicAccess = EnumSet.fromRawBits(3);
/// Direct access of a super class member. This implies the statically bound
/// access of the member.
static const EnumSet<Access> superAccess = EnumSet.fromRawBits(5);
}
/// The accesses of a member collected during closed world computation.
class MemberAccess {
static const String tag = 'MemberAccess';
final EnumSet<Access> reads;
final EnumSet<Access> writes;
final EnumSet<Access> invokes;
MemberAccess(this.reads, this.writes, this.invokes);
factory MemberAccess.readFromDataSource(DataSourceReader source) {
source.begin(tag);
EnumSet<Access> reads = EnumSet.fromRawBits(source.readInt());
EnumSet<Access> writes = EnumSet.fromRawBits(source.readInt());
EnumSet<Access> invokes = EnumSet.fromRawBits(source.readInt());
source.end(tag);
return MemberAccess(reads, writes, invokes);
}
void writeToDataSink(DataSinkWriter sink) {
sink.begin(tag);
sink.writeInt(reads.mask.bits);
sink.writeInt(writes.mask.bits);
sink.writeInt(invokes.mask.bits);
sink.end(tag);
}
}