blob: cdd46e9cf25304b1c54c1351865cd4c26ebb8eda [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 dart2js.enqueue;
import 'dart:collection' show Queue;
import 'cache_strategy.dart';
import 'common/backend_api.dart' show Backend;
import 'common/names.dart' show Identifiers;
import 'common/resolution.dart' show Resolution, ResolutionWorkItem;
import 'common/tasks.dart' show CompilerTask;
import 'common/work.dart' show WorkItem;
import 'common.dart';
import 'compiler.dart' show Compiler, GlobalDependencyRegistry;
import 'core_types.dart' show CommonElements;
import 'options.dart';
import 'dart_types.dart' show DartType, InterfaceType;
import 'elements/elements.dart'
show
AnalyzableElement,
AstElement,
ClassElement,
ConstructorElement,
Element,
Entity,
FunctionElement,
LibraryElement,
LocalFunctionElement,
TypedElement;
import 'native/native.dart' as native;
import 'types/types.dart' show TypeMaskStrategy;
import 'universe/selector.dart' show Selector;
import 'universe/world_builder.dart';
import 'universe/use.dart'
show DynamicUse, StaticUse, StaticUseKind, TypeUse, TypeUseKind;
import 'universe/world_impact.dart'
show ImpactStrategy, ImpactUseCase, WorldImpact, WorldImpactVisitor;
import 'util/util.dart' show Setlet;
import 'world.dart' show OpenWorld;
class EnqueueTask extends CompilerTask {
ResolutionEnqueuer _resolution;
Enqueuer _codegen;
final Compiler compiler;
String get name => 'Enqueue';
EnqueueTask(Compiler compiler)
: this.compiler = compiler,
super(compiler.measurer) {
_resolution = new ResolutionEnqueuer(
this,
compiler.options,
compiler.resolution,
compiler.options.analyzeOnly && compiler.options.analyzeMain
? const DirectEnqueuerStrategy()
: const TreeShakingEnqueuerStrategy(),
compiler.globalDependencies,
compiler.backend,
compiler.coreClasses,
compiler.cacheStrategy);
_codegen = compiler.backend.createCodegenEnqueuer(this, compiler);
}
ResolutionEnqueuer get resolution => _resolution;
Enqueuer get codegen => _codegen;
void forgetElement(Element element) {
resolution.forgetElement(element, compiler);
codegen.forgetElement(element, compiler);
}
}
abstract class Enqueuer {
WorldBuilder get universe;
native.NativeEnqueuer get nativeEnqueuer;
void forgetElement(Element element, Compiler compiler);
// TODO(johnniwinther): Initialize [_impactStrategy] to `null`.
ImpactStrategy _impactStrategy = const ImpactStrategy();
ImpactStrategy get impactStrategy => _impactStrategy;
void open(ImpactStrategy impactStrategy) {
_impactStrategy = impactStrategy;
}
void close() {
// TODO(johnniwinther): Set [_impactStrategy] to `null` and [queueIsClosed]
// to `true` here.
_impactStrategy = const ImpactStrategy();
}
/// Returns [:true:] if this enqueuer is the resolution enqueuer.
bool get isResolutionQueue;
bool queueIsClosed;
bool get queueIsEmpty;
ImpactUseCase get impactUse;
void forEach(void f(WorkItem work));
/// Apply the [worldImpact] to this enqueuer. If the [impactSource] is
/// provided the impact strategy will remove it from the element impact cache,
/// if it is no longer needed.
void applyImpact(WorldImpact worldImpact, {Element impactSource});
bool checkNoEnqueuedInvokedInstanceMethods();
void logSummary(log(message));
/// Returns [:true:] if [member] has been processed by this enqueuer.
bool isProcessed(Element member);
Iterable<Entity> get processedEntities;
Iterable<ClassElement> get processedClasses;
}
abstract class EnqueuerImpl extends Enqueuer {
CompilerTask get task;
void processInstantiatedClassMembers(ClassElement cls);
void processInstantiatedClassMember(ClassElement cls, Element member);
void registerStaticUse(StaticUse staticUse);
void registerStaticUseInternal(StaticUse staticUse);
void registerTypeUse(TypeUse typeUse);
void registerTypeUseInternal(TypeUse typeUse);
void registerDynamicUse(DynamicUse dynamicUse);
void handleUnseenSelectorInternal(DynamicUse dynamicUse);
}
/// [Enqueuer] which is specific to resolution.
class ResolutionEnqueuer extends EnqueuerImpl {
final CompilerTask task;
final String name;
final Resolution resolution;
final CompilerOptions options;
final Backend backend;
final GlobalDependencyRegistry globalDependencies;
final CommonElements commonElements;
final native.NativeEnqueuer nativeEnqueuer;
final EnqueuerStrategy strategy;
final Map<String, Set<Element>> instanceMembersByName =
new Map<String, Set<Element>>();
final Map<String, Set<Element>> instanceFunctionsByName =
new Map<String, Set<Element>>();
final Set<ClassElement> _processedClasses = new Set<ClassElement>();
Set<ClassElement> recentClasses = new Setlet<ClassElement>();
final ResolutionWorldBuilderImpl _universe;
bool queueIsClosed = false;
WorldImpactVisitor impactVisitor;
ResolutionEnqueuer(
this.task,
this.options,
this.resolution,
this.strategy,
this.globalDependencies,
Backend backend,
CommonElements commonElements,
CacheStrategy cacheStrategy,
[this.name = 'resolution enqueuer'])
: this.backend = backend,
this.commonElements = commonElements,
this.nativeEnqueuer = backend.nativeResolutionEnqueuer(),
processedElements = new Set<AstElement>(),
queue = new Queue<ResolutionWorkItem>(),
deferredQueue = new Queue<_DeferredAction>(),
_universe = new ResolutionWorldBuilderImpl(
backend, commonElements, cacheStrategy, const TypeMaskStrategy()) {
impactVisitor = new EnqueuerImplImpactVisitor(this);
}
ResolutionWorldBuilder get universe => _universe;
OpenWorld get openWorld => universe.openWorld;
bool get queueIsEmpty => queue.isEmpty;
DiagnosticReporter get reporter => resolution.reporter;
bool isClassProcessed(ClassElement cls) => _processedClasses.contains(cls);
Iterable<ClassElement> get processedClasses => _processedClasses;
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [element] must be a declaration element.
*/
void addToWorkList(Element element) {
assert(invariant(element, element.isDeclaration));
internalAddToWorkList(element);
}
void applyImpact(WorldImpact worldImpact, {Element impactSource}) {
if (worldImpact.isEmpty) return;
impactStrategy.visitImpact(
impactSource, worldImpact, impactVisitor, impactUse);
}
void registerInstantiatedType(InterfaceType type) {
_registerInstantiatedType(type, globalDependency: true);
}
void _registerInstantiatedType(InterfaceType type,
{ConstructorElement constructor,
bool mirrorUsage: false,
bool nativeUsage: false,
bool globalDependency: false,
bool isRedirection: false}) {
task.measure(() {
ClassElement cls = type.element;
cls.ensureResolved(resolution);
bool isNative = backend.isNative(cls);
_universe.registerTypeInstantiation(type,
constructor: constructor,
isNative: isNative,
byMirrors: mirrorUsage,
isRedirection: isRedirection, onImplemented: (ClassElement cls) {
applyImpact(backend.registerImplementedClass(cls, forResolution: true));
});
if (globalDependency && !mirrorUsage) {
globalDependencies.registerDependency(type.element);
}
if (nativeUsage) {
nativeEnqueuer.onInstantiatedType(type);
}
backend.registerInstantiatedType(type);
// TODO(johnniwinther): Share this reasoning with [Universe].
if (!cls.isAbstract || isNative || mirrorUsage) {
processInstantiatedClass(cls);
}
});
}
bool checkNoEnqueuedInvokedInstanceMethods() {
return strategy.checkEnqueuerConsistency(this);
}
void processInstantiatedClassMembers(ClassElement cls) {
strategy.processInstantiatedClass(this, cls);
}
void processInstantiatedClassMember(ClassElement cls, Element member) {
assert(invariant(member, member.isDeclaration));
if (isProcessed(member)) return;
if (!member.isInstanceMember) return;
String memberName = member.name;
if (member.isField) {
// 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.
if (backend.isNative(cls)) {
openWorld.registerUsedElement(member);
if (_universe.hasInvokedGetter(member, openWorld) ||
_universe.hasInvocation(member, openWorld)) {
addToWorkList(member);
return;
}
if (_universe.hasInvokedSetter(member, openWorld)) {
addToWorkList(member);
return;
}
// Native fields need to go into instanceMembersByName as they
// are virtual instantiation points and escape points.
} else {
// All field initializers must be resolved as they could
// have an observable side-effect (and cannot be tree-shaken
// away).
addToWorkList(member);
return;
}
} else if (member.isFunction) {
FunctionElement function = member;
function.computeType(resolution);
if (function.name == Identifiers.noSuchMethod_) {
registerNoSuchMethod(function);
}
if (function.name == Identifiers.call && !cls.typeVariables.isEmpty) {
_registerCallMethodWithFreeTypeVariables(function);
}
// If there is a property access with the same name as a method we
// need to emit the method.
if (_universe.hasInvokedGetter(function, openWorld)) {
registerClosurizedMember(function);
addToWorkList(function);
return;
}
// Store the member in [instanceFunctionsByName] to catch
// getters on the function.
instanceFunctionsByName
.putIfAbsent(memberName, () => new Set<Element>())
.add(member);
if (_universe.hasInvocation(function, openWorld)) {
addToWorkList(function);
return;
}
} else if (member.isGetter) {
FunctionElement getter = member;
getter.computeType(resolution);
if (_universe.hasInvokedGetter(getter, openWorld)) {
addToWorkList(getter);
return;
}
// We don't know what selectors the returned closure accepts. If
// the set contains any selector we have to assume that it matches.
if (_universe.hasInvocation(getter, openWorld)) {
addToWorkList(getter);
return;
}
} else if (member.isSetter) {
FunctionElement setter = member;
setter.computeType(resolution);
if (_universe.hasInvokedSetter(setter, openWorld)) {
addToWorkList(setter);
return;
}
}
// The element is not yet used. Add it to the list of instance
// members to still be processed.
instanceMembersByName
.putIfAbsent(memberName, () => new Set<Element>())
.add(member);
}
void processInstantiatedClass(ClassElement cls) {
task.measure(() {
if (_processedClasses.contains(cls)) return;
// The class must be resolved to compute the set of all
// supertypes.
cls.ensureResolved(resolution);
void processClass(ClassElement superclass) {
if (_processedClasses.contains(superclass)) return;
_processedClasses.add(superclass);
recentClasses.add(superclass);
superclass.ensureResolved(resolution);
superclass.implementation.forEachMember(processInstantiatedClassMember);
resolution.ensureClassMembers(superclass);
// We only tell the backend once that [superclass] was instantiated, so
// any additional dependencies must be treated as global
// dependencies.
applyImpact(
backend.registerInstantiatedClass(superclass, forResolution: true));
}
ClassElement superclass = cls;
while (superclass != null) {
processClass(superclass);
superclass = superclass.superclass;
}
});
}
void registerDynamicUse(DynamicUse dynamicUse) {
task.measure(() {
if (_universe.registerDynamicUse(dynamicUse)) {
handleUnseenSelector(dynamicUse);
}
});
}
void processSet(
Map<String, Set<Element>> map, String memberName, bool f(Element e)) {
Set<Element> 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<Element>();
Set<Element> remaining = new Set<Element>();
for (Element member in members) {
if (!f(member)) remaining.add(member);
}
map[memberName].addAll(remaining);
}
processInstanceMembers(String n, bool f(Element e)) {
processSet(instanceMembersByName, n, f);
}
processInstanceFunctions(String n, bool f(Element e)) {
processSet(instanceFunctionsByName, n, f);
}
void handleUnseenSelector(DynamicUse universeSelector) {
strategy.processDynamicUse(this, universeSelector);
}
void handleUnseenSelectorInternal(DynamicUse dynamicUse) {
Selector selector = dynamicUse.selector;
String methodName = selector.name;
processInstanceMembers(methodName, (Element member) {
if (dynamicUse.appliesUnnamed(member, openWorld)) {
if (member.isFunction && selector.isGetter) {
registerClosurizedMember(member);
}
addToWorkList(member);
return true;
}
return false;
});
if (selector.isGetter) {
processInstanceFunctions(methodName, (Element member) {
if (dynamicUse.appliesUnnamed(member, openWorld)) {
registerClosurizedMember(member);
return true;
}
return false;
});
}
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [element] must be a declaration element.
*/
void registerStaticUse(StaticUse staticUse) {
strategy.processStaticUse(this, staticUse);
}
void registerStaticUseInternal(StaticUse staticUse) {
Element element = staticUse.element;
assert(invariant(element, element.isDeclaration,
message: "Element ${element} is not the declaration."));
_universe.registerStaticUse(staticUse);
applyImpact(backend.registerStaticUse(element, forResolution: true));
bool addElement = true;
switch (staticUse.kind) {
case StaticUseKind.STATIC_TEAR_OFF:
applyImpact(backend.registerGetOfStaticFunction());
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.
LocalFunctionElement closure = staticUse.element;
if (closure.type.containsTypeVariables) {
universe.closuresWithFreeTypeVariables.add(closure);
}
addElement = false;
break;
case StaticUseKind.SUPER_FIELD_SET:
case StaticUseKind.SUPER_TEAR_OFF:
case StaticUseKind.GENERAL:
case StaticUseKind.DIRECT_USE:
break;
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
_registerInstantiatedType(staticUse.type,
constructor: staticUse.element, globalDependency: false);
break;
case StaticUseKind.REDIRECTION:
_registerInstantiatedType(staticUse.type,
constructor: staticUse.element,
globalDependency: false,
isRedirection: true);
break;
case StaticUseKind.DIRECT_INVOKE:
invariant(
element, 'Direct static use is not supported for resolution.');
break;
}
if (addElement) {
addToWorkList(element);
}
}
void registerTypeUse(TypeUse typeUse) {
strategy.processTypeUse(this, typeUse);
}
void registerTypeUseInternal(TypeUse typeUse) {
DartType type = typeUse.type;
switch (typeUse.kind) {
case TypeUseKind.INSTANTIATION:
_registerInstantiatedType(type, globalDependency: false);
break;
case TypeUseKind.MIRROR_INSTANTIATION:
_registerInstantiatedType(type,
mirrorUsage: true, globalDependency: false);
break;
case TypeUseKind.NATIVE_INSTANTIATION:
_registerInstantiatedType(type,
nativeUsage: true, globalDependency: true);
break;
case TypeUseKind.IS_CHECK:
case TypeUseKind.AS_CAST:
case TypeUseKind.CATCH_TYPE:
_registerIsCheck(type);
break;
case TypeUseKind.CHECKED_MODE_CHECK:
if (options.enableTypeAssertions) {
_registerIsCheck(type);
}
break;
case TypeUseKind.TYPE_LITERAL:
break;
}
}
void _registerIsCheck(DartType type) {
type = _universe.registerIsCheck(type, resolution);
// 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.
assert(!type.isTypeVariable || !type.element.enclosingElement.isTypedef);
}
void _registerCallMethodWithFreeTypeVariables(Element element) {
applyImpact(backend.registerCallMethodWithFreeTypeVariables(element,
forResolution: true));
_universe.callMethodsWithFreeTypeVariables.add(element);
}
void registerClosurizedMember(TypedElement element) {
assert(element.isInstanceMember);
if (element.computeType(resolution).containsTypeVariables) {
applyImpact(backend.registerClosureWithFreeTypeVariables(element,
forResolution: true));
_universe.closuresWithFreeTypeVariables.add(element);
}
applyImpact(backend.registerBoundClosure());
_universe.closurizedMembers.add(element);
}
void forEach(void f(WorkItem work)) {
do {
while (queue.isNotEmpty) {
// TODO(johnniwinther): Find an optimal process order.
WorkItem work = queue.removeLast();
if (!isProcessed(work.element)) {
strategy.processWorkItem(f, work);
registerProcessedElement(work.element);
}
}
List recents = recentClasses.toList(growable: false);
recentClasses.clear();
if (!onQueueEmpty(recents)) recentClasses.addAll(recents);
} while (queue.isNotEmpty || recentClasses.isNotEmpty);
}
void logSummary(log(message)) {
log('Resolved ${processedElements.length} elements.');
nativeEnqueuer.logSummary(log);
}
String toString() => 'Enqueuer($name)';
/// All declaration elements that have been processed by the resolver.
final Set<AstElement> processedElements;
Iterable<Entity> get processedEntities => processedElements;
final Queue<ResolutionWorkItem> queue;
/// Queue of deferred resolution actions to execute when the resolution queue
/// has been emptied.
final Queue<_DeferredAction> deferredQueue;
static const ImpactUseCase IMPACT_USE =
const ImpactUseCase('ResolutionEnqueuer');
ImpactUseCase get impactUse => IMPACT_USE;
bool get isResolutionQueue => true;
bool isProcessed(Element member) => processedElements.contains(member);
/// Returns `true` if [element] has been processed by the resolution enqueuer.
bool hasBeenProcessed(Element element) {
return processedElements.contains(element.analyzableElement.declaration);
}
/// Registers [element] as processed by the resolution enqueuer.
void registerProcessedElement(AstElement element) {
processedElements.add(element);
backend.onElementResolved(element);
}
/**
* Adds [element] to the work list if it has not already been processed.
*
* Returns [true] if the element was actually added to the queue.
*/
bool internalAddToWorkList(Element element) {
if (element.isMalformed) return false;
assert(invariant(element, element is AnalyzableElement,
message: 'Element $element is not analyzable.'));
if (hasBeenProcessed(element)) return false;
if (queueIsClosed) {
throw new SpannableAssertionFailure(
element, "Resolution work list is closed. Trying to add $element.");
}
openWorld.registerUsedElement(element);
ResolutionWorkItem workItem = resolution.createWorkItem(element);
queue.add(workItem);
// Enable isolate support if we start using something from the isolate
// library, or timers for the async library. We exclude constant fields,
// which are ending here because their initializing expression is compiled.
LibraryElement library = element.library;
if (!universe.hasIsolateSupport && (!element.isField || !element.isConst)) {
String uri = library.canonicalUri.toString();
if (uri == 'dart:isolate') {
enableIsolateSupport();
} else if (uri == 'dart:async') {
if (element.name == '_createTimer' ||
element.name == '_createPeriodicTimer') {
// The [:Timer:] class uses the event queue of the isolate
// library, so we make sure that event queue is generated.
enableIsolateSupport();
}
}
}
if (element.isGetter && element.name == Identifiers.runtimeType_) {
// Enable runtime type support if we discover a getter called runtimeType.
// We have to enable runtime type before hitting the codegen, so
// that constructors know whether they need to generate code for
// runtime type.
_universe.hasRuntimeTypeSupport = true;
// TODO(ahe): Record precise dependency here.
applyImpact(backend.registerRuntimeType());
} else if (commonElements.isFunctionApplyMethod(element)) {
_universe.hasFunctionApplySupport = true;
}
return true;
}
void registerNoSuchMethod(Element element) {
backend.registerNoSuchMethod(element);
}
void enableIsolateSupport() {
_universe.hasIsolateSupport = true;
applyImpact(backend.enableIsolateSupport(forResolution: true));
}
/**
* Adds an action to the deferred task queue.
*
* The action is performed the next time the resolution queue has been
* emptied.
*
* The queue is processed in FIFO order.
*/
void addDeferredAction(Element element, void action()) {
if (queueIsClosed) {
throw new SpannableAssertionFailure(
element,
"Resolution work list is closed. "
"Trying to add deferred action for $element");
}
deferredQueue.add(new _DeferredAction(element, action));
}
/// [onQueueEmpty] is called whenever the queue is drained. [recentClasses]
/// contains the set of all classes seen for the first time since
/// [onQueueEmpty] was called last. A return value of [true] indicates that
/// the [recentClasses] have been processed and may be cleared. If [false] is
/// returned, [onQueueEmpty] will be called once the queue is empty again (or
/// still empty) and [recentClasses] will be a superset of the current value.
bool onQueueEmpty(Iterable<ClassElement> recentClasses) {
_emptyDeferredQueue();
return backend.onQueueEmpty(this, recentClasses);
}
void emptyDeferredQueueForTesting() => _emptyDeferredQueue();
void _emptyDeferredQueue() {
while (!deferredQueue.isEmpty) {
_DeferredAction task = deferredQueue.removeFirst();
reporter.withCurrentElement(task.element, task.action);
}
}
void forgetElement(Element element, Compiler compiler) {
_universe.forgetElement(element, compiler);
_processedClasses.remove(element);
instanceMembersByName[element.name]?.remove(element);
instanceFunctionsByName[element.name]?.remove(element);
processedElements.remove(element);
}
}
void removeFromSet(Map<String, Set<Element>> map, Element element) {
Set<Element> set = map[element.name];
if (set == null) return;
set.remove(element);
}
/// Strategy used by the enqueuer to populate the world.
class EnqueuerStrategy {
const EnqueuerStrategy();
/// Process a class instantiated in live code.
void processInstantiatedClass(EnqueuerImpl enqueuer, ClassElement cls) {}
/// Process a static use of and element in live code.
void processStaticUse(EnqueuerImpl enqueuer, StaticUse staticUse) {}
/// Process a type use in live code.
void processTypeUse(EnqueuerImpl enqueuer, TypeUse typeUse) {}
/// Process a dynamic use for a call site in live code.
void processDynamicUse(EnqueuerImpl enqueuer, DynamicUse dynamicUse) {}
/// Check enqueuer consistency after the queue has been closed.
bool checkEnqueuerConsistency(EnqueuerImpl enqueuer) {
enqueuer.task.measure(() {
// Run through the classes and see if we need to enqueue more methods.
for (ClassElement classElement
in enqueuer.universe.directlyInstantiatedClasses) {
for (ClassElement currentClass = classElement;
currentClass != null;
currentClass = currentClass.superclass) {
enqueuer.processInstantiatedClassMembers(currentClass);
}
}
});
return true;
}
/// Process [work] using [f].
void processWorkItem(void f(WorkItem work), WorkItem work) {
f(work);
}
}
/// Strategy that only enqueues directly used elements.
class DirectEnqueuerStrategy extends EnqueuerStrategy {
const DirectEnqueuerStrategy();
void processStaticUse(EnqueuerImpl enqueuer, StaticUse staticUse) {
if (staticUse.kind == StaticUseKind.DIRECT_USE) {
enqueuer.registerStaticUseInternal(staticUse);
}
}
}
/// Strategy used for tree-shaking.
class TreeShakingEnqueuerStrategy extends EnqueuerStrategy {
const TreeShakingEnqueuerStrategy();
@override
void processInstantiatedClass(EnqueuerImpl enqueuer, ClassElement cls) {
cls.implementation.forEachMember(enqueuer.processInstantiatedClassMember);
}
@override
void processStaticUse(EnqueuerImpl enqueuer, StaticUse staticUse) {
enqueuer.registerStaticUseInternal(staticUse);
}
@override
void processTypeUse(EnqueuerImpl enqueuer, TypeUse typeUse) {
enqueuer.registerTypeUseInternal(typeUse);
}
@override
void processDynamicUse(EnqueuerImpl enqueuer, DynamicUse dynamicUse) {
enqueuer.handleUnseenSelectorInternal(dynamicUse);
}
}
class EnqueuerImplImpactVisitor implements WorldImpactVisitor {
final EnqueuerImpl enqueuer;
EnqueuerImplImpactVisitor(this.enqueuer);
@override
void visitDynamicUse(DynamicUse dynamicUse) {
enqueuer.registerDynamicUse(dynamicUse);
}
@override
void visitStaticUse(StaticUse staticUse) {
enqueuer.registerStaticUse(staticUse);
}
@override
void visitTypeUse(TypeUse typeUse) {
enqueuer.registerTypeUse(typeUse);
}
}
typedef void _DeferredActionFunction();
class _DeferredAction {
final Element element;
final _DeferredActionFunction action;
_DeferredAction(this.element, this.action);
}