blob: 05cae632087b4e55bb370f00436ae9f815d95e30 [file] [log] [blame]
// 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.
library universe;
import 'dart:collection';
import '../cache_strategy.dart';
import '../common.dart';
import '../common/backend_api.dart' show Backend;
import '../common/names.dart' show Identifiers;
import '../common/resolution.dart' show Resolution;
import '../compiler.dart' show Compiler;
import '../core_types.dart';
import '../dart_types.dart';
import '../elements/elements.dart';
import '../elements/entities.dart';
import '../universe/class_set.dart';
import '../universe/function_set.dart' show FunctionSetBuilder;
import '../util/enumset.dart';
import '../util/util.dart';
import '../world.dart' show World, ClosedWorld, ClosedWorldImpl, OpenWorld;
import 'selector.dart' show Selector;
import 'use.dart' show DynamicUse, DynamicUseKind, StaticUse, StaticUseKind;
/// The known constraint on receiver for a dynamic call site.
///
/// This can for instance be used to constrain this dynamic call to `foo` to
/// 'receivers of the exact instance `Bar`':
///
/// class Bar {
/// void foo() {}
/// }
/// main() => new Bar().foo();
///
abstract class ReceiverConstraint {
/// Returns whether [element] is a potential target when being
/// invoked on a receiver with this constraint. [selector] is used to ensure
/// library privacy is taken into account.
bool canHit(Element element, Selector selector, World world);
/// Returns whether this [TypeMask] applied to [selector] can hit a
/// [noSuchMethod].
bool needsNoSuchMethodHandling(Selector selector, World world);
}
/// The combined constraints on receivers all the dynamic call sites of the same
/// selector.
///
/// For instance for these calls
///
/// class A {
/// foo(a, b) {}
/// }
/// class B {
/// foo(a, b) {}
/// }
/// class C {
/// foo(a, b) {}
/// }
/// new A().foo(a, b);
/// new B().foo(0, 42);
///
/// the selector constraints for dynamic calls to 'foo' with two positional
/// arguments could be 'receiver of exact instance `A` or `B`'.
abstract class SelectorConstraints {
/// Returns `true` if [selector] applies to [element] under these constraints
/// given the closed [world].
///
/// Consider for instance in this world:
///
/// class A {
/// foo(a, b) {}
/// }
/// class B {
/// foo(a, b) {}
/// }
/// new A().foo(a, b);
///
/// Ideally the selector constraints for calls `foo` with two positional
/// arguments apply to `A.foo` but `B.foo`.
bool applies(Element element, Selector selector, World world);
/// Returns `true` if at least one of the receivers matching these constraints
/// in the closed [world] have no implementation matching [selector].
///
/// For instance for this code snippet
///
/// class A {}
/// class B { foo() {} }
/// m(b) => (b ? new A() : new B()).foo();
///
/// the potential receiver `new A()` has no implementation of `foo` and thus
/// needs to handle the call through its `noSuchMethod` handler.
bool needsNoSuchMethodHandling(Selector selector, World world);
}
/// A mutable [SelectorConstraints] used in [WorldBuilder].
abstract class UniverseSelectorConstraints extends SelectorConstraints {
/// Adds [constraint] to these selector constraints. Return `true` if the set
/// of potential receivers expanded due to the new constraint.
bool addReceiverConstraint(ReceiverConstraint constraint);
}
/// Strategy for computing the constraints on potential receivers of dynamic
/// call sites.
abstract class SelectorConstraintsStrategy {
/// Create a [UniverseSelectorConstraints] to represent the global receiver
/// constraints for dynamic call sites with [selector].
UniverseSelectorConstraints createSelectorConstraints(Selector selector);
}
class OpenWorldStrategy implements SelectorConstraintsStrategy {
const OpenWorldStrategy();
OpenWorldConstraints createSelectorConstraints(Selector selector) {
return new OpenWorldConstraints();
}
}
class OpenWorldConstraints extends UniverseSelectorConstraints {
bool isAll = false;
@override
bool applies(Element element, Selector selector, World world) => isAll;
@override
bool needsNoSuchMethodHandling(Selector selector, World world) => isAll;
@override
bool addReceiverConstraint(ReceiverConstraint constraint) {
if (isAll) return false;
isAll = true;
return true;
}
String toString() {
if (isAll) {
return '<all>';
} else {
return '<none>';
}
}
}
/// The [WorldBuilder] is an auxiliary class used in the process of computing
/// the [ClosedWorld].
// TODO(johnniwinther): Move common implementation to a [WorldBuilderBase] when
// universes and worlds have been unified.
abstract class WorldBuilder {
/// All directly instantiated classes, that is, classes with a generative
/// constructor that has been called directly and not only through a
/// super-call.
// TODO(johnniwinther): Improve semantic precision.
Iterable<ClassElement> get directlyInstantiatedClasses;
/// All types that are checked either through is, as or checked mode checks.
Iterable<DartType> get isChecks;
/// Registers that [type] is checked in this universe. The unaliased type is
/// returned.
DartType registerIsCheck(DartType type);
/// All directly instantiated types, that is, the types of the directly
/// instantiated classes.
// TODO(johnniwinther): Improve semantic precision.
Iterable<DartType> get instantiatedTypes;
}
abstract class ResolutionWorldBuilder implements WorldBuilder, OpenWorld {
/// Set of (live) local functions (closures) whose signatures reference type
/// variables.
///
/// A live function is one whose enclosing member function has been enqueued.
Iterable<Element> get closuresWithFreeTypeVariables;
/// Set of (live) `call` methods whose signatures reference type variables.
///
/// A live `call` method is one whose enclosing class has been instantiated.
Iterable<Element> get callMethodsWithFreeTypeVariables;
/// Set of all closures in the program. Used by the mirror tracking system
/// to find all live closure instances.
Iterable<LocalFunctionElement> get allClosures;
/// Set of methods in instantiated classes that are potentially closurized.
Iterable<Element> get closurizedMembers;
/// Returns `true` if [cls] is considered to be implemented by an
/// instantiated class, either directly, through subclasses or through
/// subtypes. The latter case only contains spurious information from
/// instantiations through factory constructors and mixins.
bool isImplemented(ClassElement cls);
/// Set of all fields that are statically known to be written to.
Iterable<Element> get fieldSetters;
/// Call [f] for all classes with instantiated types. This includes the
/// directly and abstractly instantiated classes but also classes whose type
/// arguments are used in live factory constructors.
void forEachInstantiatedClass(f(ClassElement cls, InstantiationInfo info));
/// Returns `true` if [member] is invoked as a setter.
bool hasInvokedSetter(Element member);
/// The closed world computed by this world builder.
///
/// This is only available after the world builder has been closed.
ClosedWorld get closedWorldForTesting;
}
/// The type and kind of an instantiation registered through
/// `ResolutionWorldBuilder.registerTypeInstantiation`.
class Instance {
final InterfaceType type;
final Instantiation kind;
final bool isRedirection;
Instance(this.type, this.kind, {this.isRedirection: false});
int get hashCode {
return Hashing.objectHash(
type, Hashing.objectHash(kind, Hashing.objectHash(isRedirection)));
}
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! Instance) return false;
return type == other.type &&
kind == other.kind &&
isRedirection == other.isRedirection;
}
String toString() {
StringBuffer sb = new StringBuffer();
sb.write(type);
if (kind == Instantiation.DIRECTLY_INSTANTIATED) {
sb.write(' directly');
} else if (kind == Instantiation.ABSTRACTLY_INSTANTIATED) {
sb.write(' abstractly');
} else if (kind == Instantiation.UNINSTANTIATED) {
sb.write(' none');
}
if (isRedirection) {
sb.write(' redirect');
}
return sb.toString();
}
}
/// Information about instantiations of a class.
class InstantiationInfo {
/// A map from constructor of the class to their instantiated types.
///
/// For instance
///
/// import 'dart:html';
///
/// abstract class AbstractClass<S> {
/// factory AbstractClass.a() = Class<S>.a;
/// factory AbstractClass.b() => new Class<S>.b();
/// }
/// class Class<T> implements AbstractClass<T> {
/// Class.a();
/// Class.b();
/// factory Class.c() = Class.b<T>;
/// }
///
///
/// main() {
/// new Class.a();
/// new Class<int>.a();
/// new Class<String>.b();
/// new Class<num>.c();
/// new AbstractClass<double>.a();
/// new AbstractClass<bool>.b();
/// new DivElement(); // native instantiation
/// }
///
/// will generate the mappings
///
/// AbstractClass: {
/// AbstractClass.a: {
/// AbstractClass<double> none, // from `new AbstractClass<double>.a()`
/// },
/// AbstractClass.b: {
/// AbstractClass<bool> none, // from `new AbstractClass<bool>.b()`
/// },
/// },
/// Class: {
/// Class.a: {
/// Class directly, // from `new Class.a()`
/// Class<int> directly, // from `new Class<int>.a()`
/// Class<S> directly redirect, // from `factory AbstractClass.a`
/// },
/// Class.b: {
/// Class<String> directly, // from `new Class<String>.b()`
/// Class<T> directly redirect, // from `factory Class.c`
/// Class<S> directly, // from `factory AbstractClass.b`
/// },
/// Class.c: {
/// Class<num> directly, // from `new Class<num>.c()`
/// },
/// },
/// DivElement: {
/// DivElement: {
/// DivElement abstractly, // from `new DivElement()`
/// },
/// }
///
/// If the constructor is unknown, for instance for native or mirror usage,
/// `null` is used as key.
Map<ConstructorElement, Set<Instance>> instantiationMap;
/// Register [type] as the instantiation [kind] using [constructor].
void addInstantiation(
ConstructorElement constructor, InterfaceType type, Instantiation kind,
{bool isRedirection: false}) {
instantiationMap ??= <ConstructorElement, Set<Instance>>{};
instantiationMap
.putIfAbsent(constructor, () => new Set<Instance>())
.add(new Instance(type, kind, isRedirection: isRedirection));
switch (kind) {
case Instantiation.DIRECTLY_INSTANTIATED:
isDirectlyInstantiated = true;
break;
case Instantiation.ABSTRACTLY_INSTANTIATED:
isAbstractlyInstantiated = true;
break;
case Instantiation.UNINSTANTIATED:
break;
default:
throw new StateError("Instantiation $kind is not allowed.");
}
}
/// `true` if the class is either directly or abstractly instantiated.
bool get hasInstantiation =>
isDirectlyInstantiated || isAbstractlyInstantiated;
/// `true` if the class is directly instantiated.
bool isDirectlyInstantiated = false;
/// `true` if the class is abstractly instantiated.
bool isAbstractlyInstantiated = false;
String toString() {
StringBuffer sb = new StringBuffer();
sb.write('InstantiationInfo[');
if (instantiationMap != null) {
bool needsComma = false;
instantiationMap
.forEach((ConstructorElement constructor, Set<Instance> set) {
if (needsComma) {
sb.write(', ');
}
if (constructor != null) {
sb.write(constructor);
} else {
sb.write('<unknown>');
}
sb.write(': ');
sb.write(set);
needsComma = true;
});
}
sb.write(']');
return sb.toString();
}
}
class ResolutionWorldBuilderImpl implements ResolutionWorldBuilder {
/// Instantiation information for all classes with instantiated types.
///
/// Invariant: Elements are declaration elements.
final Map<ClassElement, InstantiationInfo> _instantiationInfo =
<ClassElement, InstantiationInfo>{};
/// Classes implemented by directly instantiated classes.
final Set<ClassElement> _implementedClasses = new Set<ClassElement>();
/// The set of all referenced static fields.
///
/// Invariant: Elements are declaration elements.
final Set<FieldElement> allReferencedStaticFields = new Set<FieldElement>();
/**
* Documentation wanted -- johnniwinther
*
* Invariant: Elements are declaration elements.
*/
final Set<FunctionElement> methodsNeedingSuperGetter =
new Set<FunctionElement>();
final Map<String, Map<Selector, SelectorConstraints>> _invokedNames =
<String, Map<Selector, SelectorConstraints>>{};
final Map<String, Map<Selector, SelectorConstraints>> _invokedGetters =
<String, Map<Selector, SelectorConstraints>>{};
final Map<String, Map<Selector, SelectorConstraints>> _invokedSetters =
<String, Map<Selector, SelectorConstraints>>{};
final Map<ClassElement, _ClassUsage> _processedClasses =
<ClassElement, _ClassUsage>{};
/// Map of registered usage of static members of live classes.
final Map<Entity, _StaticMemberUsage> _staticMemberUsage =
<Entity, _StaticMemberUsage>{};
/// Map of registered usage of instance members of live classes.
final Map<MemberEntity, _MemberUsage> _instanceMemberUsage =
<MemberEntity, _MemberUsage>{};
/// Map containing instance members of live classes that are not yet live
/// themselves.
final Map<String, Set<_MemberUsage>> _instanceMembersByName =
<String, Set<_MemberUsage>>{};
/// Map containing instance methods of live classes that are not yet
/// closurized.
final Map<String, Set<_MemberUsage>> _instanceFunctionsByName =
<String, Set<_MemberUsage>>{};
/// Fields set.
final Set<Element> fieldSetters = new Set<Element>();
final Set<DartType> isChecks = new Set<DartType>();
/**
* Set of (live) [:call:] methods whose signatures reference type variables.
*
* A live [:call:] method is one whose enclosing class has been instantiated.
*/
final Set<Element> callMethodsWithFreeTypeVariables = new Set<Element>();
/**
* Set of (live) local functions (closures) whose signatures reference type
* variables.
*
* A live function is one whose enclosing member function has been enqueued.
*/
final Set<Element> closuresWithFreeTypeVariables = new Set<Element>();
/**
* Set of all closures in the program. Used by the mirror tracking system
* to find all live closure instances.
*/
final Set<LocalFunctionElement> allClosures = new Set<LocalFunctionElement>();
/**
* Set of methods in instantiated classes that are potentially
* closurized.
*/
final Set<Element> closurizedMembers = new Set<Element>();
final SelectorConstraintsStrategy selectorConstraintsStrategy;
bool hasRuntimeTypeSupport = false;
bool hasIsolateSupport = false;
bool hasFunctionApplySupport = false;
/// Used for testing the new more precise computation of instantiated types
/// and classes.
bool useInstantiationMap = false;
final Backend _backend;
final Resolution _resolution;
bool _closed = false;
ClosedWorld _closedWorldCache;
FunctionSetBuilder _allFunctions;
final Set<TypedefElement> _allTypedefs = new Set<TypedefElement>();
final Map<ClassElement, Set<MixinApplicationElement>> _mixinUses =
new Map<ClassElement, Set<MixinApplicationElement>>();
// We keep track of subtype and subclass relationships in four
// distinct sets to make class hierarchy analysis faster.
final Map<ClassElement, ClassHierarchyNode> _classHierarchyNodes =
<ClassElement, ClassHierarchyNode>{};
final Map<ClassElement, ClassSet> _classSets = <ClassElement, ClassSet>{};
final Set<Element> alreadyPopulated;
final CacheStrategy cacheStrategy;
bool get isClosed => _closed;
ResolutionWorldBuilderImpl(Backend backend, Resolution resolution,
CacheStrategy cacheStrategy, this.selectorConstraintsStrategy)
: this._backend = backend,
this._resolution = resolution,
this.cacheStrategy = cacheStrategy,
alreadyPopulated = cacheStrategy.newSet() {
_allFunctions = new FunctionSetBuilder();
}
Iterable<ClassElement> get processedClasses => _processedClasses.keys
.where((cls) => _processedClasses[cls].isInstantiated);
CommonElements get commonElements => _resolution.commonElements;
ClosedWorld get closedWorldForTesting {
if (!_closed) {
throw new SpannableAssertionFailure(
NO_LOCATION_SPANNABLE, "The world builder has not yet been closed.");
}
return _closedWorldCache;
}
/// All directly instantiated classes, that is, classes with a generative
/// constructor that has been called directly and not only through a
/// super-call.
// TODO(johnniwinther): Improve semantic precision.
Iterable<ClassElement> get directlyInstantiatedClasses {
Set<ClassElement> classes = new Set<ClassElement>();
getInstantiationMap().forEach((ClassElement cls, InstantiationInfo info) {
if (info.hasInstantiation) {
classes.add(cls);
}
});
return classes;
}
/// All directly instantiated types, that is, the types of the directly
/// instantiated classes.
///
/// See [directlyInstantiatedClasses].
// TODO(johnniwinther): Improve semantic precision.
Iterable<DartType> get instantiatedTypes {
Set<InterfaceType> types = new Set<InterfaceType>();
getInstantiationMap().forEach((_, InstantiationInfo info) {
if (info.instantiationMap != null) {
for (Set<Instance> instances in info.instantiationMap.values) {
for (Instance instance in instances) {
types.add(instance.type);
}
}
}
});
return types;
}
/// Returns `true` if [cls] is considered to be implemented by an
/// instantiated class, either directly, through subclasses or through
/// subtypes. The latter case only contains spurious information from
/// instantiations through factory constructors and mixins.
// TODO(johnniwinther): Improve semantic precision.
bool isImplemented(ClassElement cls) {
return _implementedClasses.contains(cls.declaration);
}
/// Register [type] as (directly) instantiated.
///
/// If [byMirrors] is `true`, the instantiation is through mirrors.
// TODO(johnniwinther): Fully enforce the separation between exact, through
// subclass and through subtype instantiated types/classes.
// TODO(johnniwinther): Support unknown type arguments for generic types.
void registerTypeInstantiation(
InterfaceType type, ClassUsedCallback classUsed,
{ConstructorElement constructor,
bool byMirrors: false,
bool isRedirection: false}) {
ClassElement cls = type.element;
cls.ensureResolved(_resolution);
InstantiationInfo info =
_instantiationInfo.putIfAbsent(cls, () => new InstantiationInfo());
Instantiation kind = Instantiation.UNINSTANTIATED;
bool isNative = _backend.isNative(cls);
if (!cls.isAbstract ||
// We can't use the closed-world assumption with native abstract
// classes; a native abstract class may have non-abstract subclasses
// not declared to the program. Instances of these classes are
// indistinguishable from the abstract class.
isNative ||
// Likewise, if this registration comes from the mirror system,
// all bets are off.
// TODO(herhut): Track classes required by mirrors seperately.
byMirrors) {
if (isNative || byMirrors) {
kind = Instantiation.ABSTRACTLY_INSTANTIATED;
} else {
kind = Instantiation.DIRECTLY_INSTANTIATED;
}
_processInstantiatedClass(cls, classUsed);
}
info.addInstantiation(constructor, type, kind,
isRedirection: isRedirection);
// TODO(johnniwinther): Use [_instantiationInfo] to compute this information
// instead.
if (_implementedClasses.add(cls)) {
classUsed(cls, _getClassUsage(cls).implement());
cls.allSupertypes.forEach((InterfaceType supertype) {
if (_implementedClasses.add(supertype.element)) {
classUsed(
supertype.element, _getClassUsage(supertype.element).implement());
}
});
}
}
@override
void forEachInstantiatedClass(f(ClassElement cls, InstantiationInfo info)) {
getInstantiationMap().forEach(f);
}
bool _hasMatchingSelector(
Map<Selector, SelectorConstraints> selectors, Element member) {
if (selectors == null) return false;
for (Selector selector in selectors.keys) {
if (selector.appliesUnnamed(member)) {
SelectorConstraints masks = selectors[selector];
if (masks.applies(member, selector, this)) {
return true;
}
}
}
return false;
}
/// Returns the instantiation map used for computing the closed world.
///
/// If [useInstantiationMap] is `true`, redirections are removed and
/// redirecting factories are converted to their effective target and type.
Map<ClassElement, InstantiationInfo> getInstantiationMap() {
if (!useInstantiationMap) return _instantiationInfo;
Map<ClassElement, InstantiationInfo> instantiationMap =
<ClassElement, InstantiationInfo>{};
InstantiationInfo infoFor(ClassElement cls) {
return instantiationMap.putIfAbsent(cls, () => new InstantiationInfo());
}
_instantiationInfo.forEach((cls, info) {
if (info.instantiationMap != null) {
info.instantiationMap
.forEach((ConstructorElement constructor, Set<Instance> set) {
for (Instance instance in set) {
if (instance.isRedirection) {
continue;
}
if (constructor == null || !constructor.isRedirectingFactory) {
infoFor(cls)
.addInstantiation(constructor, instance.type, instance.kind);
} else {
ConstructorElement target = constructor.effectiveTarget;
InterfaceType targetType =
constructor.computeEffectiveTargetType(instance.type);
Instantiation kind = Instantiation.DIRECTLY_INSTANTIATED;
if (target.enclosingClass.isAbstract) {
// If target is a factory constructor on an abstract class.
kind = Instantiation.UNINSTANTIATED;
}
infoFor(targetType.element)
.addInstantiation(target, targetType, kind);
}
}
});
}
});
return instantiationMap;
}
bool _hasInvocation(Element member) {
return _hasMatchingSelector(_invokedNames[member.name], member);
}
bool _hasInvokedGetter(Element member) {
return _hasMatchingSelector(_invokedGetters[member.name], member) ||
member.isFunction && methodsNeedingSuperGetter.contains(member);
}
bool hasInvokedSetter(Element member) {
return _hasMatchingSelector(_invokedSetters[member.name], member);
}
void registerDynamicUse(
DynamicUse dynamicUse, MemberUsedCallback memberUsed) {
Selector selector = dynamicUse.selector;
String methodName = selector.name;
void _process(Map<String, Set<_MemberUsage>> memberMap,
EnumSet<MemberUse> action(_MemberUsage usage)) {
_processSet(memberMap, methodName, (_MemberUsage usage) {
if (dynamicUse.appliesUnnamed(usage.entity, this)) {
memberUsed(usage.entity, action(usage));
return true;
}
return false;
});
}
switch (dynamicUse.kind) {
case DynamicUseKind.INVOKE:
if (_registerNewSelector(dynamicUse, _invokedNames)) {
_process(_instanceMembersByName, (m) => m.invoke());
}
break;
case DynamicUseKind.GET:
if (_registerNewSelector(dynamicUse, _invokedGetters)) {
_process(_instanceMembersByName, (m) => m.read());
_process(_instanceFunctionsByName, (m) => m.read());
}
break;
case DynamicUseKind.SET:
if (_registerNewSelector(dynamicUse, _invokedSetters)) {
_process(_instanceMembersByName, (m) => m.write());
}
break;
}
}
bool _registerNewSelector(DynamicUse dynamicUse,
Map<String, Map<Selector, SelectorConstraints>> selectorMap) {
Selector selector = dynamicUse.selector;
String name = selector.name;
ReceiverConstraint mask = dynamicUse.mask;
Map<Selector, SelectorConstraints> selectors = selectorMap.putIfAbsent(
name, () => new Maplet<Selector, SelectorConstraints>());
UniverseSelectorConstraints constraints =
selectors.putIfAbsent(selector, () {
return selectorConstraintsStrategy.createSelectorConstraints(selector);
});
return constraints.addReceiverConstraint(mask);
}
DartType registerIsCheck(DartType type) {
type.computeUnaliased(_resolution);
type = type.unaliased;
// Even in checked mode, type annotations for return type and argument
// types do not imply type checks, so there should never be a check
// against the type variable of a typedef.
isChecks.add(type);
return type;
}
void registerStaticUse(StaticUse staticUse, MemberUsedCallback memberUsed) {
Element element = staticUse.element;
assert(invariant(element, element.isDeclaration,
message: "Element ${element} is not the declaration."));
_StaticMemberUsage usage = _staticMemberUsage.putIfAbsent(element, () {
if ((element.isStatic || element.isTopLevel) && element.isFunction) {
return new _StaticFunctionUsage(element);
} else {
return new _GeneralStaticMemberUsage(element);
}
});
EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
if (Elements.isStaticOrTopLevel(element) && element.isField) {
allReferencedStaticFields.add(element);
}
// TODO(johnniwinther): Avoid this. Currently [FIELD_GET] and
// [FIELD_SET] contains [BoxFieldElement]s which we cannot enqueue.
// Also [CLOSURE] contains [LocalFunctionElement] which we cannot
// enqueue.
switch (staticUse.kind) {
case StaticUseKind.FIELD_GET:
break;
case StaticUseKind.FIELD_SET:
fieldSetters.add(element);
break;
case StaticUseKind.CLOSURE:
LocalFunctionElement closure = staticUse.element;
if (closure.type.containsTypeVariables) {
closuresWithFreeTypeVariables.add(closure);
}
allClosures.add(element);
break;
case StaticUseKind.SUPER_TEAR_OFF:
useSet.addAll(usage.tearOff());
methodsNeedingSuperGetter.add(element);
break;
case StaticUseKind.SUPER_FIELD_SET:
fieldSetters.add(element);
useSet.addAll(usage.normalUse());
break;
case StaticUseKind.STATIC_TEAR_OFF:
useSet.addAll(usage.tearOff());
break;
case StaticUseKind.GENERAL:
case StaticUseKind.DIRECT_USE:
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
case StaticUseKind.REDIRECTION:
useSet.addAll(usage.normalUse());
break;
case StaticUseKind.DIRECT_INVOKE:
invariant(
element, 'Direct static use is not supported for resolution.');
break;
}
if (useSet.isNotEmpty) {
memberUsed(usage.entity, useSet);
}
}
void forgetEntity(Entity entity, Compiler compiler) {
allClosures.remove(entity);
slowDirectlyNestedClosures(entity).forEach(compiler.forgetElement);
closurizedMembers.remove(entity);
fieldSetters.remove(entity);
_instantiationInfo.remove(entity);
void removeUsage(Set<_MemberUsage> set, Entity entity) {
if (set == null) return;
set.removeAll(
set.where((_MemberUsage usage) => usage.entity == entity).toList());
}
_processedClasses.remove(entity);
removeUsage(_instanceMembersByName[entity.name], entity);
removeUsage(_instanceFunctionsByName[entity.name], entity);
}
// TODO(ahe): Replace this method with something that is O(1), for example,
// by using a map.
List<LocalFunctionElement> slowDirectlyNestedClosures(Element element) {
// Return new list to guard against concurrent modifications.
return new List<LocalFunctionElement>.from(
allClosures.where((LocalFunctionElement closure) {
return closure.executableContext == element;
}));
}
/// Return the canonical [_ClassUsage] for [cls].
_ClassUsage _getClassUsage(ClassElement cls) {
return _processedClasses.putIfAbsent(cls, () {
cls.ensureResolved(_resolution);
_ClassUsage usage = new _ClassUsage(cls);
_resolution.ensureClassMembers(cls);
return usage;
});
}
/// Register [cls] and all its superclasses as instantiated.
void _processInstantiatedClass(
ClassElement cls, ClassUsedCallback classUsed) {
// Registers [superclass] as instantiated. Returns `true` if it wasn't
// already instantiated and we therefore have to process its superclass as
// well.
bool processClass(ClassElement superclass) {
_ClassUsage usage = _getClassUsage(superclass);
if (!usage.isInstantiated) {
classUsed(usage.cls, usage.instantiate());
return true;
}
return false;
}
while (cls != null && processClass(cls)) {
cls = cls.superclass;
}
}
/// Computes usage for all members declared by [cls]. Calls [membersUsed] with
/// the usage changes for each member.
void processClassMembers(ClassElement cls, MemberUsedCallback memberUsed) {
cls.implementation.forEachMember((ClassElement cls, MemberElement member) {
_processInstantiatedClassMember(cls, member, memberUsed);
});
}
/// Call [updateUsage] on all [_MemberUsage]s in the set in [map] for
/// [memberName]. If [updateUsage] returns `true` the usage is removed from
/// the set.
void _processSet(Map<String, Set<_MemberUsage>> map, String memberName,
bool updateUsage(_MemberUsage e)) {
Set<_MemberUsage> members = map[memberName];
if (members == null) return;
// [f] might add elements to [: map[memberName] :] during the loop below
// so we create a new list for [: map[memberName] :] and prepend the
// [remaining] members after the loop.
map[memberName] = new Set<_MemberUsage>();
Set<_MemberUsage> remaining = new Set<_MemberUsage>();
for (_MemberUsage usage in members) {
if (!updateUsage(usage)) remaining.add(usage);
}
map[memberName].addAll(remaining);
}
void _processInstantiatedClassMember(
ClassElement cls, MemberElement member, MemberUsedCallback memberUsed) {
assert(invariant(member, member.isDeclaration));
if (!member.isInstanceMember) return;
String memberName = member.name;
member.computeType(_resolution);
// The obvious thing to test here would be "member.isNative",
// however, that only works after metadata has been parsed/analyzed,
// and that may not have happened yet.
// So instead we use the enclosing class, which we know have had
// its metadata parsed and analyzed.
// Note: this assumes that there are no non-native fields on native
// classes, which may not be the case when a native class is subclassed.
_instanceMemberUsage.putIfAbsent(member, () {
bool isNative = _backend.isNative(cls);
_MemberUsage usage = new _MemberUsage(member, isNative: isNative);
EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
useSet.addAll(usage.appliedUse);
if (member.isField && isNative) {
registerUsedElement(member);
}
if (member.isFunction &&
member.name == Identifiers.call &&
!cls.typeVariables.isEmpty) {
callMethodsWithFreeTypeVariables.add(member);
}
if (_hasInvokedGetter(member)) {
useSet.addAll(usage.read());
}
if (_hasInvocation(member)) {
useSet.addAll(usage.invoke());
}
if (hasInvokedSetter(member)) {
useSet.addAll(usage.write());
}
if (usage.pendingUse.contains(MemberUse.NORMAL)) {
// The element is not yet used. Add it to the list of instance
// members to still be processed.
_instanceMembersByName
.putIfAbsent(memberName, () => new Set<_MemberUsage>())
.add(usage);
}
if (usage.pendingUse.contains(MemberUse.CLOSURIZE_INSTANCE)) {
// Store the member in [instanceFunctionsByName] to catch
// getters on the function.
_instanceFunctionsByName
.putIfAbsent(memberName, () => new Set<_MemberUsage>())
.add(usage);
}
memberUsed(usage.entity, useSet);
return usage;
});
}
/// Returns an iterable over all mixin applications that mixin [cls].
Iterable<MixinApplicationElement> allMixinUsesOf(ClassElement cls) {
Iterable<MixinApplicationElement> uses = _mixinUses[cls];
return uses != null ? uses : const <MixinApplicationElement>[];
}
/// Called to add [cls] to the set of known classes.
///
/// This ensures that class hierarchy queries can be performed on [cls] and
/// classes that extend or implement it.
void registerClass(ClassElement cls) => _registerClass(cls);
void _registerClass(ClassElement cls, {bool isDirectlyInstantiated: false}) {
_ensureClassSet(cls);
if (isDirectlyInstantiated) {
_updateClassHierarchyNodeForClass(cls, directlyInstantiated: true);
}
}
void registerTypedef(TypedefElement typdef) {
_allTypedefs.add(typdef);
}
ClassHierarchyNode _ensureClassHierarchyNode(ClassElement cls) {
cls = cls.declaration;
return _classHierarchyNodes.putIfAbsent(cls, () {
ClassHierarchyNode parentNode;
if (cls.superclass != null) {
parentNode = _ensureClassHierarchyNode(cls.superclass);
}
return new ClassHierarchyNode(parentNode, cls);
});
}
ClassSet _ensureClassSet(ClassElement cls) {
cls = cls.declaration;
return _classSets.putIfAbsent(cls, () {
ClassHierarchyNode node = _ensureClassHierarchyNode(cls);
ClassSet classSet = new ClassSet(node);
for (InterfaceType type in cls.allSupertypes) {
// TODO(johnniwinther): Optimization: Avoid adding [cls] to
// superclasses.
ClassSet subtypeSet = _ensureClassSet(type.element);
subtypeSet.addSubtype(node);
}
if (cls.isMixinApplication) {
// TODO(johnniwinther): Store this in the [ClassSet].
MixinApplicationElement mixinApplication = cls;
if (mixinApplication.mixin != null) {
// If [mixinApplication] is malformed [mixin] is `null`.
registerMixinUse(mixinApplication, mixinApplication.mixin);
}
}
return classSet;
});
}
void _updateSuperClassHierarchyNodeForClass(ClassHierarchyNode node) {
// Ensure that classes implicitly implementing `Function` are in its
// subtype set.
ClassElement cls = node.cls;
if (cls != commonElements.functionClass &&
cls.implementsFunction(commonElements)) {
ClassSet subtypeSet = _ensureClassSet(commonElements.functionClass);
subtypeSet.addSubtype(node);
}
if (!node.isInstantiated && node.parentNode != null) {
_updateSuperClassHierarchyNodeForClass(node.parentNode);
}
}
void _updateClassHierarchyNodeForClass(ClassElement cls,
{bool directlyInstantiated: false, bool abstractlyInstantiated: false}) {
ClassHierarchyNode node = _ensureClassHierarchyNode(cls);
_updateSuperClassHierarchyNodeForClass(node);
if (directlyInstantiated) {
node.isDirectlyInstantiated = true;
}
if (abstractlyInstantiated) {
node.isAbstractlyInstantiated = true;
}
}
ClosedWorld closeWorld(DiagnosticReporter reporter) {
Map<ClassElement, Set<ClassElement>> typesImplementedBySubclasses =
new Map<ClassElement, Set<ClassElement>>();
/// Updates the `isDirectlyInstantiated` and `isIndirectlyInstantiated`
/// properties of the [ClassHierarchyNode] for [cls].
void addSubtypes(ClassElement cls, InstantiationInfo info) {
if (!info.hasInstantiation) {
return;
}
if (cacheStrategy.hasIncrementalSupport && !alreadyPopulated.add(cls)) {
return;
}
assert(cls.isDeclaration);
if (!cls.isResolved) {
reporter.internalError(cls, 'Class "${cls.name}" is not resolved.');
}
_updateClassHierarchyNodeForClass(cls,
directlyInstantiated: info.isDirectlyInstantiated,
abstractlyInstantiated: info.isAbstractlyInstantiated);
// Walk through the superclasses, and record the types
// implemented by that type on the superclasses.
ClassElement superclass = cls.superclass;
while (superclass != null) {
Set<Element> typesImplementedBySubclassesOfCls =
typesImplementedBySubclasses.putIfAbsent(
superclass, () => new Set<ClassElement>());
for (DartType current in cls.allSupertypes) {
typesImplementedBySubclassesOfCls.add(current.element);
}
superclass = superclass.superclass;
}
}
// Use the [:seenClasses:] set to include non-instantiated
// classes: if the superclass of these classes require RTI, then
// they also need RTI, so that a constructor passes the type
// variables to the super constructor.
forEachInstantiatedClass(addSubtypes);
_closed = true;
return _closedWorldCache = new ClosedWorldImpl(
backend: _backend,
commonElements: commonElements,
resolverWorld: this,
functionSetBuilder: _allFunctions,
allTypedefs: _allTypedefs,
mixinUses: _mixinUses,
typesImplementedBySubclasses: typesImplementedBySubclasses,
classHierarchyNodes: _classHierarchyNodes,
classSets: _classSets);
}
void registerMixinUse(
MixinApplicationElement mixinApplication, ClassElement mixin) {
// TODO(johnniwinther): Add map restricted to live classes.
// We don't support patch classes as mixin.
assert(mixin.isDeclaration);
Set<MixinApplicationElement> users =
_mixinUses.putIfAbsent(mixin, () => new Set<MixinApplicationElement>());
users.add(mixinApplication);
}
void registerUsedElement(MemberElement element) {
if (element.isInstanceMember && !element.isAbstract) {
_allFunctions.add(element);
}
}
ClosedWorld get closedWorldCache {
assert(isClosed);
return _closedWorldCache;
}
}
/// World builder specific to codegen.
///
/// This adds additional access to liveness of selectors and elements.
abstract class CodegenWorldBuilder implements WorldBuilder {
void forEachInvokedName(
f(String name, Map<Selector, SelectorConstraints> selectors));
void forEachInvokedGetter(
f(String name, Map<Selector, SelectorConstraints> selectors));
void forEachInvokedSetter(
f(String name, Map<Selector, SelectorConstraints> selectors));
/// Returns `true` if [member] is invoked as a setter.
bool hasInvokedSetter(Element member, ClosedWorld world);
bool hasInvokedGetter(Element member, ClosedWorld world);
Map<Selector, SelectorConstraints> invocationsByName(String name);
Map<Selector, SelectorConstraints> getterInvocationsByName(String name);
Map<Selector, SelectorConstraints> setterInvocationsByName(String name);
Iterable<FunctionElement> get staticFunctionsNeedingGetter;
Iterable<FunctionElement> get methodsNeedingSuperGetter;
/// The set of all referenced static fields.
///
/// Invariant: Elements are declaration elements.
Iterable<FieldElement> get allReferencedStaticFields;
}
class CodegenWorldBuilderImpl implements CodegenWorldBuilder {
final Backend _backend;
/// The set of all directly instantiated classes, that is, classes with a
/// generative constructor that has been called directly and not only through
/// a super-call.
///
/// Invariant: Elements are declaration elements.
// TODO(johnniwinther): [_directlyInstantiatedClasses] and
// [_instantiatedTypes] sets should be merged.
final Set<ClassElement> _directlyInstantiatedClasses =
new Set<ClassElement>();
/// The set of all directly instantiated types, that is, the types of the
/// directly instantiated classes.
///
/// See [_directlyInstantiatedClasses].
final Set<DartType> _instantiatedTypes = new Set<DartType>();
/// Classes implemented by directly instantiated classes.
final Set<ClassElement> _implementedClasses = new Set<ClassElement>();
/// The set of all referenced static fields.
///
/// Invariant: Elements are declaration elements.
final Set<FieldElement> allReferencedStaticFields = new Set<FieldElement>();
/**
* Documentation wanted -- johnniwinther
*
* Invariant: Elements are declaration elements.
*/
final Set<FunctionElement> staticFunctionsNeedingGetter =
new Set<FunctionElement>();
final Set<FunctionElement> methodsNeedingSuperGetter =
new Set<FunctionElement>();
final Map<String, Map<Selector, SelectorConstraints>> _invokedNames =
<String, Map<Selector, SelectorConstraints>>{};
final Map<String, Map<Selector, SelectorConstraints>> _invokedGetters =
<String, Map<Selector, SelectorConstraints>>{};
final Map<String, Map<Selector, SelectorConstraints>> _invokedSetters =
<String, Map<Selector, SelectorConstraints>>{};
final Map<ClassElement, _ClassUsage> _processedClasses =
<ClassElement, _ClassUsage>{};
/// Map of registered usage of static members of live classes.
final Map<Entity, _StaticMemberUsage> _staticMemberUsage =
<Entity, _StaticMemberUsage>{};
/// Map of registered usage of instance members of live classes.
final Map<MemberEntity, _MemberUsage> _instanceMemberUsage =
<MemberEntity, _MemberUsage>{};
/// Map containing instance members of live classes that are not yet live
/// themselves.
final Map<String, Set<_MemberUsage>> _instanceMembersByName =
<String, Set<_MemberUsage>>{};
/// Map containing instance methods of live classes that are not yet
/// closurized.
final Map<String, Set<_MemberUsage>> _instanceFunctionsByName =
<String, Set<_MemberUsage>>{};
final Set<DartType> isChecks = new Set<DartType>();
final SelectorConstraintsStrategy selectorConstraintsStrategy;
CodegenWorldBuilderImpl(this._backend, this.selectorConstraintsStrategy);
// TODO(johnniwinther): Remove this hack:
ClosedWorld get _world =>
_backend.compiler.resolverWorld.closedWorldForTesting;
Iterable<ClassElement> get processedClasses => _processedClasses.keys
.where((cls) => _processedClasses[cls].isInstantiated);
/// All directly instantiated classes, that is, classes with a generative
/// constructor that has been called directly and not only through a
/// super-call.
// TODO(johnniwinther): Improve semantic precision.
Iterable<ClassElement> get directlyInstantiatedClasses {
return _directlyInstantiatedClasses;
}
/// All directly instantiated types, that is, the types of the directly
/// instantiated classes.
///
/// See [directlyInstantiatedClasses].
// TODO(johnniwinther): Improve semantic precision.
Iterable<DartType> get instantiatedTypes => _instantiatedTypes;
/// Register [type] as (directly) instantiated.
///
/// If [byMirrors] is `true`, the instantiation is through mirrors.
// TODO(johnniwinther): Fully enforce the separation between exact, through
// subclass and through subtype instantiated types/classes.
// TODO(johnniwinther): Support unknown type arguments for generic types.
void registerTypeInstantiation(
InterfaceType type, ClassUsedCallback classUsed,
{bool byMirrors: false}) {
ClassElement cls = type.element;
bool isNative = _backend.isNative(cls);
_instantiatedTypes.add(type);
if (!cls.isAbstract
// We can't use the closed-world assumption with native abstract
// classes; a native abstract class may have non-abstract subclasses
// not declared to the program. Instances of these classes are
// indistinguishable from the abstract class.
||
isNative
// Likewise, if this registration comes from the mirror system,
// all bets are off.
// TODO(herhut): Track classes required by mirrors separately.
||
byMirrors) {
_directlyInstantiatedClasses.add(cls);
_processInstantiatedClass(cls, classUsed);
}
// TODO(johnniwinther): Replace this by separate more specific mappings that
// include the type arguments.
if (_implementedClasses.add(cls)) {
classUsed(cls, _getClassUsage(cls).implement());
cls.allSupertypes.forEach((InterfaceType supertype) {
if (_implementedClasses.add(supertype.element)) {
classUsed(
supertype.element, _getClassUsage(supertype.element).implement());
}
});
}
}
bool _hasMatchingSelector(Map<Selector, SelectorConstraints> selectors,
Element member, ClosedWorld world) {
if (selectors == null) return false;
for (Selector selector in selectors.keys) {
if (selector.appliesUnnamed(member)) {
SelectorConstraints masks = selectors[selector];
if (masks.applies(member, selector, world)) {
return true;
}
}
}
return false;
}
bool hasInvocation(Element member, ClosedWorld world) {
return _hasMatchingSelector(_invokedNames[member.name], member, world);
}
bool hasInvokedGetter(Element member, ClosedWorld world) {
return _hasMatchingSelector(_invokedGetters[member.name], member, world) ||
member.isFunction && methodsNeedingSuperGetter.contains(member);
}
bool hasInvokedSetter(Element member, ClosedWorld world) {
return _hasMatchingSelector(_invokedSetters[member.name], member, world);
}
bool registerDynamicUse(
DynamicUse dynamicUse, MemberUsedCallback memberUsed) {
Selector selector = dynamicUse.selector;
String methodName = selector.name;
void _process(Map<String, Set<_MemberUsage>> memberMap,
EnumSet<MemberUse> action(_MemberUsage usage)) {
_processSet(memberMap, methodName, (_MemberUsage usage) {
if (dynamicUse.appliesUnnamed(usage.entity, _world)) {
memberUsed(usage.entity, action(usage));
return true;
}
return false;
});
}
switch (dynamicUse.kind) {
case DynamicUseKind.INVOKE:
if (_registerNewSelector(dynamicUse, _invokedNames)) {
_process(_instanceMembersByName, (m) => m.invoke());
return true;
}
break;
case DynamicUseKind.GET:
if (_registerNewSelector(dynamicUse, _invokedGetters)) {
_process(_instanceMembersByName, (m) => m.read());
_process(_instanceFunctionsByName, (m) => m.read());
return true;
}
break;
case DynamicUseKind.SET:
if (_registerNewSelector(dynamicUse, _invokedSetters)) {
_process(_instanceMembersByName, (m) => m.write());
return true;
}
break;
}
return false;
}
bool _registerNewSelector(DynamicUse dynamicUse,
Map<String, Map<Selector, SelectorConstraints>> selectorMap) {
Selector selector = dynamicUse.selector;
String name = selector.name;
ReceiverConstraint mask = dynamicUse.mask;
Map<Selector, SelectorConstraints> selectors = selectorMap.putIfAbsent(
name, () => new Maplet<Selector, SelectorConstraints>());
UniverseSelectorConstraints constraints =
selectors.putIfAbsent(selector, () {
return selectorConstraintsStrategy.createSelectorConstraints(selector);
});
return constraints.addReceiverConstraint(mask);
}
Map<Selector, SelectorConstraints> _asUnmodifiable(
Map<Selector, SelectorConstraints> map) {
if (map == null) return null;
return new UnmodifiableMapView(map);
}
Map<Selector, SelectorConstraints> invocationsByName(String name) {
return _asUnmodifiable(_invokedNames[name]);
}
Map<Selector, SelectorConstraints> getterInvocationsByName(String name) {
return _asUnmodifiable(_invokedGetters[name]);
}
Map<Selector, SelectorConstraints> setterInvocationsByName(String name) {
return _asUnmodifiable(_invokedSetters[name]);
}
void forEachInvokedName(
f(String name, Map<Selector, SelectorConstraints> selectors)) {
_invokedNames.forEach(f);
}
void forEachInvokedGetter(
f(String name, Map<Selector, SelectorConstraints> selectors)) {
_invokedGetters.forEach(f);
}
void forEachInvokedSetter(
f(String name, Map<Selector, SelectorConstraints> selectors)) {
_invokedSetters.forEach(f);
}
DartType registerIsCheck(DartType type) {
type = type.unaliased;
// Even in checked mode, type annotations for return type and argument
// types do not imply type checks, so there should never be a check
// against the type variable of a typedef.
isChecks.add(type);
return type;
}
void _registerStaticUse(StaticUse staticUse) {
Element element = staticUse.element;
if (Elements.isStaticOrTopLevel(element) && element.isField) {
allReferencedStaticFields.add(element);
}
switch (staticUse.kind) {
case StaticUseKind.STATIC_TEAR_OFF:
staticFunctionsNeedingGetter.add(element);
break;
case StaticUseKind.SUPER_TEAR_OFF:
methodsNeedingSuperGetter.add(element);
break;
case StaticUseKind.SUPER_FIELD_SET:
case StaticUseKind.FIELD_SET:
case StaticUseKind.GENERAL:
case StaticUseKind.DIRECT_USE:
case StaticUseKind.CLOSURE:
case StaticUseKind.FIELD_GET:
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
case StaticUseKind.REDIRECTION:
case StaticUseKind.DIRECT_INVOKE:
break;
}
}
void registerStaticUse(StaticUse staticUse, MemberUsedCallback memberUsed) {
Element element = staticUse.element;
assert(invariant(element, element.isDeclaration,
message: "Element ${element} is not the declaration."));
_registerStaticUse(staticUse);
_StaticMemberUsage usage = _staticMemberUsage.putIfAbsent(element, () {
if ((element.isStatic || element.isTopLevel) && element.isFunction) {
return new _StaticFunctionUsage(element);
} else {
return new _GeneralStaticMemberUsage(element);
}
});
EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
switch (staticUse.kind) {
case StaticUseKind.STATIC_TEAR_OFF:
useSet.addAll(usage.tearOff());
break;
case StaticUseKind.FIELD_GET:
case StaticUseKind.FIELD_SET:
case StaticUseKind.CLOSURE:
// TODO(johnniwinther): Avoid this. Currently [FIELD_GET] and
// [FIELD_SET] contains [BoxFieldElement]s which we cannot enqueue.
// Also [CLOSURE] contains [LocalFunctionElement] which we cannot
// enqueue.
break;
case StaticUseKind.SUPER_FIELD_SET:
case StaticUseKind.SUPER_TEAR_OFF:
case StaticUseKind.GENERAL:
case StaticUseKind.DIRECT_USE:
useSet.addAll(usage.normalUse());
break;
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
case StaticUseKind.REDIRECTION:
useSet.addAll(usage.normalUse());
break;
case StaticUseKind.DIRECT_INVOKE:
_MemberUsage instanceUsage =
_getMemberUsage(staticUse.element, memberUsed);
memberUsed(instanceUsage.entity, instanceUsage.invoke());
_instanceMembersByName[instanceUsage.entity.name]
?.remove(instanceUsage);
useSet.addAll(usage.normalUse());
break;
}
memberUsed(usage.entity, useSet);
}
void forgetElement(Element element, Compiler compiler) {
_processedClasses.remove(element);
_directlyInstantiatedClasses.remove(element);
if (element is ClassElement) {
assert(invariant(element, element.thisType.isRaw,
message: 'Generic classes not supported (${element.thisType}).'));
_instantiatedTypes..remove(element.rawType)..remove(element.thisType);
}
removeFromSet(_instanceMembersByName, element);
removeFromSet(_instanceFunctionsByName, element);
if (element is MemberElement) {
for (Element closure in element.nestedClosures) {
removeFromSet(_instanceMembersByName, closure);
removeFromSet(_instanceFunctionsByName, closure);
}
}
}
void processClassMembers(ClassElement cls, MemberUsedCallback memberUsed) {
cls.implementation.forEachMember((_, MemberElement member) {
assert(invariant(member, member.isDeclaration));
if (!member.isInstanceMember) return;
_getMemberUsage(member, memberUsed);
});
}
_MemberUsage _getMemberUsage(
MemberElement member, MemberUsedCallback memberUsed) {
assert(invariant(member, member.isDeclaration));
return _instanceMemberUsage.putIfAbsent(member, () {
String memberName = member.name;
ClassElement cls = member.enclosingClass;
bool isNative = _backend.isNative(cls);
_MemberUsage usage = new _MemberUsage(member, isNative: isNative);
EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
useSet.addAll(usage.appliedUse);
if (hasInvokedGetter(member, _world)) {
useSet.addAll(usage.read());
}
if (hasInvokedSetter(member, _world)) {
useSet.addAll(usage.write());
}
if (hasInvocation(member, _world)) {
useSet.addAll(usage.invoke());
}
if (usage.pendingUse.contains(MemberUse.CLOSURIZE_INSTANCE)) {
// Store the member in [instanceFunctionsByName] to catch
// getters on the function.
_instanceFunctionsByName
.putIfAbsent(usage.entity.name, () => new Set<_MemberUsage>())
.add(usage);
}
if (usage.pendingUse.contains(MemberUse.NORMAL)) {
// The element is not yet used. Add it to the list of instance
// members to still be processed.
_instanceMembersByName
.putIfAbsent(memberName, () => new Set<_MemberUsage>())
.add(usage);
}
memberUsed(member, useSet);
return usage;
});
}
void _processSet(Map<String, Set<_MemberUsage>> map, String memberName,
bool f(_MemberUsage e)) {
Set<_MemberUsage> members = map[memberName];
if (members == null) return;
// [f] might add elements to [: map[memberName] :] during the loop below
// so we create a new list for [: map[memberName] :] and prepend the
// [remaining] members after the loop.
map[memberName] = new Set<_MemberUsage>();
Set<_MemberUsage> remaining = new Set<_MemberUsage>();
for (_MemberUsage member in members) {
if (!f(member)) remaining.add(member);
}
map[memberName].addAll(remaining);
}
/// Return the canonical [_ClassUsage] for [cls].
_ClassUsage _getClassUsage(ClassElement cls) {
return _processedClasses.putIfAbsent(cls, () => new _ClassUsage(cls));
}
void _processInstantiatedClass(
ClassElement cls, ClassUsedCallback classUsed) {
// Registers [superclass] as instantiated. Returns `true` if it wasn't
// already instantiated and we therefore have to process its superclass as
// well.
bool processClass(ClassElement superclass) {
_ClassUsage usage = _getClassUsage(superclass);
if (!usage.isInstantiated) {
classUsed(usage.cls, usage.instantiate());
return true;
}
return false;
}
while (cls != null && processClass(cls)) {
cls = cls.superclass;
}
}
}
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;
}
/// Registry for the observed use of a member [entity] in the open world.
abstract class _MemberUsage extends _AbstractUsage<MemberUse> {
// TODO(johnniwinther): Change [Entity] to [MemberEntity].
final Entity 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 {
assert(member.isFunction);
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 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.toString();
}
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> 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> 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);
EnumSet<MemberUse> get _originalUse => MemberUses.ALL_INSTANCE;
@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(MemberUses.CLOSURIZE_INSTANCE_ONLY);
} else if (hasRead) {
hasInvoke = true;
return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
} else {
hasRead = hasInvoke = true;
return _pendingUse.removeAll(MemberUses.ALL_INSTANCE);
}
}
@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 MemberUses.NORMAL_ONLY;
}
@override
EnumSet<MemberUse> fullyUse() => write();
}
/// 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.toString();
}
/// 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> {
final Entity 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();
@override
EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
String toString() => entity.toString();
}
class _GeneralStaticMemberUsage extends _StaticMemberUsage {
_GeneralStaticMemberUsage(Entity entity) : super.internal(entity);
EnumSet<MemberUse> tearOff() => normalUse();
}
class _StaticFunctionUsage extends _StaticMemberUsage {
bool hasClosurization = false;
_StaticFunctionUsage(Entity 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;
}
void removeFromSet(Map<String, Set<_MemberUsage>> map, Element element) {
Set<_MemberUsage> set = map[element.name];
if (set == null) return;
set.removeAll(
set.where((_MemberUsage usage) => usage.entity == element).toList());
}