blob: 6b166f8ebc103793d2f0c89747960792998d9b84 [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 '../common.dart';
import '../elements/entities.dart';
import '../js_model/elements.dart' show JSignatureMethod;
import '../util/enumset.dart';
abstract class AbstractUsage<T> {
final EnumSet<T> _pendingUse = new EnumSet<T>();
AbstractUsage() {
_pendingUse.addAll(_originalUse);
}
/// Returns the possible uses of [entity] that have not yet been registered.
EnumSet<T> get pendingUse => _pendingUse;
/// 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> {
final MemberEntity entity;
MemberUsage.internal(this.entity);
factory MemberUsage(MemberEntity member, {bool isNative: false}) {
if (member.isField) {
if (member.isAssignable) {
return new FieldUsage(member, isNative: isNative);
} else {
return new FinalFieldUsage(member, isNative: isNative);
}
} else if (member.isGetter) {
return new GetterUsage(member);
} else if (member.isSetter) {
return new SetterUsage(member);
} else if (member.isConstructor) {
return new ConstructorUsage(member);
} else {
assert(member.isFunction, failedAt(member, "Unexpected member: $member"));
return new FunctionUsage(member);
}
}
/// `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 => false;
/// `true` if a value has been written to [entity].
bool get hasWrite => false;
/// `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 => false;
/// `true` if [entity] has been used in all the ways possible.
bool get fullyUsed;
/// 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 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() => MemberUses.NONE;
/// Registers a write of a value to [entity] and returns the new [MemberUse]s
/// that it caused.
EnumSet<MemberUse> write() => 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() => MemberUses.NONE;
/// Registers all possible uses of [entity] and returns the new [MemberUse]s
/// that it caused.
EnumSet<MemberUse> fullyUse() => MemberUses.NONE;
@override
EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
int get hashCode => entity.hashCode;
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! MemberUsage) return false;
return entity == other.entity;
}
String toString() => '$entity:${appliedUse.iterable(MemberUse.values)}';
}
class FieldUsage extends MemberUsage {
bool hasRead = false;
bool hasWrite = false;
FieldUsage(FieldEntity field, {bool isNative: false})
: super.internal(field) {
if (!isNative) {
// All field initializers must be resolved as they could
// have an observable side-effect (and cannot be tree-shaken
// away).
fullyUse();
}
}
@override
bool get fullyUsed => hasRead && hasWrite;
@override
EnumSet<MemberUse> init() => read();
@override
EnumSet<MemberUse> read() {
if (fullyUsed) {
return MemberUses.NONE;
}
hasRead = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> write() {
if (fullyUsed) {
return MemberUses.NONE;
}
hasWrite = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> invoke() => read();
@override
EnumSet<MemberUse> fullyUse() {
if (fullyUsed) {
return MemberUses.NONE;
}
hasRead = hasWrite = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
}
class FinalFieldUsage extends MemberUsage {
bool hasRead = false;
FinalFieldUsage(FieldEntity field, {bool isNative: false})
: super.internal(field) {
if (!isNative) {
// All field initializers must be resolved as they could
// have an observable side-effect (and cannot be tree-shaken
// away).
read();
}
}
@override
bool get fullyUsed => hasRead;
@override
EnumSet<MemberUse> init() => read();
@override
EnumSet<MemberUse> read() {
if (hasRead) {
return MemberUses.NONE;
}
hasRead = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> invoke() => read();
@override
EnumSet<MemberUse> fullyUse() => read();
}
class FunctionUsage extends MemberUsage {
bool hasInvoke = false;
bool hasRead = false;
FunctionUsage(FunctionEntity function) : super.internal(function) {
if (function is JSignatureMethod) {
// We mark signature methods as "always used" to prevent them from being
// optimized away.
// TODO(johnniwinther): Make this a part of the regular enqueueing.
invoke();
}
}
EnumSet<MemberUse> get _originalUse =>
entity.isInstanceMember ? MemberUses.ALL_INSTANCE : MemberUses.ALL_STATIC;
@override
EnumSet<MemberUse> read() => fullyUse();
@override
EnumSet<MemberUse> invoke() {
if (hasInvoke) {
return MemberUses.NONE;
}
hasInvoke = true;
return _pendingUse
.removeAll(hasRead ? MemberUses.NONE : MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> fullyUse() {
if (hasInvoke) {
if (hasRead) {
return MemberUses.NONE;
}
hasRead = true;
return _pendingUse.removeAll(entity.isInstanceMember
? MemberUses.CLOSURIZE_INSTANCE_ONLY
: MemberUses.CLOSURIZE_STATIC_ONLY);
} else if (hasRead) {
hasInvoke = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
} else {
hasRead = hasInvoke = true;
return _pendingUse.removeAll(entity.isInstanceMember
? MemberUses.ALL_INSTANCE
: MemberUses.ALL_STATIC);
}
}
@override
bool get fullyUsed => hasInvoke && hasRead;
}
class GetterUsage extends MemberUsage {
bool hasRead = false;
GetterUsage(FunctionEntity getter) : super.internal(getter);
@override
bool get fullyUsed => hasRead;
@override
EnumSet<MemberUse> read() {
if (hasRead) {
return MemberUses.NONE;
}
hasRead = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> invoke() => read();
@override
EnumSet<MemberUse> fullyUse() => read();
}
class SetterUsage extends MemberUsage {
bool hasWrite = false;
SetterUsage(FunctionEntity setter) : super.internal(setter);
@override
bool get fullyUsed => hasWrite;
@override
EnumSet<MemberUse> write() {
if (hasWrite) {
return MemberUses.NONE;
}
hasWrite = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> fullyUse() => write();
}
class ConstructorUsage extends MemberUsage {
bool hasInvoke = false;
ConstructorUsage(ConstructorEntity constructor) : super.internal(constructor);
EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
@override
EnumSet<MemberUse> invoke() {
if (hasInvoke) {
return MemberUses.NONE;
}
hasInvoke = true;
return _pendingUse
.removeAll(hasRead ? MemberUses.NONE : MemberUses.NORMAL_ONLY);
}
@override
EnumSet<MemberUse> fullyUse() => invoke();
@override
bool get fullyUsed => hasInvoke;
}
/// Enum class for the possible kind of use of [MemberEntity] objects.
enum MemberUse { NORMAL, CLOSURIZE_INSTANCE, 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);
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;
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);
// TODO(johnniwinther): Merge this with [MemberUsage].
abstract class StaticMemberUsage extends AbstractUsage<MemberUse>
implements MemberUsage {
final MemberEntity entity;
bool hasNormalUse = false;
bool get hasClosurization => false;
StaticMemberUsage.internal(this.entity);
EnumSet<MemberUse> normalUse() {
if (hasNormalUse) {
return MemberUses.NONE;
}
hasNormalUse = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
}
EnumSet<MemberUse> tearOff();
EnumSet<MemberUse> init() => normalUse();
EnumSet<MemberUse> read() => tearOff();
EnumSet<MemberUse> write() => normalUse();
EnumSet<MemberUse> invoke() => normalUse();
EnumSet<MemberUse> fullyUse() => normalUse();
@override
EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
String toString() => '$entity:${appliedUse.iterable(MemberUse.values)}';
}
class GeneralStaticMemberUsage extends StaticMemberUsage {
GeneralStaticMemberUsage(MemberEntity entity) : super.internal(entity);
EnumSet<MemberUse> tearOff() => normalUse();
@override
bool get fullyUsed => hasNormalUse;
@override
bool get hasInvoke => hasNormalUse;
@override
bool get hasWrite => hasNormalUse;
@override
bool get hasRead => hasNormalUse;
}
class StaticFunctionUsage extends StaticMemberUsage {
bool hasClosurization = false;
StaticFunctionUsage(MemberEntity entity) : super.internal(entity);
EnumSet<MemberUse> tearOff() {
if (hasClosurization) {
return MemberUses.NONE;
}
hasNormalUse = hasClosurization = true;
return _pendingUse.removeAll(MemberUses.ALL_STATIC);
}
@override
EnumSet<MemberUse> get _originalUse => MemberUses.ALL_STATIC;
@override
bool get fullyUsed => hasNormalUse && hasClosurization;
@override
bool get hasInvoke => hasNormalUse;
@override
bool get hasWrite => hasNormalUse;
@override
bool get hasRead => hasClosurization;
}