blob: a322bc3da6d5a18f8f319d0835d11f79dc058888 [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';
import '../serialization/serialization.dart';
import '../util/enumset.dart';
import 'call_structure.dart';
abstract class AbstractUsage<T> {
final EnumSet<T> _pendingUse;
AbstractUsage.cloned(this._pendingUse);
AbstractUsage() : this._pendingUse = new EnumSet<T>() {
_pendingUse.addAll(_originalUse);
}
/// Returns the uses of [entity] that have been registered.
EnumSet<T> get _appliedUse => _originalUse.minus(_pendingUse);
EnumSet<T> 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<T> other) {
if (identical(this, other)) return true;
return _originalUse.value == other._originalUse.value &&
_pendingUse.value == other._pendingUse.value;
}
}
/// Registry for the observed use of a member [entity] in the open world.
abstract class MemberUsage extends AbstractUsage<MemberUse> {
/// Constant empty access set used as the potential access set for impossible
/// accesses, for instance writing to a final field or invoking a setter.
static const EnumSet<Access> emptySet = const EnumSet.fixed(0);
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) {
if (original.isEmpty) return emptySet;
return original.clone();
}
if (member.isTopLevel || member.isStatic || member.isConstructor) {
// TODO(johnniwinther): Track super constructor invocations?
return new EnumSet.fromValues([Access.staticAccess]);
} else if (member.isInstanceMember) {
return new EnumSet.fromValues(Access.values);
} else {
assert(member is JRecordField, "Unexpected member: $member");
return new EnumSet();
}
}
/// 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.isField) {
if (member.isAssignable) {
return new FieldUsage(member,
potentialReads: createPotentialReads(),
potentialWrites: createPotentialWrites(),
potentialInvokes: createPotentialInvokes());
} else {
return new FieldUsage(member,
potentialReads: createPotentialReads(),
potentialWrites: emptySet,
potentialInvokes: createPotentialInvokes());
}
} else if (member.isGetter) {
return new PropertyUsage(member,
potentialReads: createPotentialReads(),
potentialWrites: emptySet,
potentialInvokes: createPotentialInvokes());
} else if (member.isSetter) {
return new PropertyUsage(member,
potentialReads: emptySet,
potentialWrites: createPotentialWrites(),
potentialInvokes: emptySet);
} else if (member.isConstructor) {
return new MethodUsage(member,
potentialReads: emptySet, potentialInvokes: createPotentialInvokes());
} else {
assert(member is FunctionEntity,
failedAt(member, "Unexpected member: $member"));
return new MethodUsage(member,
potentialReads: createPotentialReads(),
potentialInvokes: createPotentialInvokes());
}
}
/// `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.fixed(0);
/// The set of registered read accesses to this member.
EnumSet<Access> get reads => const EnumSet.fixed(0);
/// `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.fixed(0);
/// The set of registered write accesses to this member.
EnumSet<Access> get writes => const EnumSet.fixed(0);
/// `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.fixed(0);
/// The set of registered invocation accesses to this member.
EnumSet<Access> get invokes => const EnumSet.fixed(0);
/// 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.
EnumSet<MemberUse> invoke(
EnumSet<Access> accesses, CallStructure callStructure) =>
MemberUses.NONE;
@override
EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
@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
final EnumSet<Access> potentialReads;
@override
final EnumSet<Access> potentialWrites;
@override
final EnumSet<Access> potentialInvokes;
@override
final EnumSet<Access> reads;
@override
final EnumSet<Access> writes;
@override
final EnumSet<Access> invokes;
PropertyUsage.cloned(MemberEntity member, EnumSet<MemberUse> pendingUse,
{this.potentialReads,
this.potentialWrites,
this.potentialInvokes,
this.reads,
this.writes,
this.invokes})
: assert(potentialReads != null),
assert(potentialWrites != null),
assert(potentialInvokes != null),
assert(reads != null),
assert(writes != null),
assert(invokes != null),
super.cloned(member, pendingUse);
PropertyUsage(MemberEntity member,
{this.potentialReads, this.potentialWrites, this.potentialInvokes})
: reads = new EnumSet(),
writes = new EnumSet(),
invokes = new EnumSet(),
assert(potentialReads != null),
assert(potentialWrites != null),
assert(potentialInvokes != null),
super.internal(member);
@override
EnumSet<MemberUse> read(EnumSet<Access> accesses) {
bool alreadyHasRead = hasRead;
reads.addAll(potentialReads.removeAll(accesses));
if (alreadyHasRead) {
return MemberUses.NONE;
}
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> write(EnumSet<Access> accesses) {
bool alreadyHasWrite = hasWrite;
writes.addAll(potentialWrites.removeAll(accesses));
if (alreadyHasWrite) {
return MemberUses.NONE;
}
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> invoke(
EnumSet<Access> accesses, CallStructure callStructure) {
// 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.addAll(potentialReads.removeAll(Accesses.staticAccess));
invokes.addAll(potentialInvokes.removeAll(accesses));
if (alreadyHasRead) {
return MemberUses.NONE;
}
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
MemberUsage clone() {
return new PropertyUsage.cloned(entity, _pendingUse.clone(),
potentialReads: potentialReads.clone(),
potentialWrites: potentialWrites.clone(),
potentialInvokes: potentialInvokes.clone(),
reads: reads.clone(),
writes: writes.clone(),
invokes: invokes.clone());
}
@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())})';
}
/// Member usage tracking for a field.
class FieldUsage extends MemberUsage {
@override
bool hasInit;
@override
final EnumSet<Access> potentialReads;
@override
final EnumSet<Access> potentialWrites;
@override
final EnumSet<Access> potentialInvokes;
@override
final EnumSet<Access> reads;
@override
final EnumSet<Access> invokes;
@override
final EnumSet<Access> writes;
List<ConstantValue> _initialConstants;
FieldUsage.cloned(FieldEntity field, EnumSet<MemberUse> pendingUse,
{this.potentialReads,
this.potentialWrites,
this.potentialInvokes,
this.hasInit,
this.reads,
this.writes,
this.invokes})
: assert(potentialReads != null),
assert(potentialWrites != null),
assert(potentialInvokes != null),
assert(reads != null),
assert(writes != null),
assert(invokes != null),
super.cloned(field, pendingUse);
FieldUsage(FieldEntity field,
{this.potentialReads, this.potentialWrites, this.potentialInvokes})
: hasInit = false,
reads = new EnumSet(),
writes = new EnumSet(),
invokes = new EnumSet(),
assert(potentialReads != null),
assert(potentialWrites != null),
assert(potentialInvokes != null),
super.internal(field);
@override
Iterable<ConstantValue> get initialConstants => _initialConstants ?? const [];
@override
EnumSet<MemberUse> init() {
if (hasInit) {
return MemberUses.NONE;
}
hasInit = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> constantInit(ConstantValue constant) {
_initialConstants ??= [];
_initialConstants.add(constant);
return init();
}
@override
bool get hasRead => reads.isNotEmpty;
@override
EnumSet<MemberUse> read(EnumSet<Access> accesses) {
bool alreadyHasRead = hasRead;
reads.addAll(potentialReads.removeAll(accesses));
if (alreadyHasRead) {
return MemberUses.NONE;
}
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
bool get hasWrite => writes.isNotEmpty;
@override
EnumSet<MemberUse> write(EnumSet<Access> accesses) {
bool alreadyHasWrite = hasWrite;
writes.addAll(potentialWrites.removeAll(accesses));
if (alreadyHasWrite) {
return MemberUses.NONE;
}
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> invoke(
EnumSet<Access> accesses, CallStructure callStructure) {
// 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.addAll(potentialReads.removeAll(Accesses.staticAccess));
invokes.addAll(potentialInvokes.removeAll(accesses));
if (alreadyHasRead) {
return MemberUses.NONE;
}
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
MemberUsage clone() {
return new FieldUsage.cloned(entity, _pendingUse.clone(),
potentialReads: potentialReads.clone(),
potentialWrites: potentialWrites.clone(),
potentialInvokes: potentialInvokes.clone(),
hasInit: hasInit,
reads: reads.clone(),
writes: writes.clone(),
invokes: invokes.clone());
}
@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())})';
}
/// Member usage tracking for a constructor or method.
class MethodUsage extends MemberUsage {
@override
final EnumSet<Access> potentialReads;
@override
final EnumSet<Access> potentialInvokes;
@override
final EnumSet<Access> reads;
@override
final EnumSet<Access> invokes;
final ParameterUsage parameterUsage;
MethodUsage.cloned(FunctionEntity function, this.parameterUsage,
EnumSet<MemberUse> pendingUse,
{this.potentialReads, this.reads, this.potentialInvokes, this.invokes})
: assert(potentialReads != null),
assert(potentialInvokes != null),
assert(reads != null),
assert(invokes != null),
super.cloned(function, pendingUse);
MethodUsage(FunctionEntity function,
{this.potentialReads, this.potentialInvokes})
: reads = new EnumSet(),
invokes = new EnumSet(),
parameterUsage = new ParameterUsage(function.parameterStructure),
assert(potentialReads != null),
assert(potentialInvokes != null),
super.internal(function);
@override
bool get hasInvoke => invokes.isNotEmpty && parameterUsage.hasInvoke;
@override
EnumSet<MemberUse> get _originalUse =>
entity.isInstanceMember ? MemberUses.ALL_INSTANCE : MemberUses.ALL_STATIC;
@override
EnumSet<MemberUse> read(EnumSet<Access> accesses) {
bool alreadyHasInvoke = hasInvoke;
bool alreadyHasRead = hasRead;
reads.addAll(potentialReads.removeAll(accesses));
invokes.addAll(potentialInvokes.removeAll(Accesses.dynamicAccess));
parameterUsage.fullyUse();
if (alreadyHasInvoke) {
if (alreadyHasRead) {
return MemberUses.NONE;
}
return _pendingUse.removeAll(entity.isInstanceMember
? MemberUses.CLOSURIZE_INSTANCE_ONLY
: MemberUses.CLOSURIZE_STATIC_ONLY);
} else if (alreadyHasRead) {
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
} else {
return _pendingUse.removeAll(entity.isInstanceMember
? MemberUses.ALL_INSTANCE
: MemberUses.ALL_STATIC);
}
}
@override
EnumSet<MemberUse> invoke(
EnumSet<Access> accesses, CallStructure callStructure) {
bool alreadyHasInvoke = hasInvoke;
parameterUsage.invoke(callStructure);
invokes.addAll(potentialInvokes.removeAll(accesses));
if (alreadyHasInvoke) {
return MemberUses.NONE;
} else {
return _pendingUse
.removeAll(hasRead ? MemberUses.NONE : MemberUses.NORMAL_ONLY);
}
}
@override
ParameterStructure get invokedParameters => parameterUsage.invokedParameters;
@override
bool get hasPendingDynamicInvoke =>
potentialInvokes.contains(Access.dynamicAccess) ||
(invokes.contains(Access.dynamicAccess) && !parameterUsage.isFullyUsed);
@override
MemberUsage clone() {
return new MethodUsage.cloned(
entity, parameterUsage.clone(), _pendingUse.clone(),
reads: reads.clone(),
potentialReads: potentialReads.clone(),
invokes: invokes.clone(),
potentialInvokes: potentialInvokes.clone());
}
@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.
CLOSURIZE_INSTANCE,
/// Tear-off of a static method.
CLOSURIZE_STATIC,
}
/// Common [EnumSet]s used for [MemberUse].
class MemberUses {
static const EnumSet<MemberUse> NONE = const EnumSet<MemberUse>.fixed(0);
static const EnumSet<MemberUse> NORMAL_ONLY =
const EnumSet<MemberUse>.fixed(1);
static const EnumSet<MemberUse> CLOSURIZE_INSTANCE_ONLY =
const EnumSet<MemberUse>.fixed(2);
static const EnumSet<MemberUse> CLOSURIZE_STATIC_ONLY =
const EnumSet<MemberUse>.fixed(4);
static const EnumSet<MemberUse> ALL_INSTANCE =
const EnumSet<MemberUse>.fixed(3);
static const EnumSet<MemberUse> ALL_STATIC =
const EnumSet<MemberUse>.fixed(5);
}
typedef void MemberUsedCallback(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;
return _pendingUse.removeAll(ClassUses.INSTANTIATED_ONLY);
}
EnumSet<ClassUse> implement() {
if (isImplemented) {
return ClassUses.NONE;
}
isImplemented = true;
return _pendingUse.removeAll(ClassUses.IMPLEMENTED_ONLY);
}
@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 = const EnumSet<ClassUse>.fixed(0);
static const EnumSet<ClassUse> INSTANTIATED_ONLY =
const EnumSet<ClassUse>.fixed(1);
static const EnumSet<ClassUse> IMPLEMENTED_ONLY =
const EnumSet<ClassUse>.fixed(2);
static const EnumSet<ClassUse> ALL = const EnumSet<ClassUse>.fixed(3);
}
typedef void ClassUsedCallback(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;
/// 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.
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) {
_hasInvoke = false;
_areAllTypeParametersProvided = _parameterStructure.typeParameters == 0;
_providedPositionalParameters = _parameterStructure.positionalParameters ==
_parameterStructure.requiredParameters
? null
: 0;
if (!_parameterStructure.namedParameters.isEmpty) {
_unprovidedNamedParameters =
new Set<String>.from(_parameterStructure.namedParameters);
}
}
ParameterUsage.cloned(this._parameterStructure,
{bool hasInvoke,
int providedPositionalParameters,
bool areAllTypeParametersProvided,
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 new ParameterStructure(
_parameterStructure.requiredParameters,
_providedPositionalParameters ??
_parameterStructure.positionalParameters,
_unprovidedNamedParameters == null
? _parameterStructure.namedParameters
: _parameterStructure.namedParameters
.where((n) => !_unprovidedNamedParameters.contains(n))
.toList(),
_areAllTypeParametersProvided ? _parameterStructure.typeParameters : 0);
}
ParameterUsage clone() {
return new 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 = const EnumSet<Access>.fixed(1);
/// Dynamically bound access of a member. This implies the statically bound
/// access of the member.
static const EnumSet<Access> dynamicAccess = const EnumSet<Access>.fixed(3);
/// Direct access of a super class member. This implies the statically bound
/// access of the member.
static const EnumSet<Access> superAccess = const EnumSet<Access>.fixed(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(DataSource source) {
source.begin(tag);
EnumSet<Access> reads = new EnumSet.fixed(source.readInt());
EnumSet<Access> writes = new EnumSet.fixed(source.readInt());
EnumSet<Access> invokes = new EnumSet.fixed(source.readInt());
source.end(tag);
return new MemberAccess(reads, writes, invokes);
}
void writeToDataSink(DataSink sink) {
sink.begin(tag);
sink.writeInt(reads.value);
sink.writeInt(writes.value);
sink.writeInt(invokes.value);
sink.end(tag);
}
}