Reimplement member usage tracking

We now track member usage in terms of static, dynamic and super access
for reads, writes and invocations. The information collected during
closed world computation is now the basis for the potential member usage
in codegen, thus ensuring that we cannot conclude in codegen that for
instance a field is read dynamically when the closed world knows that
it is never read dynamically.

Closes #36516

Change-Id: I3a1cb87c71268c34bcd67e14a035d9d1be324ab0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/100840
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/deferred_load.dart b/pkg/compiler/lib/src/deferred_load.dart
index a412c65..dd23b37 100644
--- a/pkg/compiler/lib/src/deferred_load.dart
+++ b/pkg/compiler/lib/src/deferred_load.dart
@@ -296,7 +296,7 @@
               _collectTypeArgumentDependencies(
                   staticUse.type.typeArguments, dependencies);
               break;
-            case StaticUseKind.INVOKE:
+            case StaticUseKind.STATIC_INVOKE:
             case StaticUseKind.CLOSURE_CALL:
             case StaticUseKind.DIRECT_INVOKE:
               // TODO(johnniwinther): Use rti need data to skip unneeded type
diff --git a/pkg/compiler/lib/src/enqueue.dart b/pkg/compiler/lib/src/enqueue.dart
index aeca0ec..7b7204d 100644
--- a/pkg/compiler/lib/src/enqueue.dart
+++ b/pkg/compiler/lib/src/enqueue.dart
@@ -207,7 +207,7 @@
 
   /// Check enqueuer consistency after the queue has been closed.
   bool checkEnqueuerConsistency(ElementEnvironment elementEnvironment) {
-    task.measure(() {
+    task.measureSubtask('resolution.check', () {
       // Run through the classes and see if we need to enqueue more methods.
       for (ClassEntity classElement
           in worldBuilder.directlyInstantiatedClasses) {
@@ -284,7 +284,7 @@
       {ConstructorEntity constructor,
       bool nativeUsage: false,
       bool globalDependency: false}) {
-    task.measure(() {
+    task.measureSubtask('resolution.typeUse', () {
       _worldBuilder.registerTypeInstantiation(type, _applyClassUse,
           constructor: constructor);
       listener.registerInstantiatedType(type,
@@ -307,7 +307,7 @@
         _reporter.internalError(member,
             'Unenqueued use of $member: ${useSet.iterable(MemberUse.values)}');
       }
-    }, dryRun: true);
+    }, checkEnqueuerConsistency: true);
   }
 
   /// Callback for applying the use of a [member].
@@ -343,14 +343,14 @@
 
   @override
   void processDynamicUse(DynamicUse dynamicUse) {
-    task.measure(() {
+    task.measureSubtask('resolution.dynamicUse', () {
       _worldBuilder.registerDynamicUse(dynamicUse, _applyMemberUse);
     });
   }
 
   @override
   void processConstantUse(ConstantUse constantUse) {
-    task.measure(() {
+    task.measureSubtask('resolution.constantUse', () {
       if (_worldBuilder.registerConstantUse(constantUse)) {
         applyImpact(listener.registerUsedConstant(constantUse.value),
             impactSource: 'constant use');
@@ -361,18 +361,20 @@
 
   @override
   void processStaticUse(StaticUse staticUse) {
-    _worldBuilder.registerStaticUse(staticUse, _applyMemberUse);
-    // TODO(johnniwinther): Add `ResolutionWorldBuilder.registerConstructorUse`
-    // for these:
-    switch (staticUse.kind) {
-      case StaticUseKind.CONSTRUCTOR_INVOKE:
-      case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
-        _registerInstantiatedType(staticUse.type,
-            constructor: staticUse.element, globalDependency: false);
-        break;
-      default:
-        break;
-    }
+    task.measureSubtask('resolution.staticUse', () {
+      _worldBuilder.registerStaticUse(staticUse, _applyMemberUse);
+      // TODO(johnniwinther): Add `ResolutionWorldBuilder.registerConstructorUse`
+      // for these:
+      switch (staticUse.kind) {
+        case StaticUseKind.CONSTRUCTOR_INVOKE:
+        case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
+          _registerInstantiatedType(staticUse.type,
+              constructor: staticUse.element, globalDependency: false);
+          break;
+        default:
+          break;
+      }
+    });
   }
 
   @override
diff --git a/pkg/compiler/lib/src/js_backend/enqueuer.dart b/pkg/compiler/lib/src/js_backend/enqueuer.dart
index 0057cb2..ba98487 100644
--- a/pkg/compiler/lib/src/js_backend/enqueuer.dart
+++ b/pkg/compiler/lib/src/js_backend/enqueuer.dart
@@ -107,7 +107,7 @@
 
   void _registerInstantiatedType(InterfaceType type,
       {bool nativeUsage: false}) {
-    task.measure(() {
+    task.measureSubtask('codegen.typeUse', () {
       _worldBuilder.registerTypeInstantiation(type, _applyClassUse);
       listener.registerInstantiatedType(type, nativeUsage: nativeUsage);
     });
@@ -127,7 +127,7 @@
         failedAt(member,
             'Unenqueued use of $member: ${useSet.iterable(MemberUse.values)}');
       }
-    }, dryRun: true);
+    }, checkEnqueuerConsistency: true);
   }
 
   /// Callback for applying the use of a [cls].
@@ -160,26 +160,28 @@
 
   @override
   void processDynamicUse(DynamicUse dynamicUse) {
-    task.measure(() {
+    task.measureSubtask('codegen.dynamicUse', () {
       _worldBuilder.registerDynamicUse(dynamicUse, _applyMemberUse);
     });
   }
 
   @override
   void processStaticUse(StaticUse staticUse) {
-    _worldBuilder.registerStaticUse(staticUse, _applyMemberUse);
-    switch (staticUse.kind) {
-      case StaticUseKind.CONSTRUCTOR_INVOKE:
-      case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
-        processTypeUse(new TypeUse.instantiation(staticUse.type));
-        break;
-      case StaticUseKind.INLINING:
-        // TODO(johnniwinther): Should this be tracked with _MemberUsage ?
-        listener.registerUsedElement(staticUse.element);
-        break;
-      default:
-        break;
-    }
+    task.measureSubtask('codegen.staticUse', () {
+      _worldBuilder.registerStaticUse(staticUse, _applyMemberUse);
+      switch (staticUse.kind) {
+        case StaticUseKind.CONSTRUCTOR_INVOKE:
+        case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
+          processTypeUse(new TypeUse.instantiation(staticUse.type));
+          break;
+        case StaticUseKind.INLINING:
+          // TODO(johnniwinther): Should this be tracked with _MemberUsage ?
+          listener.registerUsedElement(staticUse.element);
+          break;
+        default:
+          break;
+      }
+    });
   }
 
   @override
@@ -229,7 +231,7 @@
 
   @override
   void processConstantUse(ConstantUse constantUse) {
-    task.measure(() {
+    task.measureSubtask('codegen.constantUse', () {
       if (_worldBuilder.registerConstantUse(constantUse)) {
         applyImpact(listener.registerUsedConstant(constantUse.value));
         _recentConstants = true;
diff --git a/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart
index d056518..e57d145 100644
--- a/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart
+++ b/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart
@@ -53,7 +53,7 @@
       _closedWorld.elementEnvironment;
 
   bool needsSuperGetter(FunctionEntity element) =>
-      _codegenWorld.methodsNeedingSuperGetter.contains(element);
+      _codegenWorld.methodsNeedsSuperGetter(element);
 
   /// Generates stubs to fill in missing optional named or positional arguments
   /// and missing type arguments.  Returns `null` if no stub is needed.
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
index 3273ea7..314051a 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
@@ -477,7 +477,7 @@
 
   void _addJsInteropStubs(LibrariesMap librariesMap) {
     if (_classes.containsKey(_commonElements.objectClass)) {
-      var toStringInvocation = _namer.invocationName(Selectors.toString_);
+      js.Name toStringInvocation = _namer.invocationName(Selectors.toString_);
       // TODO(jacobr): register toString as used so that it is always accessible
       // from JavaScript.
       _classes[_commonElements.objectClass].callStubs.add(_buildStubMethod(
@@ -492,22 +492,23 @@
     // a regular getter that returns a JavaScript function and tearing off
     // a method in the case where there exist multiple JavaScript classes
     // that conflict on whether the member is a getter or a method.
-    var interceptorClass = _classes[_commonElements.jsJavaScriptObjectClass];
-    var stubNames = new Set<String>();
+    Class interceptorClass = _classes[_commonElements.jsJavaScriptObjectClass];
+    Set<String> stubNames = {};
     librariesMap
         .forEach((LibraryEntity library, List<ClassEntity> classElements, _) {
       for (ClassEntity cls in classElements) {
         if (_nativeData.isJsInteropClass(cls)) {
           _elementEnvironment.forEachLocalClassMember(cls,
               (MemberEntity member) {
-            var jsName = _nativeData.computeUnescapedJSInteropName(member.name);
+            String jsName =
+                _nativeData.computeUnescapedJSInteropName(member.name);
             if (!member.isInstanceMember) return;
             if (member.isGetter || member.isField || member.isFunction) {
-              var selectors =
+              Iterable<Selector> selectors =
                   _codegenWorld.getterInvocationsByName(member.name);
               if (selectors != null && !selectors.isEmpty) {
-                for (var selector in selectors.keys) {
-                  var stubName = _namer.invocationName(selector);
+                for (Selector selector in selectors) {
+                  js.Name stubName = _namer.invocationName(selector);
                   if (stubNames.add(stubName.key)) {
                     interceptorClass.callStubs.add(_buildStubMethod(stubName,
                         js.js('function(obj) { return obj.# }', [jsName]),
@@ -518,7 +519,7 @@
             }
 
             if (member.isSetter || (member.isField && !member.isConst)) {
-              var selectors =
+              Iterable<Selector> selectors =
                   _codegenWorld.setterInvocationsByName(member.name);
               if (selectors != null && !selectors.isEmpty) {
                 var stubName = _namer.setterForMember(member);
@@ -765,11 +766,11 @@
           assert(!field.needsUncheckedSetter);
           FieldEntity element = field.element;
           js.Expression code = _generatedCode[element];
+          assert(code != null, "No setter code for field: $field");
           if (code == null) {
-            // TODO(johnniwinther): Static types are not honoured in the dynamic
-            // uses created in codegen, leading to dead code, as known by the
-            // closed world computation, being triggered by the codegen
-            // enqueuer. We cautiously generate an empty function for this case.
+            // This should never occur because codegen member usage is now
+            // limited by closed world member usage. In the case we've missed a
+            // spot we cautiously generate an empty function.
             code = js.js("function() {}");
           }
           js.Name name = _namer.deriveSetterName(field.accessorName);
@@ -901,9 +902,8 @@
         isClosureCallMethod = true;
       } else {
         // Careful with operators.
-        canTearOff = _codegenWorld.hasInvokedGetter(element);
-        assert(canTearOff ||
-            !_codegenWorld.methodsNeedingSuperGetter.contains(element));
+        canTearOff = _codegenWorld.hasInvokedGetter(element) ||
+            _codegenWorld.methodsNeedsSuperGetter(element);
         tearOffName = _namer.getterForElement(element);
       }
     }
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index f83ecba..633e642 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -1084,11 +1084,12 @@
     js.Expression code;
     if (field.isElided) {
       ConstantValue constantValue = field.constantValue;
+      assert(
+          constantValue != null, "No constant value for elided field: $field");
       if (constantValue == null) {
-        // TODO(johnniwinther): Static types are not honoured in the dynamic
-        // uses created in codegen, leading to dead code, as known by the closed
-        // world computation, being triggered by the codegen enqueuer. We
-        // cautiously generate a null constant for this case.
+        // This should never occur because codegen member usage is now limited
+        // by closed world member usage. In the case we've missed a spot we
+        // cautiously generate a null constant.
         constantValue = new NullConstantValue();
       }
       code = js.js(
diff --git a/pkg/compiler/lib/src/js_model/js_world.dart b/pkg/compiler/lib/src/js_model/js_world.dart
index d08654f..6ed9e82 100644
--- a/pkg/compiler/lib/src/js_model/js_world.dart
+++ b/pkg/compiler/lib/src/js_model/js_world.dart
@@ -32,6 +32,7 @@
 import '../universe/class_hierarchy.dart';
 import '../universe/class_set.dart';
 import '../universe/function_set.dart' show FunctionSet;
+import '../universe/member_usage.dart';
 import '../universe/selector.dart';
 import '../world.dart';
 import 'element_map.dart';
@@ -93,6 +94,8 @@
   final OutputUnitData outputUnitData;
   Sorter _sorter;
 
+  final Map<MemberEntity, MemberAccess> memberAccess;
+
   JsClosedWorld(
       this.elementMap,
       this.nativeData,
@@ -113,7 +116,8 @@
       this.annotationsData,
       this.globalLocalsMap,
       this.closureDataLookup,
-      this.outputUnitData) {
+      this.outputUnitData,
+      this.memberAccess) {
     _abstractValueDomain = abstractValueStrategy.createDomain(this);
   }
 
@@ -167,6 +171,9 @@
     OutputUnitData outputUnitData =
         new OutputUnitData.readFromDataSource(source);
 
+    Map<MemberEntity, MemberAccess> memberAccess =
+        source.readMemberMap(() => new MemberAccess.readFromDataSource(source));
+
     source.end(tag);
 
     return new JsClosedWorld(
@@ -189,7 +196,8 @@
         annotationsData,
         globalLocalsMap,
         closureData,
-        outputUnitData);
+        outputUnitData,
+        memberAccess);
   }
 
   /// Serializes this [JsClosedWorld] to [sink].
@@ -217,6 +225,8 @@
     annotationsData.writeToDataSink(sink);
     closureDataLookup.writeToDataSink(sink);
     outputUnitData.writeToDataSink(sink);
+    sink.writeMemberMap(
+        memberAccess, (MemberAccess access) => access.writeToDataSink(sink));
     sink.end(tag);
   }
 
@@ -539,6 +549,11 @@
     return elementEnvironment.isMixinApplication(cls) &&
         !elementEnvironment.isUnnamedMixinApplication(cls);
   }
+
+  @override
+  MemberAccess getMemberAccess(MemberEntity member) {
+    return memberAccess[member];
+  }
 }
 
 class KernelSorter implements Sorter {
diff --git a/pkg/compiler/lib/src/js_model/js_world_builder.dart b/pkg/compiler/lib/src/js_model/js_world_builder.dart
index 69a71f6..a9c3bfe 100644
--- a/pkg/compiler/lib/src/js_model/js_world_builder.dart
+++ b/pkg/compiler/lib/src/js_model/js_world_builder.dart
@@ -29,6 +29,7 @@
 import '../universe/class_hierarchy.dart';
 import '../universe/class_set.dart';
 import '../universe/feature.dart';
+import '../universe/member_usage.dart';
 import '../universe/selector.dart';
 import '../world.dart';
 import 'closure.dart';
@@ -202,6 +203,11 @@
     OutputUnitData outputUnitData =
         _convertOutputUnitData(map, kOutputUnitData, closureData);
 
+    Map<MemberEntity, MemberAccess> memberAccess = map.toBackendMemberMap(
+        closedWorld.liveMemberUsage,
+        (MemberUsage usage) =>
+            new MemberAccess(usage.reads, usage.writes, usage.invokes));
+
     return new JsClosedWorld(
         _elementMap,
         nativeData,
@@ -226,7 +232,8 @@
         annotationsData,
         _globalLocalsMap,
         closureData,
-        outputUnitData);
+        outputUnitData,
+        memberAccess);
   }
 
   BackendUsage _convertBackendUsage(
@@ -487,7 +494,7 @@
         map.toBackendLibrary,
         convertClassMap,
         convertMemberMap,
-        (m) => convertMap<ConstantValue, OutputUnit>(
+        (m) => convertMap<ConstantValue, OutputUnit, OutputUnit>(
             m, map.toBackendConstant, (v) => v));
   }
 }
@@ -633,20 +640,20 @@
     return convertMap(map, toBackendClass, convert);
   }
 
-  Map<MemberEntity, V> toBackendMemberMap<V>(
-      Map<MemberEntity, V> map, V convert(V value)) {
+  Map<MemberEntity, V2> toBackendMemberMap<V1, V2>(
+      Map<MemberEntity, V1> map, V2 convert(V1 value)) {
     return convertMap(map, toBackendMember, convert);
   }
 }
 
 E identity<E>(E element) => element;
 
-Map<K, V> convertMap<K, V>(
-    Map<K, V> map, K convertKey(K key), V convertValue(V value)) {
-  Map<K, V> newMap = <K, V>{};
-  map.forEach((K key, V value) {
+Map<K, V2> convertMap<K, V1, V2>(
+    Map<K, V1> map, K convertKey(K key), V2 convertValue(V1 value)) {
+  Map<K, V2> newMap = <K, V2>{};
+  map.forEach((K key, V1 value) {
     K newKey = convertKey(key);
-    V newValue = convertValue(value);
+    V2 newValue = convertValue(value);
     if (newKey != null && newValue != null) {
       // Entities that are not used don't have a corresponding backend entity.
       newMap[newKey] = newValue;
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index 3ba7b09..2d2a01ec 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -1979,6 +1979,8 @@
       // for this to work.
       assert(selector.applies(target),
           failedAt(node, '$selector does not apply to $target'));
+      assert(!selector.isGetter && !selector.isSetter,
+          "Unexpected direct invocation selector: $selector.");
       _registry.registerStaticUse(new StaticUse.directInvoke(
           target, selector.callStructure, node.typeArguments));
     } else {
diff --git a/pkg/compiler/lib/src/universe/codegen_world_builder.dart b/pkg/compiler/lib/src/universe/codegen_world_builder.dart
index ec55791..b719421 100644
--- a/pkg/compiler/lib/src/universe/codegen_world_builder.dart
+++ b/pkg/compiler/lib/src/universe/codegen_world_builder.dart
@@ -46,9 +46,9 @@
 
   Map<Selector, SelectorConstraints> invocationsByName(String name);
 
-  Map<Selector, SelectorConstraints> getterInvocationsByName(String name);
+  Iterable<Selector> getterInvocationsByName(String name);
 
-  Map<Selector, SelectorConstraints> setterInvocationsByName(String name);
+  Iterable<Selector> setterInvocationsByName(String name);
 
   void forEachInvokedName(
       f(String name, Map<Selector, SelectorConstraints> selectors));
@@ -68,7 +68,7 @@
   /// All directly or indirectly instantiated classes.
   Iterable<ClassEntity> get instantiatedClasses;
 
-  Iterable<FunctionEntity> get methodsNeedingSuperGetter;
+  bool methodsNeedsSuperGetter(FunctionEntity function);
 
   /// The calls [f] for all static fields.
   void forEachStaticField(void Function(FieldEntity) f);
@@ -111,8 +111,6 @@
   /// Classes implemented by directly instantiated classes.
   final Set<ClassEntity> _implementedClasses = new Set<ClassEntity>();
 
-  final Set<FunctionEntity> _methodsNeedingSuperGetter =
-      new Set<FunctionEntity>();
   final Map<String, Map<Selector, SelectorConstraints>> _invokedNames =
       <String, Map<Selector, SelectorConstraints>>{};
   final Map<String, Map<Selector, SelectorConstraints>> _invokedGetters =
@@ -129,14 +127,22 @@
   final Map<MemberEntity, MemberUsage> _memberUsage =
       <MemberEntity, MemberUsage>{};
 
-  /// Map containing instance members of live classes that are not yet live
-  /// themselves.
-  final Map<String, Set<MemberUsage>> _instanceMembersByName =
+  /// Map containing instance members of live classes that have not yet been
+  /// fully invoked dynamically.
+  ///
+  /// A method is fully invoked if all is optional parameter have been passed
+  /// in some invocation.
+  final Map<String, Set<MemberUsage>> _invokableInstanceMembersByName =
       <String, Set<MemberUsage>>{};
 
-  /// Map containing instance methods of live classes that are not yet
-  /// closurized.
-  final Map<String, Set<MemberUsage>> _instanceFunctionsByName =
+  /// Map containing instance members of live classes that have not yet been
+  /// read from dynamically.
+  final Map<String, Set<MemberUsage>> _readableInstanceMembersByName =
+      <String, Set<MemberUsage>>{};
+
+  /// Map containing instance members of live classes that have not yet been
+  /// written to dynamically.
+  final Map<String, Set<MemberUsage>> _writableInstanceMembersByName =
       <String, Set<MemberUsage>>{};
 
   final Set<DartType> _isChecks = new Set<DartType>();
@@ -208,35 +214,51 @@
     return false;
   }
 
-  bool hasInvocation(MemberEntity member) {
-    return _hasMatchingSelector(
-        _invokedNames[member.name], member, _closedWorld);
+  Iterable<CallStructure> _getMatchingCallStructures(
+      Map<Selector, SelectorConstraints> selectors, MemberEntity member) {
+    if (selectors == null) return const <CallStructure>[];
+    Set<CallStructure> callStructures;
+    for (Selector selector in selectors.keys) {
+      if (selector.appliesUnnamed(member)) {
+        SelectorConstraints masks = selectors[selector];
+        if (masks.canHit(member, selector.memberName, _closedWorld)) {
+          callStructures ??= new Set<CallStructure>();
+          callStructures.add(selector.callStructure);
+        }
+      }
+    }
+    return callStructures ?? const <CallStructure>[];
   }
 
-  bool hasInvokedGetter(MemberEntity member) {
-    return _hasMatchingSelector(
-            _invokedGetters[member.name], member, _closedWorld) ||
-        member.isFunction && _methodsNeedingSuperGetter.contains(member);
+  Iterable<CallStructure> _getInvocationCallStructures(MemberEntity member) {
+    return _getMatchingCallStructures(_invokedNames[member.name], member);
   }
 
-  bool hasInvokedSetter(MemberEntity member) {
+  bool _hasInvokedGetter(MemberEntity member) {
+    return _hasMatchingSelector(
+        _invokedGetters[member.name], member, _closedWorld);
+  }
+
+  bool _hasInvokedSetter(MemberEntity member) {
     return _hasMatchingSelector(
         _invokedSetters[member.name], member, _closedWorld);
   }
 
-  bool registerDynamicUse(
+  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)) {
+    void _process(
+        Map<String, Set<MemberUsage>> memberMap,
+        EnumSet<MemberUse> action(MemberUsage usage),
+        bool shouldBeRemoved(MemberUsage usage)) {
       _processSet(memberMap, methodName, (MemberUsage usage) {
         if (selector.appliesUnnamed(usage.entity) &&
             _selectorConstraintsStrategy.appliedUnnamed(
                 dynamicUse, usage.entity, _closedWorld)) {
           memberUsed(usage.entity, action(usage));
-          return true;
+          return shouldBeRemoved(usage);
         }
         return false;
       });
@@ -247,27 +269,31 @@
         registerDynamicInvocation(
             dynamicUse.selector, dynamicUse.typeArguments);
         if (_registerNewSelector(dynamicUse, _invokedNames)) {
-          // We don't track parameters in the codegen world builder, so we
-          // pass `null` instead of the concrete call structure.
-          _process(_instanceMembersByName, (m) => m.invoke(null));
-          return true;
+          _process(
+              _invokableInstanceMembersByName,
+              (m) => m.invoke(Accesses.dynamicAccess, selector.callStructure),
+              // If not all optional parameters have been passed in invocations
+              // we must keep the member in [_invokableInstanceMembersByName].
+              (u) => !u.hasPendingDynamicInvoke);
         }
         break;
       case DynamicUseKind.GET:
         if (_registerNewSelector(dynamicUse, _invokedGetters)) {
-          _process(_instanceMembersByName, (m) => m.read());
-          _process(_instanceFunctionsByName, (m) => m.read());
-          return true;
+          _process(
+              _readableInstanceMembersByName,
+              (m) => m.read(Accesses.dynamicAccess),
+              (u) => !u.hasPendingDynamicRead);
         }
         break;
       case DynamicUseKind.SET:
         if (_registerNewSelector(dynamicUse, _invokedSetters)) {
-          _process(_instanceMembersByName, (m) => m.write());
-          return true;
+          _process(
+              _writableInstanceMembersByName,
+              (m) => m.write(Accesses.dynamicAccess),
+              (u) => !u.hasPendingDynamicWrite);
         }
         break;
     }
-    return false;
   }
 
   bool _registerNewSelector(DynamicUse dynamicUse,
@@ -296,32 +322,43 @@
     MemberUsage usage = _getMemberUsage(element, useSet);
     switch (staticUse.kind) {
       case StaticUseKind.STATIC_TEAR_OFF:
-        useSet.addAll(usage.read());
+        useSet.addAll(usage.read(Accesses.staticAccess));
         break;
-      case StaticUseKind.FIELD_GET:
-      case StaticUseKind.FIELD_SET:
+      case StaticUseKind.INSTANCE_FIELD_GET:
+      case StaticUseKind.INSTANCE_FIELD_SET:
       case StaticUseKind.CALL_METHOD:
         // 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.INVOKE:
+      case StaticUseKind.SUPER_INVOKE:
         registerStaticInvocation(staticUse);
-        // We don't track parameters in the codegen world builder, so we
-        // pass `null` instead of the concrete call structure.
-        useSet.addAll(usage.invoke(null));
+        useSet.addAll(
+            usage.invoke(Accesses.superAccess, staticUse.callStructure));
+        break;
+      case StaticUseKind.STATIC_INVOKE:
+        registerStaticInvocation(staticUse);
+        useSet.addAll(
+            usage.invoke(Accesses.staticAccess, staticUse.callStructure));
         break;
       case StaticUseKind.SUPER_FIELD_SET:
-      case StaticUseKind.SET:
-        useSet.addAll(usage.write());
+        useSet.addAll(usage.write(Accesses.superAccess));
+        break;
+      case StaticUseKind.SUPER_SETTER_SET:
+        useSet.addAll(usage.write(Accesses.superAccess));
+        break;
+      case StaticUseKind.STATIC_SET:
+        useSet.addAll(usage.write(Accesses.staticAccess));
         break;
       case StaticUseKind.SUPER_TEAR_OFF:
-        _methodsNeedingSuperGetter.add(element);
-        useSet.addAll(usage.read());
+        useSet.addAll(usage.read(Accesses.superAccess));
         break;
-      case StaticUseKind.GET:
-        useSet.addAll(usage.read());
+      case StaticUseKind.SUPER_GET:
+        useSet.addAll(usage.read(Accesses.superAccess));
+        break;
+      case StaticUseKind.STATIC_GET:
+        useSet.addAll(usage.read(Accesses.staticAccess));
         break;
       case StaticUseKind.FIELD_INIT:
         useSet.addAll(usage.init());
@@ -333,14 +370,15 @@
       case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
         // We don't track parameters in the codegen world builder, so we
         // pass `null` instead of the concrete call structure.
-        useSet.addAll(usage.invoke(null));
+        useSet.addAll(
+            usage.invoke(Accesses.staticAccess, staticUse.callStructure));
         break;
       case StaticUseKind.DIRECT_INVOKE:
         MemberEntity member = staticUse.element;
         // We don't track parameters in the codegen world builder, so we
         // pass `null` instead of the concrete call structure.
-        useSet.addAll(usage.invoke(null));
-        _instanceMembersByName[usage.entity.name]?.remove(usage);
+        useSet.addAll(
+            usage.invoke(Accesses.staticAccess, staticUse.callStructure));
         if (staticUse.typeArguments?.isNotEmpty ?? false) {
           registerDynamicInvocation(
               new Selector.call(member.memberName, staticUse.callStructure),
@@ -361,85 +399,99 @@
   }
 
   void processClassMembers(ClassEntity cls, MemberUsedCallback memberUsed,
-      {bool dryRun: false}) {
+      {bool checkEnqueuerConsistency: false}) {
     _elementEnvironment.forEachClassMember(cls,
         (ClassEntity cls, MemberEntity member) {
-      _processInstantiatedClassMember(cls, member, memberUsed, dryRun: dryRun);
+      _processInstantiatedClassMember(cls, member, memberUsed,
+          checkEnqueuerConsistency: checkEnqueuerConsistency);
     });
   }
 
   void _processInstantiatedClassMember(
       ClassEntity cls, MemberEntity member, MemberUsedCallback memberUsed,
-      {bool dryRun: false}) {
+      {bool checkEnqueuerConsistency: false}) {
     if (!member.isInstanceMember) return;
     EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
-    _getMemberUsage(member, useSet);
+    MemberUsage usage = _getMemberUsage(member, useSet);
     if (useSet.isNotEmpty) {
-      memberUsed(member, useSet);
+      if (checkEnqueuerConsistency) {
+        throw new SpannableAssertionFailure(member,
+            'Unenqueued usage of $member: \nbefore: <none>\nafter : $usage');
+      } else {
+        memberUsed(member, useSet);
+      }
     }
   }
 
   MemberUsage _getMemberUsage(MemberEntity member, EnumSet<MemberUse> useSet,
-      {bool dryRun: false}) {
+      {bool checkEnqueuerConsistency: false}) {
     // TODO(johnniwinther): Change [TypeMask] to not apply to a superclass
     // member unless the class has been instantiated. Similar to
     // [StrongModeConstraint].
     MemberUsage usage = _memberUsage[member];
     if (usage == null) {
+      MemberAccess potentialAccess = _closedWorld.getMemberAccess(member);
       if (member.isInstanceMember) {
         String memberName = member.name;
         ClassEntity cls = member.enclosingClass;
         bool isNative = _nativeBasicData.isNativeClass(cls);
-        usage = new MemberUsage(member);
+        usage = new MemberUsage(member, potentialAccess: potentialAccess);
         if (member.isField && !isNative) {
           useSet.addAll(usage.init());
         }
         if (member is JSignatureMethod) {
-          // We mark signature methods as "always used" to prevent them from being
-          // optimized away.
+          // We mark signature methods as "always used" to prevent them from
+          // being optimized away.
           // TODO(johnniwinther): Make this a part of the regular enqueueing.
-          useSet.addAll(usage.invoke(CallStructure.NO_ARGS));
+          useSet.addAll(
+              usage.invoke(Accesses.dynamicAccess, CallStructure.NO_ARGS));
         }
 
-        if (!usage.hasRead && hasInvokedGetter(member)) {
-          useSet.addAll(usage.read());
+        if (usage.hasPendingDynamicRead && _hasInvokedGetter(member)) {
+          useSet.addAll(usage.read(Accesses.dynamicAccess));
         }
-        if (!usage.hasWrite && hasInvokedSetter(member)) {
-          useSet.addAll(usage.write());
+        if (usage.hasPendingDynamicWrite && _hasInvokedSetter(member)) {
+          useSet.addAll(usage.write(Accesses.dynamicAccess));
         }
-        if (!usage.hasInvoke && hasInvocation(member)) {
-          // We don't track parameters in the codegen world builder, so we
-          // pass `null` instead of the concrete call structures.
-          useSet.addAll(usage.invoke(null));
+        if (usage.hasPendingDynamicInvoke) {
+          Iterable<CallStructure> callStructures =
+              _getInvocationCallStructures(member);
+          for (CallStructure callStructure in callStructures) {
+            useSet.addAll(usage.invoke(Accesses.dynamicAccess, callStructure));
+            if (!usage.hasPendingDynamicInvoke) {
+              break;
+            }
+          }
         }
 
-        if (!dryRun) {
-          if (usage.hasPendingClosurizationUse) {
-            // Store the member in [instanceFunctionsByName] to catch
-            // getters on the function.
-            _instanceFunctionsByName
-                .putIfAbsent(usage.entity.name, () => new Set<MemberUsage>())
+        if (!checkEnqueuerConsistency) {
+          if (usage.hasPendingDynamicInvoke) {
+            _invokableInstanceMembersByName
+                .putIfAbsent(memberName, () => {})
                 .add(usage);
           }
-          if (usage.hasPendingNormalUse) {
-            // The element is not yet used. Add it to the list of instance
-            // members to still be processed.
-            _instanceMembersByName
-                .putIfAbsent(memberName, () => new Set<MemberUsage>())
+          if (usage.hasPendingDynamicRead) {
+            _readableInstanceMembersByName
+                .putIfAbsent(memberName, () => {})
+                .add(usage);
+          }
+          if (usage.hasPendingDynamicWrite) {
+            _writableInstanceMembersByName
+                .putIfAbsent(memberName, () => {})
                 .add(usage);
           }
         }
       } else {
-        usage = new MemberUsage(member);
+        usage = new MemberUsage(member, potentialAccess: potentialAccess);
         if (member.isField) {
           useSet.addAll(usage.init());
         }
       }
-      if (!dryRun) {
+      if (!checkEnqueuerConsistency) {
         _memberUsage[member] = usage;
       }
     } else {
-      if (dryRun) {
+      if (checkEnqueuerConsistency) {
         usage = usage.clone();
       }
     }
@@ -521,7 +573,6 @@
         constTypeLiterals: _constTypeLiterals,
         directlyInstantiatedClasses: directlyInstantiatedClasses,
         typeVariableTypeLiterals: typeVariableTypeLiterals,
-        methodsNeedingSuperGetter: _methodsNeedingSuperGetter,
         instantiatedClasses: instantiatedClasses,
         isChecks: _isChecks,
         instantiatedTypes: _instantiatedTypes,
@@ -550,9 +601,6 @@
   final Iterable<TypeVariableType> typeVariableTypeLiterals;
 
   @override
-  final Iterable<FunctionEntity> methodsNeedingSuperGetter;
-
-  @override
   final Iterable<ClassEntity> instantiatedClasses;
 
   @override
@@ -580,7 +628,6 @@
       {this.constTypeLiterals,
       this.directlyInstantiatedClasses,
       this.typeVariableTypeLiterals,
-      this.methodsNeedingSuperGetter,
       this.instantiatedClasses,
       this.isChecks,
       this.instantiatedTypes,
@@ -754,15 +801,23 @@
 
   @override
   bool hasInvokedGetter(MemberEntity member) {
-    return _hasMatchingSelector(
-            _invokedGetters[member.name], member, _closedWorld) ||
-        member.isFunction && methodsNeedingSuperGetter.contains(member);
+    MemberUsage memberUsage = _liveMemberUsage[member];
+    if (memberUsage == null) return false;
+    return memberUsage.reads.contains(Access.dynamicAccess);
+  }
+
+  @override
+  bool methodsNeedsSuperGetter(FunctionEntity function) {
+    MemberUsage memberUsage = _liveMemberUsage[function];
+    if (memberUsage == null) return false;
+    return memberUsage.reads.contains(Access.superAccess);
   }
 
   @override
   bool hasInvokedSetter(MemberEntity member) {
-    return _hasMatchingSelector(
-        _invokedSetters[member.name], member, _closedWorld);
+    MemberUsage memberUsage = _liveMemberUsage[member];
+    if (memberUsage == null) return false;
+    return memberUsage.writes.contains(Access.dynamicAccess);
   }
 
   Map<Selector, SelectorConstraints> _asUnmodifiable(
@@ -777,13 +832,13 @@
   }
 
   @override
-  Map<Selector, SelectorConstraints> getterInvocationsByName(String name) {
-    return _asUnmodifiable(_invokedGetters[name]);
+  Iterable<Selector> getterInvocationsByName(String name) {
+    return _invokedGetters[name]?.keys;
   }
 
   @override
-  Map<Selector, SelectorConstraints> setterInvocationsByName(String name) {
-    return _asUnmodifiable(_invokedSetters[name]);
+  Iterable<Selector> setterInvocationsByName(String name) {
+    return _invokedSetters[name]?.keys;
   }
 
   @override
diff --git a/pkg/compiler/lib/src/universe/member_usage.dart b/pkg/compiler/lib/src/universe/member_usage.dart
index f80d47c..a322bc3 100644
--- a/pkg/compiler/lib/src/universe/member_usage.dart
+++ b/pkg/compiler/lib/src/universe/member_usage.dart
@@ -7,6 +7,8 @@
 import '../common.dart';
 import '../constants/values.dart';
 import '../elements/entities.dart';
+import '../js_model/closure.dart';
+import '../serialization/serialization.dart';
 import '../util/enumset.dart';
 import 'call_structure.dart';
 
@@ -37,6 +39,10 @@
 
 /// Registry for the observed use of a member [entity] in the open world.
 abstract class MemberUsage extends AbstractUsage<MemberUse> {
+  /// Constant empty access set used as the potential access set for impossible
+  /// accesses, for instance writing to a final field or invoking a setter.
+  static const EnumSet<Access> emptySet = const EnumSet.fixed(0);
+
   final MemberEntity entity;
 
   MemberUsage.internal(this.entity) : super();
@@ -44,82 +50,140 @@
   MemberUsage.cloned(this.entity, EnumSet<MemberUse> pendingUse)
       : super.cloned(pendingUse);
 
-  factory MemberUsage(MemberEntity member, {bool trackParameters: false}) {
+  factory MemberUsage(MemberEntity member, {MemberAccess potentialAccess}) {
+    /// Create the set of potential accesses to [member], limited to [original]
+    /// if provided.
+    EnumSet<Access> createPotentialAccessSet(EnumSet<Access> original) {
+      if (original != null) {
+        if (original.isEmpty) return emptySet;
+        return original.clone();
+      }
+      if (member.isTopLevel || member.isStatic || member.isConstructor) {
+        // TODO(johnniwinther): Track super constructor invocations?
+        return new EnumSet.fromValues([Access.staticAccess]);
+      } else if (member.isInstanceMember) {
+        return new EnumSet.fromValues(Access.values);
+      } else {
+        assert(member is JRecordField, "Unexpected member: $member");
+        return new EnumSet();
+      }
+    }
+
+    /// Create the set of potential read accesses to [member], limited to reads
+    /// in [potentialAccess] if provided.
+    EnumSet<Access> createPotentialReads() {
+      return createPotentialAccessSet(potentialAccess?.reads);
+    }
+
+    /// Create the set of potential write accesses to [member], limited to
+    /// writes in [potentialAccess] if provided.
+    EnumSet<Access> createPotentialWrites() {
+      return createPotentialAccessSet(potentialAccess?.writes);
+    }
+
+    /// Create the set of potential invocation accesses to [member], limited to
+    /// invocations in [potentialAccess] if provided.
+    EnumSet<Access> createPotentialInvokes() {
+      return createPotentialAccessSet(potentialAccess?.invokes);
+    }
+
     if (member.isField) {
       if (member.isAssignable) {
-        return new FieldUsage(member);
+        return new FieldUsage(member,
+            potentialReads: createPotentialReads(),
+            potentialWrites: createPotentialWrites(),
+            potentialInvokes: createPotentialInvokes());
       } else {
-        return new FinalFieldUsage(member);
+        return new FieldUsage(member,
+            potentialReads: createPotentialReads(),
+            potentialWrites: emptySet,
+            potentialInvokes: createPotentialInvokes());
       }
     } else if (member.isGetter) {
-      return new GetterUsage(member);
+      return new PropertyUsage(member,
+          potentialReads: createPotentialReads(),
+          potentialWrites: emptySet,
+          potentialInvokes: createPotentialInvokes());
     } else if (member.isSetter) {
-      return new SetterUsage(member);
+      return new PropertyUsage(member,
+          potentialReads: emptySet,
+          potentialWrites: createPotentialWrites(),
+          potentialInvokes: emptySet);
     } else if (member.isConstructor) {
-      if (trackParameters) {
-        return new ParameterTrackingConstructorUsage(member);
-      } else {
-        return new ConstructorUsage(member);
-      }
-    } else if (member.isConstructor) {
-      if (trackParameters) {
-        return new ParameterTrackingConstructorUsage(member);
-      } else {
-        return new ConstructorUsage(member);
-      }
+      return new MethodUsage(member,
+          potentialReads: emptySet, potentialInvokes: createPotentialInvokes());
     } else {
       assert(member is FunctionEntity,
           failedAt(member, "Unexpected member: $member"));
-      if (trackParameters) {
-        return new ParameterTrackingFunctionUsage(member);
-      } else {
-        return new FunctionUsage(member);
-      }
+      return new MethodUsage(member,
+          potentialReads: createPotentialReads(),
+          potentialInvokes: createPotentialInvokes());
     }
   }
 
   /// `true` if [entity] has been initialized.
   bool get hasInit => true;
 
+  /// The set of constant initial values for a field.
   Iterable<ConstantValue> get initialConstants => null;
 
   /// `true` if [entity] has been read as a value. For a field this is a normal
   /// read access, for a function this is a closurization.
-  bool get hasRead => false;
+  bool get hasRead => reads.isNotEmpty;
+
+  /// The set of potential read accesses to this member that have not yet
+  /// been registered.
+  EnumSet<Access> get potentialReads => const EnumSet.fixed(0);
+
+  /// The set of registered read accesses to this member.
+  EnumSet<Access> get reads => const EnumSet.fixed(0);
 
   /// `true` if a value has been written to [entity].
-  bool get hasWrite => false;
+  bool get hasWrite => writes.isNotEmpty;
+
+  /// The set of potential write accesses to this member that have not yet
+  /// been registered.
+  EnumSet<Access> get potentialWrites => const EnumSet.fixed(0);
+
+  /// The set of registered write accesses to this member.
+  EnumSet<Access> get writes => const EnumSet.fixed(0);
 
   /// `true` if an invocation has been performed on the value [entity]. For a
   /// function this is a normal invocation, for a field this is a read access
   /// followed by an invocation of the function-like value.
-  bool get hasInvoke => false;
+  bool get hasInvoke => invokes.isNotEmpty;
 
-  /// `true` if all parameters are provided in invocations of [entity].
-  ///
-  /// For method or constructors with no optional arguments this is the same
-  /// as [hasInvoke] but for method or constructors with optional arguments some
-  /// parameters may have been provided in any invocation in which case
-  /// [isFullyInvoked] is `false`.
-  bool get isFullyInvoked => hasInvoke;
+  /// The set of potential invocation accesses to this member that have not yet
+  /// been registered.
+  EnumSet<Access> get potentialInvokes => const EnumSet.fixed(0);
+
+  /// The set of registered invocation accesses to this member.
+  EnumSet<Access> get invokes => const EnumSet.fixed(0);
 
   /// Returns the [ParameterStructure] corresponding to the parameters that are
   /// used in invocations of [entity]. For a field, getter or setter this is
   /// always `null`.
   ParameterStructure get invokedParameters => null;
 
-  /// `true` if [entity] has further normal use. For a field this means that
-  /// it hasn't been read from or written to. For a function this means that it
-  /// hasn't been invoked or, when parameter usage is tracked, that some
-  /// parameters haven't been provided in any invocation.
-  bool get hasPendingNormalUse => _pendingUse.contains(MemberUse.NORMAL);
+  /// Whether this member has any potential but unregistered dynamic reads,
+  /// writes or invocations.
+  bool get hasPendingDynamicUse =>
+      hasPendingDynamicInvoke ||
+      hasPendingDynamicRead ||
+      hasPendingDynamicWrite;
 
-  /// `true` if [entity] hasn't been closurized. This is only used for
-  /// functions.
-  bool get hasPendingClosurizationUse => false;
+  /// Whether this member has any potential but unregistered dynamic
+  /// invocations.
+  bool get hasPendingDynamicInvoke =>
+      potentialInvokes.contains(Access.dynamicAccess);
 
-  /// `true` if [entity] has been used in all the ways possible.
-  bool get fullyUsed;
+  /// Whether this member has any potential but unregistered dynamic reads.
+  bool get hasPendingDynamicRead =>
+      potentialReads.contains(Access.dynamicAccess);
+
+  /// Whether this member has any potential but unregistered dynamic writes.
+  bool get hasPendingDynamicWrite =>
+      potentialWrites.contains(Access.dynamicAccess);
 
   /// Registers the [entity] has been initialized and returns the new
   /// [MemberUse]s that it caused.
@@ -140,22 +204,20 @@
   ///
   /// For a field this is a normal read access, for a function this is a
   /// closurization.
-  EnumSet<MemberUse> read() => MemberUses.NONE;
+  EnumSet<MemberUse> read(EnumSet<Access> accesses) => MemberUses.NONE;
 
   /// Registers a write of a value to [entity] and returns the new [MemberUse]s
   /// that it caused.
-  EnumSet<MemberUse> write() => MemberUses.NONE;
+  EnumSet<MemberUse> write(EnumSet<Access> accesses) => MemberUses.NONE;
 
   /// Registers an invocation on the value of [entity] and returns the new
   /// [MemberUse]s that it caused.
   ///
   /// For a function this is a normal invocation, for a field this is a read
   /// access followed by an invocation of the function-like value.
-  EnumSet<MemberUse> invoke(CallStructure callStructure) => MemberUses.NONE;
-
-  /// Registers all possible uses of [entity] and returns the new [MemberUse]s
-  /// that it caused.
-  EnumSet<MemberUse> fullyUse() => MemberUses.NONE;
+  EnumSet<MemberUse> invoke(
+          EnumSet<Access> accesses, CallStructure callStructure) =>
+      MemberUses.NONE;
 
   @override
   EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
@@ -178,58 +240,187 @@
         hasRead == other.hasRead &&
         hasInvoke == other.hasInvoke &&
         hasWrite == other.hasWrite &&
-        hasPendingClosurizationUse == other.hasPendingClosurizationUse &&
-        hasPendingNormalUse == other.hasPendingNormalUse &&
-        fullyUsed == other.fullyUsed &&
-        isFullyInvoked == other.isFullyInvoked &&
+        hasPendingDynamicRead == other.hasPendingDynamicRead &&
+        hasPendingDynamicWrite == other.hasPendingDynamicWrite &&
+        hasPendingDynamicInvoke == other.hasPendingDynamicInvoke &&
+        hasPendingDynamicUse == other.hasPendingDynamicUse &&
         _pendingUse == other._pendingUse &&
-        _appliedUse == other._appliedUse;
+        _appliedUse == other._appliedUse &&
+        reads == other.reads &&
+        writes == other.writes &&
+        invokes == other.invokes &&
+        potentialReads == other.potentialReads &&
+        potentialWrites == other.potentialWrites &&
+        potentialInvokes == other.potentialInvokes &&
+        invokedParameters == other.invokedParameters;
+  }
+}
+
+/// Member usage tracking for a getter or setter.
+class PropertyUsage extends MemberUsage {
+  @override
+  final EnumSet<Access> potentialReads;
+
+  @override
+  final EnumSet<Access> potentialWrites;
+
+  @override
+  final EnumSet<Access> potentialInvokes;
+
+  @override
+  final EnumSet<Access> reads;
+
+  @override
+  final EnumSet<Access> writes;
+
+  @override
+  final EnumSet<Access> invokes;
+
+  PropertyUsage.cloned(MemberEntity member, EnumSet<MemberUse> pendingUse,
+      {this.potentialReads,
+      this.potentialWrites,
+      this.potentialInvokes,
+      this.reads,
+      this.writes,
+      this.invokes})
+      : assert(potentialReads != null),
+        assert(potentialWrites != null),
+        assert(potentialInvokes != null),
+        assert(reads != null),
+        assert(writes != null),
+        assert(invokes != null),
+        super.cloned(member, pendingUse);
+
+  PropertyUsage(MemberEntity member,
+      {this.potentialReads, this.potentialWrites, this.potentialInvokes})
+      : reads = new EnumSet(),
+        writes = new EnumSet(),
+        invokes = new EnumSet(),
+        assert(potentialReads != null),
+        assert(potentialWrites != null),
+        assert(potentialInvokes != null),
+        super.internal(member);
+
+  @override
+  EnumSet<MemberUse> read(EnumSet<Access> accesses) {
+    bool alreadyHasRead = hasRead;
+    reads.addAll(potentialReads.removeAll(accesses));
+    if (alreadyHasRead) {
+      return MemberUses.NONE;
+    }
+    return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
   }
 
   @override
-  String toString() => '$entity:${_appliedUse.iterable(MemberUse.values)}';
+  EnumSet<MemberUse> write(EnumSet<Access> accesses) {
+    bool alreadyHasWrite = hasWrite;
+    writes.addAll(potentialWrites.removeAll(accesses));
+    if (alreadyHasWrite) {
+      return MemberUses.NONE;
+    }
+    return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
+  }
+
+  @override
+  EnumSet<MemberUse> invoke(
+      EnumSet<Access> accesses, CallStructure callStructure) {
+    // We use `hasRead` here instead of `hasInvoke` because getters only have
+    // 'normal use' (they cannot be closurized). This means that invoking an
+    // already read getter does not result a new member use.
+    bool alreadyHasRead = hasRead;
+    reads.addAll(potentialReads.removeAll(Accesses.staticAccess));
+    invokes.addAll(potentialInvokes.removeAll(accesses));
+    if (alreadyHasRead) {
+      return MemberUses.NONE;
+    }
+    return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
+  }
+
+  @override
+  MemberUsage clone() {
+    return new PropertyUsage.cloned(entity, _pendingUse.clone(),
+        potentialReads: potentialReads.clone(),
+        potentialWrites: potentialWrites.clone(),
+        potentialInvokes: potentialInvokes.clone(),
+        reads: reads.clone(),
+        writes: writes.clone(),
+        invokes: invokes.clone());
+  }
+
+  @override
+  String toString() => 'PropertyUsage($entity,'
+      'reads=${reads.iterable(Access.values)},'
+      'writes=${writes.iterable(Access.values)},'
+      'invokes=${invokes.iterable(Access.values)},'
+      'potentialReads=${potentialReads.iterable(Access.values)},'
+      'potentialWrites=${potentialWrites.iterable(Access.values)},'
+      'potentialInvokes=${potentialInvokes.iterable(Access.values)},'
+      'pendingUse=${_pendingUse.iterable(MemberUse.values)},'
+      'initialConstants=${initialConstants?.map((c) => c.toStructuredText())})';
 }
 
+/// Member usage tracking for a field.
 class FieldUsage extends MemberUsage {
   @override
   bool hasInit;
+
   @override
-  bool hasRead;
+  final EnumSet<Access> potentialReads;
+
   @override
-  bool hasWrite;
+  final EnumSet<Access> potentialWrites;
+
+  @override
+  final EnumSet<Access> potentialInvokes;
+
+  @override
+  final EnumSet<Access> reads;
+
+  @override
+  final EnumSet<Access> invokes;
+
+  @override
+  final EnumSet<Access> writes;
 
   List<ConstantValue> _initialConstants;
 
   FieldUsage.cloned(FieldEntity field, EnumSet<MemberUse> pendingUse,
-      {this.hasInit, this.hasRead, this.hasWrite})
-      : super.cloned(field, pendingUse);
+      {this.potentialReads,
+      this.potentialWrites,
+      this.potentialInvokes,
+      this.hasInit,
+      this.reads,
+      this.writes,
+      this.invokes})
+      : assert(potentialReads != null),
+        assert(potentialWrites != null),
+        assert(potentialInvokes != null),
+        assert(reads != null),
+        assert(writes != null),
+        assert(invokes != null),
+        super.cloned(field, pendingUse);
 
-  FieldUsage(FieldEntity field)
+  FieldUsage(FieldEntity field,
+      {this.potentialReads, this.potentialWrites, this.potentialInvokes})
       : hasInit = false,
-        hasRead = false,
-        hasWrite = false,
+        reads = new EnumSet(),
+        writes = new EnumSet(),
+        invokes = new EnumSet(),
+        assert(potentialReads != null),
+        assert(potentialWrites != null),
+        assert(potentialInvokes != null),
         super.internal(field);
 
   @override
   Iterable<ConstantValue> get initialConstants => _initialConstants ?? const [];
 
   @override
-  bool get hasPendingNormalUse => !fullyUsed;
-
-  @override
-  bool get fullyUsed => hasInit && hasRead && hasWrite;
-
-  @override
   EnumSet<MemberUse> init() {
     if (hasInit) {
       return MemberUses.NONE;
     }
     hasInit = true;
-    EnumSet<MemberUse> result = _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
-    if (!fullyUsed) {
-      result = result.union(MemberUses.PARTIAL_USE_ONLY);
-    }
-    return result;
+    return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
   }
 
   @override
@@ -240,193 +431,128 @@
   }
 
   @override
-  EnumSet<MemberUse> read() {
-    if (hasRead) {
+  bool get hasRead => reads.isNotEmpty;
+
+  @override
+  EnumSet<MemberUse> read(EnumSet<Access> accesses) {
+    bool alreadyHasRead = hasRead;
+    reads.addAll(potentialReads.removeAll(accesses));
+    if (alreadyHasRead) {
       return MemberUses.NONE;
     }
-    hasRead = true;
-    EnumSet<MemberUse> result = _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
-    if (!fullyUsed) {
-      result = result.union(MemberUses.PARTIAL_USE_ONLY);
-    }
-    return result;
+    return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
   }
 
   @override
-  EnumSet<MemberUse> write() {
-    if (hasWrite) {
+  bool get hasWrite => writes.isNotEmpty;
+
+  @override
+  EnumSet<MemberUse> write(EnumSet<Access> accesses) {
+    bool alreadyHasWrite = hasWrite;
+    writes.addAll(potentialWrites.removeAll(accesses));
+    if (alreadyHasWrite) {
       return MemberUses.NONE;
     }
-    hasWrite = true;
-    EnumSet<MemberUse> result = _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
-    if (!fullyUsed) {
-      result = result.union(MemberUses.PARTIAL_USE_ONLY);
-    }
-    return result;
+    return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
   }
 
   @override
-  EnumSet<MemberUse> invoke(CallStructure callStructure) => read();
-
-  @override
-  EnumSet<MemberUse> fullyUse() {
-    if (fullyUsed) {
+  EnumSet<MemberUse> invoke(
+      EnumSet<Access> accesses, CallStructure callStructure) {
+    // We use `hasRead` here instead of `hasInvoke` because fields only have
+    // 'normal use' (they cannot be closurized). This means that invoking an
+    // already read field does not result a new member use.
+    bool alreadyHasRead = hasRead;
+    reads.addAll(potentialReads.removeAll(Accesses.staticAccess));
+    invokes.addAll(potentialInvokes.removeAll(accesses));
+    if (alreadyHasRead) {
       return MemberUses.NONE;
     }
-    hasInit = hasRead = hasWrite = true;
     return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
   }
 
   @override
   MemberUsage clone() {
     return new FieldUsage.cloned(entity, _pendingUse.clone(),
-        hasInit: hasInit, hasRead: hasRead, hasWrite: hasWrite);
+        potentialReads: potentialReads.clone(),
+        potentialWrites: potentialWrites.clone(),
+        potentialInvokes: potentialInvokes.clone(),
+        hasInit: hasInit,
+        reads: reads.clone(),
+        writes: writes.clone(),
+        invokes: invokes.clone());
   }
 
   @override
-  String toString() => 'FieldUsage($entity,hasInit=$hasInit,hasRead=$hasRead,'
-      'hasWrite=$hasWrite,pendingUse=${_pendingUse.iterable(MemberUse.values)},'
+  String toString() => 'FieldUsage($entity,hasInit=$hasInit,'
+      'reads=${reads.iterable(Access.values)},'
+      'writes=${writes.iterable(Access.values)},'
+      'invokes=${invokes.iterable(Access.values)},'
+      'potentialReads=${potentialReads.iterable(Access.values)},'
+      'potentialWrites=${potentialWrites.iterable(Access.values)},'
+      'potentialInvokes=${potentialInvokes.iterable(Access.values)},'
+      'pendingUse=${_pendingUse.iterable(MemberUse.values)},'
       'initialConstants=${initialConstants.map((c) => c.toStructuredText())})';
 }
 
-class FinalFieldUsage extends MemberUsage {
+/// Member usage tracking for a constructor or method.
+class MethodUsage extends MemberUsage {
   @override
-  bool hasInit;
-  @override
-  bool hasRead;
-
-  List<ConstantValue> _initialConstants;
-
-  FinalFieldUsage.cloned(FieldEntity field, EnumSet<MemberUse> pendingUse,
-      {this.hasInit, this.hasRead})
-      : super.cloned(field, pendingUse);
-
-  FinalFieldUsage(FieldEntity field)
-      : this.hasInit = false,
-        this.hasRead = false,
-        super.internal(field);
+  final EnumSet<Access> potentialReads;
 
   @override
-  Iterable<ConstantValue> get initialConstants => _initialConstants ?? const [];
+  final EnumSet<Access> potentialInvokes;
 
   @override
-  bool get hasPendingNormalUse => !fullyUsed;
+  final EnumSet<Access> reads;
 
   @override
-  bool get fullyUsed => hasInit && hasRead;
+  final EnumSet<Access> invokes;
 
-  @override
-  EnumSet<MemberUse> init() {
-    if (hasInit) {
-      return MemberUses.NONE;
-    }
-    hasInit = true;
-    EnumSet<MemberUse> result = _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
-    if (!fullyUsed) {
-      result = result.union(MemberUses.PARTIAL_USE_ONLY);
-    }
-    return result;
-  }
+  final ParameterUsage parameterUsage;
 
-  @override
-  EnumSet<MemberUse> constantInit(ConstantValue constant) {
-    _initialConstants ??= [];
-    _initialConstants.add(constant);
-    return init();
-  }
+  MethodUsage.cloned(FunctionEntity function, this.parameterUsage,
+      EnumSet<MemberUse> pendingUse,
+      {this.potentialReads, this.reads, this.potentialInvokes, this.invokes})
+      : assert(potentialReads != null),
+        assert(potentialInvokes != null),
+        assert(reads != null),
+        assert(invokes != null),
+        super.cloned(function, pendingUse);
 
-  @override
-  EnumSet<MemberUse> read() {
-    if (hasRead) {
-      return MemberUses.NONE;
-    }
-    hasRead = true;
-    EnumSet<MemberUse> result = _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
-    if (!fullyUsed) {
-      result = result.union(MemberUses.PARTIAL_USE_ONLY);
-    }
-    return result;
-  }
-
-  @override
-  EnumSet<MemberUse> invoke(CallStructure callStructure) => read();
-
-  @override
-  EnumSet<MemberUse> fullyUse() {
-    if (fullyUsed) {
-      return MemberUses.NONE;
-    }
-    hasInit = hasRead = true;
-    return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
-  }
-
-  @override
-  MemberUsage clone() {
-    return new FinalFieldUsage.cloned(entity, _pendingUse.clone(),
-        hasInit: hasInit, hasRead: hasRead);
-  }
-
-  @override
-  String toString() => 'FinalFieldUsage($entity,hasInit=$hasInit,'
-      'hasRead=$hasRead,pendingUse=${_pendingUse.iterable(MemberUse.values)},'
-      'initialConstants=${initialConstants.map((c) => c.toStructuredText())})';
-}
-
-class FunctionUsage extends MemberUsage {
-  @override
-  bool hasInvoke;
-  @override
-  bool hasRead;
-
-  FunctionUsage.cloned(FunctionEntity function, EnumSet<MemberUse> pendingUse,
-      {this.hasInvoke, this.hasRead})
-      : super.cloned(function, pendingUse);
-
-  FunctionUsage(FunctionEntity function)
-      : this.hasInvoke = false,
-        this.hasRead = false,
+  MethodUsage(FunctionEntity function,
+      {this.potentialReads, this.potentialInvokes})
+      : reads = new EnumSet(),
+        invokes = new EnumSet(),
+        parameterUsage = new ParameterUsage(function.parameterStructure),
+        assert(potentialReads != null),
+        assert(potentialInvokes != null),
         super.internal(function);
 
   @override
-  FunctionEntity get entity => super.entity;
+  bool get hasInvoke => invokes.isNotEmpty && parameterUsage.hasInvoke;
 
   @override
   EnumSet<MemberUse> get _originalUse =>
       entity.isInstanceMember ? MemberUses.ALL_INSTANCE : MemberUses.ALL_STATIC;
 
   @override
-  bool get hasPendingClosurizationUse => entity.isInstanceMember
-      ? _pendingUse.contains(MemberUse.CLOSURIZE_INSTANCE)
-      : _pendingUse.contains(MemberUse.CLOSURIZE_STATIC);
-
-  @override
-  EnumSet<MemberUse> read() => fullyUse();
-
-  @override
-  EnumSet<MemberUse> invoke(CallStructure callStructure) {
-    if (hasInvoke) {
-      return MemberUses.NONE;
-    }
-    hasInvoke = true;
-    return _pendingUse
-        .removeAll(hasRead ? MemberUses.NONE : MemberUses.NORMAL_ONLY);
-  }
-
-  @override
-  EnumSet<MemberUse> fullyUse() {
-    if (hasInvoke) {
-      if (hasRead) {
+  EnumSet<MemberUse> read(EnumSet<Access> accesses) {
+    bool alreadyHasInvoke = hasInvoke;
+    bool alreadyHasRead = hasRead;
+    reads.addAll(potentialReads.removeAll(accesses));
+    invokes.addAll(potentialInvokes.removeAll(Accesses.dynamicAccess));
+    parameterUsage.fullyUse();
+    if (alreadyHasInvoke) {
+      if (alreadyHasRead) {
         return MemberUses.NONE;
       }
-      hasRead = true;
       return _pendingUse.removeAll(entity.isInstanceMember
           ? MemberUses.CLOSURIZE_INSTANCE_ONLY
           : MemberUses.CLOSURIZE_STATIC_ONLY);
-    } else if (hasRead) {
-      hasInvoke = true;
+    } else if (alreadyHasRead) {
       return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
     } else {
-      hasRead = hasInvoke = true;
       return _pendingUse.removeAll(entity.isInstanceMember
           ? MemberUses.ALL_INSTANCE
           : MemberUses.ALL_STATIC);
@@ -434,289 +560,45 @@
   }
 
   @override
-  bool get fullyUsed => hasInvoke && hasRead;
-
-  @override
-  ParameterStructure get invokedParameters =>
-      hasInvoke ? entity.parameterStructure : null;
-
-  @override
-  MemberUsage clone() {
-    return new FunctionUsage.cloned(entity, _pendingUse.clone(),
-        hasInvoke: hasInvoke, hasRead: hasRead);
-  }
-}
-
-class ParameterTrackingFunctionUsage extends MemberUsage {
-  @override
-  bool hasRead;
-
-  final ParameterUsage _parameterUsage;
-
-  ParameterTrackingFunctionUsage.cloned(FunctionEntity function,
-      this._parameterUsage, EnumSet<MemberUse> pendingUse,
-      {this.hasRead})
-      : super.cloned(function, pendingUse);
-
-  ParameterTrackingFunctionUsage(FunctionEntity function)
-      : hasRead = false,
-        _parameterUsage = new ParameterUsage(function.parameterStructure),
-        super.internal(function);
-
-  @override
-  bool get hasInvoke => _parameterUsage.hasInvoke;
-
-  @override
-  bool get hasPendingClosurizationUse => entity.isInstanceMember
-      ? _pendingUse.contains(MemberUse.CLOSURIZE_INSTANCE)
-      : _pendingUse.contains(MemberUse.CLOSURIZE_STATIC);
-
-  @override
-  EnumSet<MemberUse> get _originalUse =>
-      entity.isInstanceMember ? MemberUses.ALL_INSTANCE : MemberUses.ALL_STATIC;
-
-  @override
-  EnumSet<MemberUse> read() => fullyUse();
-
-  @override
-  EnumSet<MemberUse> invoke(CallStructure callStructure) {
-    if (_parameterUsage.isFullyUsed) {
-      return MemberUses.NONE;
-    }
+  EnumSet<MemberUse> invoke(
+      EnumSet<Access> accesses, CallStructure callStructure) {
     bool alreadyHasInvoke = hasInvoke;
-    bool hasPartialChange = _parameterUsage.invoke(callStructure);
-    EnumSet<MemberUse> result;
+    parameterUsage.invoke(callStructure);
+    invokes.addAll(potentialInvokes.removeAll(accesses));
     if (alreadyHasInvoke) {
-      result = MemberUses.NONE;
+      return MemberUses.NONE;
     } else {
-      result = _pendingUse
+      return _pendingUse
           .removeAll(hasRead ? MemberUses.NONE : MemberUses.NORMAL_ONLY);
     }
-    return hasPartialChange
-        ? result.union(MemberUses.PARTIAL_USE_ONLY)
-        : result;
   }
 
   @override
-  EnumSet<MemberUse> fullyUse() {
-    bool alreadyHasInvoke = hasInvoke;
-    _parameterUsage.fullyUse();
-    if (alreadyHasInvoke) {
-      if (hasRead) {
-        return MemberUses.NONE;
-      }
-      hasRead = true;
-      return _pendingUse.removeAll(entity.isInstanceMember
-          ? MemberUses.CLOSURIZE_INSTANCE_ONLY
-          : MemberUses.CLOSURIZE_STATIC_ONLY);
-    } else if (hasRead) {
-      return _pendingUse.removeAll(MemberUses.NORMAL_ONLY);
-    } else {
-      hasRead = true;
-      return _pendingUse.removeAll(entity.isInstanceMember
-          ? MemberUses.ALL_INSTANCE
-          : MemberUses.ALL_STATIC);
-    }
-  }
+  ParameterStructure get invokedParameters => parameterUsage.invokedParameters;
 
   @override
-  bool get hasPendingNormalUse => !isFullyInvoked;
-
-  @override
-  bool get isFullyInvoked => _parameterUsage.isFullyUsed;
-
-  @override
-  bool get fullyUsed => isFullyInvoked && hasRead;
-
-  @override
-  ParameterStructure get invokedParameters => _parameterUsage.invokedParameters;
+  bool get hasPendingDynamicInvoke =>
+      potentialInvokes.contains(Access.dynamicAccess) ||
+      (invokes.contains(Access.dynamicAccess) && !parameterUsage.isFullyUsed);
 
   @override
   MemberUsage clone() {
-    return new ParameterTrackingFunctionUsage.cloned(
-        entity, _parameterUsage.clone(), _pendingUse.clone(),
-        hasRead: hasRead);
-  }
-}
-
-class GetterUsage extends MemberUsage {
-  @override
-  bool hasRead;
-
-  GetterUsage.cloned(FunctionEntity getter, EnumSet<MemberUse> pendingUse,
-      {this.hasRead})
-      : super.cloned(getter, pendingUse);
-
-  GetterUsage(FunctionEntity getter)
-      : hasRead = false,
-        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);
+    return new MethodUsage.cloned(
+        entity, parameterUsage.clone(), _pendingUse.clone(),
+        reads: reads.clone(),
+        potentialReads: potentialReads.clone(),
+        invokes: invokes.clone(),
+        potentialInvokes: potentialInvokes.clone());
   }
 
   @override
-  EnumSet<MemberUse> invoke(CallStructure callStructure) => read();
-
-  @override
-  EnumSet<MemberUse> fullyUse() => read();
-
-  @override
-  MemberUsage clone() {
-    return new GetterUsage.cloned(entity, _pendingUse.clone(),
-        hasRead: hasRead);
-  }
-}
-
-class SetterUsage extends MemberUsage {
-  @override
-  bool hasWrite;
-
-  SetterUsage.cloned(FunctionEntity setter, EnumSet<MemberUse> pendingUse,
-      {this.hasWrite})
-      : super.cloned(setter, pendingUse);
-
-  SetterUsage(FunctionEntity setter)
-      : hasWrite = false,
-        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();
-
-  @override
-  MemberUsage clone() {
-    return new SetterUsage.cloned(entity, _pendingUse.clone(),
-        hasWrite: hasWrite);
-  }
-}
-
-class ConstructorUsage extends MemberUsage {
-  @override
-  bool hasInvoke;
-
-  ConstructorUsage.cloned(
-      ConstructorEntity constructor, EnumSet<MemberUse> pendingUse,
-      {this.hasInvoke})
-      : super.cloned(constructor, pendingUse);
-
-  ConstructorUsage(ConstructorEntity constructor)
-      : hasInvoke = false,
-        super.internal(constructor);
-
-  @override
-  ConstructorEntity get entity => super.entity;
-
-  @override
-  EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
-
-  @override
-  EnumSet<MemberUse> invoke(CallStructure callStructure) {
-    if (hasInvoke) {
-      return MemberUses.NONE;
-    }
-    hasInvoke = true;
-    return _pendingUse
-        .removeAll(hasRead ? MemberUses.NONE : MemberUses.NORMAL_ONLY);
-  }
-
-  @override
-  EnumSet<MemberUse> fullyUse() =>
-      invoke(entity.parameterStructure.callStructure);
-
-  @override
-  bool get fullyUsed => hasInvoke;
-
-  @override
-  ParameterStructure get invokedParameters =>
-      hasInvoke ? entity.parameterStructure : null;
-
-  @override
-  MemberUsage clone() {
-    return new ConstructorUsage.cloned(entity, _pendingUse.clone(),
-        hasInvoke: hasInvoke);
-  }
-}
-
-class ParameterTrackingConstructorUsage extends MemberUsage {
-  final ParameterUsage _parameterUsage;
-
-  ParameterTrackingConstructorUsage.cloned(ConstructorEntity constructor,
-      this._parameterUsage, EnumSet<MemberUse> pendingUse)
-      : super.cloned(constructor, pendingUse);
-
-  ParameterTrackingConstructorUsage(ConstructorEntity constructor)
-      : _parameterUsage = new ParameterUsage(constructor.parameterStructure),
-        super.internal(constructor);
-
-  @override
-  ConstructorEntity get entity => super.entity;
-
-  @override
-  EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
-
-  @override
-  EnumSet<MemberUse> invoke(CallStructure callStructure) {
-    if (isFullyInvoked) {
-      return MemberUses.NONE;
-    }
-    bool alreadyHasInvoke = hasInvoke;
-    bool hasPartialChange = _parameterUsage.invoke(callStructure);
-    EnumSet<MemberUse> result;
-    if (alreadyHasInvoke) {
-      result = MemberUses.NONE;
-    } else {
-      result = _pendingUse
-          .removeAll(hasRead ? MemberUses.NONE : MemberUses.NORMAL_ONLY);
-    }
-    return hasPartialChange
-        ? result.union(MemberUses.PARTIAL_USE_ONLY)
-        : result;
-  }
-
-  @override
-  EnumSet<MemberUse> fullyUse() =>
-      invoke(entity.parameterStructure.callStructure);
-
-  @override
-  bool get hasInvoke => _parameterUsage.hasInvoke;
-
-  @override
-  bool get fullyUsed => _parameterUsage.isFullyUsed;
-
-  @override
-  bool get hasPendingNormalUse => !isFullyInvoked;
-
-  @override
-  bool get isFullyInvoked => _parameterUsage.isFullyUsed;
-
-  @override
-  ParameterStructure get invokedParameters => _parameterUsage.invokedParameters;
-
-  @override
-  MemberUsage clone() {
-    return new ParameterTrackingConstructorUsage.cloned(
-        entity, _parameterUsage.clone(), _pendingUse.clone());
-  }
+  String toString() => 'MethodUsage($entity,'
+      'reads=${reads.iterable(Access.values)},'
+      'invokes=${invokes.iterable(Access.values)},'
+      'parameterUsage=${parameterUsage},'
+      'potentialReads=${potentialReads.iterable(Access.values)},'
+      'potentialInvokes=${potentialInvokes.iterable(Access.values)},'
+      'pendingUse=${_pendingUse.iterable(MemberUse.values)})';
 }
 
 /// Enum class for the possible kind of use of [MemberEntity] objects.
@@ -729,12 +611,6 @@
 
   /// Tear-off of a static method.
   CLOSURIZE_STATIC,
-
-  /// Invocation that provides previously unprovided optional parameters.
-  ///
-  /// This is used to check that no partial use is missed by the enqueuer, as
-  /// asserted through the `Enqueuery.checkEnqueuerConsistency` method.
-  PARTIAL_USE,
 }
 
 /// Common [EnumSet]s used for [MemberUse].
@@ -750,8 +626,6 @@
       const EnumSet<MemberUse>.fixed(3);
   static const EnumSet<MemberUse> ALL_STATIC =
       const EnumSet<MemberUse>.fixed(5);
-  static const EnumSet<MemberUse> PARTIAL_USE_ONLY =
-      const EnumSet<MemberUse>.fixed(8);
 }
 
 typedef void MemberUsedCallback(MemberEntity member, EnumSet<MemberUse> useSet);
@@ -921,4 +795,68 @@
         areAllTypeParametersProvided: _areAllTypeParametersProvided,
         unprovidedNamedParameters: _unprovidedNamedParameters?.toSet());
   }
+
+  @override
+  String toString() {
+    return 'ParameterUsage('
+        '_hasInvoke=$_hasInvoke,'
+        '_providedPositionalParameters=$_providedPositionalParameters,'
+        '_areAllTypeParametersProvided=$_areAllTypeParametersProvided,'
+        '_unprovidedNamedParameters=$_unprovidedNamedParameters)';
+  }
+}
+
+/// Enum for member access kinds use in [MemberUsage] computation during
+/// resolution or codegen enqueueing.
+enum Access {
+  /// Statically bound access of a member.
+  staticAccess,
+
+  /// Dynamically bound access of a member.
+  dynamicAccess,
+
+  /// Direct access of a super class member.
+  superAccess,
+}
+
+/// Access sets used for registration of member usage.
+class Accesses {
+  /// Statically bound access of a member.
+  static const EnumSet<Access> staticAccess = const EnumSet<Access>.fixed(1);
+
+  /// Dynamically bound access of a member. This implies the statically bound
+  /// access of the member.
+  static const EnumSet<Access> dynamicAccess = const EnumSet<Access>.fixed(3);
+
+  /// Direct access of a super class member. This implies the statically bound
+  /// access of the member.
+  static const EnumSet<Access> superAccess = const EnumSet<Access>.fixed(5);
+}
+
+/// The accesses of a member collected during closed world computation.
+class MemberAccess {
+  static const String tag = 'MemberAccess';
+
+  final EnumSet<Access> reads;
+  final EnumSet<Access> writes;
+  final EnumSet<Access> invokes;
+
+  MemberAccess(this.reads, this.writes, this.invokes);
+
+  factory MemberAccess.readFromDataSource(DataSource source) {
+    source.begin(tag);
+    EnumSet<Access> reads = new EnumSet.fixed(source.readInt());
+    EnumSet<Access> writes = new EnumSet.fixed(source.readInt());
+    EnumSet<Access> invokes = new EnumSet.fixed(source.readInt());
+    source.end(tag);
+    return new MemberAccess(reads, writes, invokes);
+  }
+
+  void writeToDataSink(DataSink sink) {
+    sink.begin(tag);
+    sink.writeInt(reads.value);
+    sink.writeInt(writes.value);
+    sink.writeInt(invokes.value);
+    sink.end(tag);
+  }
 }
diff --git a/pkg/compiler/lib/src/universe/resolution_world_builder.dart b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
index 7aa32b0..82cc3a2 100644
--- a/pkg/compiler/lib/src/universe/resolution_world_builder.dart
+++ b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
@@ -66,8 +66,12 @@
 
   /// Computes usage for all members declared by [cls]. Calls [membersUsed] with
   /// the usage changes for each member.
+  ///
+  /// If [checkEnqueuerConsistency] is `true` we check that no new member
+  /// usage can be found. This check is performed without changing the already
+  /// collected member usage.
   void processClassMembers(ClassEntity cls, MemberUsedCallback memberUsed,
-      {bool dryRun: false});
+      {bool checkEnqueuerConsistency: false});
 
   /// Applies the [dynamicUse] to applicable instance members. Calls
   /// [membersUsed] with the usage changes for each member.
@@ -288,14 +292,22 @@
 
   Map<MemberEntity, MemberUsage> get memberUsageForTesting => _memberUsage;
 
-  /// Map containing instance members of live classes that are not yet fully
-  /// live themselves.
-  final Map<String, Set<MemberUsage>> _instanceMembersByName =
+  /// Map containing instance members of live classes that have not yet been
+  /// fully invoked dynamically.
+  ///
+  /// A method is fully invoked if all is optional parameter have been passed
+  /// in some invocation.
+  final Map<String, Set<MemberUsage>> _invokableInstanceMembersByName =
       <String, Set<MemberUsage>>{};
 
-  /// Map containing instance methods of live classes that are not yet
-  /// closurized.
-  final Map<String, Set<MemberUsage>> _instanceFunctionsByName =
+  /// Map containing instance members of live classes that have not yet been
+  /// read from dynamically.
+  final Map<String, Set<MemberUsage>> _readableInstanceMembersByName =
+      <String, Set<MemberUsage>>{};
+
+  /// Map containing instance members of live classes that have not yet been
+  /// written to dynamically.
+  final Map<String, Set<MemberUsage>> _writableInstanceMembersByName =
       <String, Set<MemberUsage>>{};
 
   final Set<FieldEntity> _fieldSetters = new Set<FieldEntity>();
@@ -490,8 +502,7 @@
   }
 
   bool _hasInvokedGetter(MemberEntity member) {
-    return _hasMatchingSelector(_invokedGetters[member.name], member) ||
-        member.isFunction && _methodsNeedingSuperGetter.contains(member);
+    return _hasMatchingSelector(_invokedGetters[member.name], member);
   }
 
   bool _hasInvokedSetter(MemberEntity member) {
@@ -525,23 +536,36 @@
             dynamicUse.selector, dynamicUse.typeArguments);
         if (_registerNewSelector(dynamicUse, _invokedNames)) {
           _process(
-              _instanceMembersByName,
-              (m) => m.invoke(dynamicUse.selector.callStructure),
-              (u) => !u.hasPendingNormalUse);
+              _invokableInstanceMembersByName,
+              (m) => m.invoke(
+                  Accesses.dynamicAccess, dynamicUse.selector.callStructure),
+              // If not all optional parameters have been passed in invocations
+              // we must keep the member in [_invokableInstanceMembersByName].
+              // TODO(johnniwinther): Also remove from
+              // [_readableInstanceMembersByName] in case of getters/setters.
+              (u) => !u.hasPendingDynamicInvoke);
         }
         break;
       case DynamicUseKind.GET:
         if (_registerNewSelector(dynamicUse, _invokedGetters)) {
-          _process(_instanceMembersByName, (m) => m.read(),
-              (u) => !u.hasPendingNormalUse);
-          _process(_instanceFunctionsByName, (m) => m.read(),
-              (u) => !u.hasPendingClosurizationUse);
+          _process(
+              _readableInstanceMembersByName,
+              (m) => m.read(Accesses.dynamicAccess),
+              // TODO(johnniwinther): Members cannot be partially read so
+              // we should always remove them.
+              // TODO(johnniwinther): Also remove from
+              // [_invokableInstanceMembersByName] in case of methods.
+              (u) => !u.hasPendingDynamicRead);
         }
         break;
       case DynamicUseKind.SET:
         if (_registerNewSelector(dynamicUse, _invokedSetters)) {
-          _process(_instanceMembersByName, (m) => m.write(),
-              (u) => !u.hasPendingNormalUse);
+          _process(
+              _writableInstanceMembersByName,
+              (m) => m.write(Accesses.dynamicAccess),
+              // TODO(johnniwinther): Members cannot be partially written so
+              // we should always remove them.
+              (u) => !u.hasPendingDynamicWrite);
         }
         break;
     }
@@ -604,10 +628,11 @@
     // [FIELD_SET] contains [BoxFieldElement]s which we cannot enqueue.
     // Also [CLOSURE] contains [LocalFunctionElement] which we cannot
     // enqueue.
+
     switch (staticUse.kind) {
-      case StaticUseKind.FIELD_GET:
+      case StaticUseKind.INSTANCE_FIELD_GET:
         break;
-      case StaticUseKind.FIELD_SET:
+      case StaticUseKind.INSTANCE_FIELD_SET:
         _fieldSetters.add(staticUse.element);
         break;
       case StaticUseKind.CLOSURE:
@@ -615,21 +640,27 @@
         // Already handled above.
         break;
       case StaticUseKind.SUPER_TEAR_OFF:
-        useSet.addAll(usage.read());
+        useSet.addAll(usage.read(Accesses.superAccess));
         _methodsNeedingSuperGetter.add(staticUse.element);
         break;
       case StaticUseKind.SUPER_FIELD_SET:
         _fieldSetters.add(staticUse.element);
-        useSet.addAll(usage.write());
+        useSet.addAll(usage.write(Accesses.superAccess));
         break;
-      case StaticUseKind.GET:
-        useSet.addAll(usage.read());
+      case StaticUseKind.SUPER_GET:
+        useSet.addAll(usage.read(Accesses.superAccess));
+        break;
+      case StaticUseKind.STATIC_GET:
+        useSet.addAll(usage.read(Accesses.staticAccess));
         break;
       case StaticUseKind.STATIC_TEAR_OFF:
-        useSet.addAll(usage.read());
+        useSet.addAll(usage.read(Accesses.staticAccess));
         break;
-      case StaticUseKind.SET:
-        useSet.addAll(usage.write());
+      case StaticUseKind.SUPER_SETTER_SET:
+        useSet.addAll(usage.write(Accesses.superAccess));
+        break;
+      case StaticUseKind.STATIC_SET:
+        useSet.addAll(usage.write(Accesses.staticAccess));
         break;
       case StaticUseKind.FIELD_INIT:
         useSet.addAll(usage.init());
@@ -637,13 +668,20 @@
       case StaticUseKind.FIELD_CONSTANT_INIT:
         useSet.addAll(usage.constantInit(staticUse.constant));
         break;
-      case StaticUseKind.INVOKE:
+      case StaticUseKind.SUPER_INVOKE:
         registerStaticInvocation(staticUse);
-        useSet.addAll(usage.invoke(staticUse.callStructure));
+        useSet.addAll(
+            usage.invoke(Accesses.superAccess, staticUse.callStructure));
+        break;
+      case StaticUseKind.STATIC_INVOKE:
+        registerStaticInvocation(staticUse);
+        useSet.addAll(
+            usage.invoke(Accesses.staticAccess, staticUse.callStructure));
         break;
       case StaticUseKind.CONSTRUCTOR_INVOKE:
       case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
-        useSet.addAll(usage.invoke(staticUse.callStructure));
+        useSet.addAll(
+            usage.invoke(Accesses.staticAccess, staticUse.callStructure));
         break;
       case StaticUseKind.DIRECT_INVOKE:
         failedAt(element, 'Direct static use is not supported for resolution.');
@@ -692,10 +730,11 @@
 
   @override
   void processClassMembers(ClassEntity cls, MemberUsedCallback memberUsed,
-      {bool dryRun: false}) {
+      {bool checkEnqueuerConsistency: false}) {
     _elementEnvironment.forEachClassMember(cls,
         (ClassEntity cls, MemberEntity member) {
-      _processInstantiatedClassMember(cls, member, memberUsed, dryRun: dryRun);
+      _processInstantiatedClassMember(cls, member, memberUsed,
+          checkEnqueuerConsistency: checkEnqueuerConsistency);
     });
   }
 
@@ -720,7 +759,7 @@
   }
 
   MemberUsage _getMemberUsage(MemberEntity member, EnumSet<MemberUse> useSet,
-      {bool dryRun: false}) {
+      {bool checkEnqueuerConsistency: false}) {
     MemberUsage usage = _memberUsage[member];
     if (usage == null) {
       if (member.isInstanceMember) {
@@ -736,11 +775,11 @@
         // 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.
         bool isNative = _nativeBasicData.isNativeClass(cls);
-        usage = new MemberUsage(member, trackParameters: true);
+        usage = new MemberUsage(member);
         if (member.isField && !isNative) {
           useSet.addAll(usage.init());
         }
-        if (!dryRun) {
+        if (!checkEnqueuerConsistency) {
           if (member.isField && isNative) {
             registerUsedElement(member);
           }
@@ -751,100 +790,114 @@
           }
         }
 
-        if (!usage.hasRead && _hasInvokedGetter(member)) {
-          useSet.addAll(usage.read());
+        if (usage.hasPendingDynamicRead && _hasInvokedGetter(member)) {
+          useSet.addAll(usage.read(Accesses.dynamicAccess));
         }
-        if (!usage.isFullyInvoked) {
+        if (usage.hasPendingDynamicInvoke) {
           Iterable<CallStructure> callStructures =
               _getInvocationCallStructures(member);
           for (CallStructure callStructure in callStructures) {
-            useSet.addAll(usage.invoke(callStructure));
-            if (usage.isFullyInvoked) {
+            useSet.addAll(usage.invoke(Accesses.dynamicAccess, callStructure));
+            if (!usage.hasPendingDynamicInvoke) {
               break;
             }
           }
         }
-        if (!usage.hasWrite && _hasInvokedSetter(member)) {
-          useSet.addAll(usage.write());
+        if (usage.hasPendingDynamicWrite && _hasInvokedSetter(member)) {
+          useSet.addAll(usage.write(Accesses.dynamicAccess));
         }
 
-        if (!dryRun) {
-          if (usage.hasPendingNormalUse) {
-            // The element is not yet used. Add it to the list of instance
-            // members to still be processed.
-            _instanceMembersByName
-                .putIfAbsent(memberName, () => new Set<MemberUsage>())
+        if (!checkEnqueuerConsistency) {
+          if (usage.hasPendingDynamicInvoke) {
+            _invokableInstanceMembersByName
+                .putIfAbsent(memberName, () => {})
                 .add(usage);
           }
-          if (usage.hasPendingClosurizationUse) {
-            // Store the member in [instanceFunctionsByName] to catch
-            // getters on the function.
-            _instanceFunctionsByName
-                .putIfAbsent(memberName, () => new Set<MemberUsage>())
+          if (usage.hasPendingDynamicRead) {
+            _readableInstanceMembersByName
+                .putIfAbsent(memberName, () => {})
+                .add(usage);
+          }
+          if (usage.hasPendingDynamicWrite) {
+            _writableInstanceMembersByName
+                .putIfAbsent(memberName, () => {})
                 .add(usage);
           }
         }
       } else {
-        usage = new MemberUsage(member, trackParameters: true);
+        usage = new MemberUsage(member);
         if (member.isField) {
           useSet.addAll(usage.init());
         }
       }
-      if (!dryRun) {
+      if (!checkEnqueuerConsistency) {
         _memberUsage[member] = usage;
       }
     }
     return usage;
   }
 
-  void _processInstantiatedClassMember(ClassEntity cls,
-      covariant MemberEntity member, MemberUsedCallback memberUsed,
-      {bool dryRun: false}) {
+  void _processInstantiatedClassMember(
+      ClassEntity cls, MemberEntity member, MemberUsedCallback memberUsed,
+      {bool checkEnqueuerConsistency: false}) {
     if (!member.isInstanceMember) return;
     String memberName = member.name;
 
     MemberUsage usage = _memberUsage[member];
     if (usage == null) {
       EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
-      usage = _getMemberUsage(member, useSet, dryRun: dryRun);
-      memberUsed(usage.entity, useSet);
+      usage = _getMemberUsage(member, useSet,
+          checkEnqueuerConsistency: checkEnqueuerConsistency);
+      if (useSet.isNotEmpty) {
+        if (checkEnqueuerConsistency) {
+          throw new SpannableAssertionFailure(member,
+              'Unenqueued usage of $member: \nbefore: <none>\nafter : $usage');
+        } else {
+          memberUsed(usage.entity, useSet);
+        }
+      }
     } else {
       MemberUsage original = usage;
-      if (dryRun) {
+      if (checkEnqueuerConsistency) {
         usage = usage.clone();
       }
-      if (!usage.fullyUsed) {
+      if (usage.hasPendingDynamicUse) {
         EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
-        if (!usage.hasRead && _hasInvokedGetter(member)) {
-          useSet.addAll(usage.read());
+        if (usage.hasPendingDynamicRead && _hasInvokedGetter(member)) {
+          useSet.addAll(usage.read(Accesses.dynamicAccess));
         }
-        if (!usage.isFullyInvoked) {
+        if (usage.hasPendingDynamicInvoke) {
           Iterable<CallStructure> callStructures =
               _getInvocationCallStructures(member);
           for (CallStructure callStructure in callStructures) {
-            useSet.addAll(usage.invoke(callStructure));
-            if (usage.isFullyInvoked) {
+            useSet.addAll(usage.invoke(Accesses.dynamicAccess, callStructure));
+            if (!usage.hasPendingDynamicInvoke) {
               break;
             }
           }
         }
-        if (!usage.hasWrite && _hasInvokedSetter(member)) {
-          useSet.addAll(usage.write());
+        if (usage.hasPendingDynamicWrite && _hasInvokedSetter(member)) {
+          useSet.addAll(usage.write(Accesses.dynamicAccess));
         }
-        if (!dryRun) {
-          if (!usage.hasPendingNormalUse) {
-            _instanceMembersByName[memberName]?.remove(usage);
+        if (!checkEnqueuerConsistency) {
+          if (!usage.hasPendingDynamicRead) {
+            _readableInstanceMembersByName[memberName]?.remove(usage);
           }
-          if (!usage.hasPendingClosurizationUse) {
-            _instanceFunctionsByName[memberName]?.remove(usage);
+          if (!usage.hasPendingDynamicInvoke) {
+            _invokableInstanceMembersByName[memberName]?.remove(usage);
           }
+          if (!usage.hasPendingDynamicWrite) {
+            _writableInstanceMembersByName[memberName]?.remove(usage);
+          }
+        }
+        if (checkEnqueuerConsistency && !original.dataEquals(usage)) {
+          _elementMap.reporter.internalError(
+              member,
+              'Unenqueued usage of $member: \n'
+              'before: $original\nafter : $usage');
         }
         memberUsed(usage.entity, useSet);
       }
-      if (dryRun && !original.dataEquals(usage)) {
-        _elementMap.reporter.internalError(member,
-            'Unenqueued usage of $member: before: $original, after: $usage');
-      }
     }
   }
 
diff --git a/pkg/compiler/lib/src/universe/use.dart b/pkg/compiler/lib/src/universe/use.dart
index da78e72..453f812 100644
--- a/pkg/compiler/lib/src/universe/use.dart
+++ b/pkg/compiler/lib/src/universe/use.dart
@@ -147,8 +147,11 @@
   STATIC_TEAR_OFF,
   SUPER_TEAR_OFF,
   SUPER_FIELD_SET,
-  FIELD_GET,
-  FIELD_SET,
+  SUPER_GET,
+  SUPER_SETTER_SET,
+  SUPER_INVOKE,
+  INSTANCE_FIELD_GET,
+  INSTANCE_FIELD_SET,
   CLOSURE,
   CLOSURE_CALL,
   CALL_METHOD,
@@ -156,9 +159,9 @@
   CONST_CONSTRUCTOR_INVOKE,
   DIRECT_INVOKE,
   INLINING,
-  INVOKE,
-  GET,
-  SET,
+  STATIC_INVOKE,
+  STATIC_GET,
+  STATIC_SET,
   FIELD_INIT,
   FIELD_CONSTANT_INIT,
 }
@@ -197,9 +200,10 @@
   String get shortText {
     StringBuffer sb = new StringBuffer();
     switch (kind) {
-      case StaticUseKind.FIELD_SET:
+      case StaticUseKind.INSTANCE_FIELD_SET:
       case StaticUseKind.SUPER_FIELD_SET:
-      case StaticUseKind.SET:
+      case StaticUseKind.SUPER_SETTER_SET:
+      case StaticUseKind.STATIC_SET:
         sb.write('set:');
         break;
       case StaticUseKind.FIELD_INIT:
@@ -268,8 +272,8 @@
         failedAt(element,
             "Not CallStructure for static invocation of element $element."));
 
-    return new GenericStaticUse(element, StaticUseKind.INVOKE, callStructure,
-        typeArguments, deferredImport);
+    return new GenericStaticUse(element, StaticUseKind.STATIC_INVOKE,
+        callStructure, typeArguments, deferredImport);
   }
 
   /// Closurization of a static or top-level function [element].
@@ -300,7 +304,7 @@
         element.isField || element.isGetter,
         failedAt(element,
             "Static get element $element must be a field or a getter."));
-    return new StaticUse.internal(element, StaticUseKind.GET,
+    return new StaticUse.internal(element, StaticUseKind.STATIC_GET,
         deferredImport: deferredImport);
   }
 
@@ -317,7 +321,7 @@
         (element.isField && element.isAssignable) || element.isSetter,
         failedAt(element,
             "Static set element $element must be a field or a setter."));
-    return new StaticUse.internal(element, StaticUseKind.SET,
+    return new StaticUse.internal(element, StaticUseKind.STATIC_SET,
         deferredImport: deferredImport);
   }
 
@@ -348,7 +352,7 @@
         failedAt(element,
             "Not CallStructure for super invocation of element $element."));
     return new GenericStaticUse(
-        element, StaticUseKind.INVOKE, callStructure, typeArguments);
+        element, StaticUseKind.SUPER_INVOKE, callStructure, typeArguments);
   }
 
   /// Read access of a super field or getter [element].
@@ -361,7 +365,7 @@
         element.isField || element.isGetter,
         failedAt(element,
             "Super get element $element must be a field or a getter."));
-    return new StaticUse.internal(element, StaticUseKind.GET);
+    return new StaticUse.internal(element, StaticUseKind.SUPER_GET);
   }
 
   /// Write access of a super field [element].
@@ -383,7 +387,7 @@
             element, "Super set element $element must be an instance method."));
     assert(element.isSetter,
         failedAt(element, "Super set element $element must be a setter."));
-    return new StaticUse.internal(element, StaticUseKind.SET);
+    return new StaticUse.internal(element, StaticUseKind.SUPER_SETTER_SET);
   }
 
   /// Closurization of a super method [element].
@@ -411,7 +415,7 @@
             element,
             "Not CallStructure for super constructor invocation of element "
             "$element."));
-    return new StaticUse.internal(element, StaticUseKind.INVOKE,
+    return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
         callStructure: callStructure);
   }
 
@@ -425,14 +429,14 @@
             element,
             "Not CallStructure for constructor body invocation of element "
             "$element."));
-    return new StaticUse.internal(element, StaticUseKind.INVOKE,
+    return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
         callStructure: callStructure);
   }
 
   /// Direct invocation of a generator (body) [element], as a static call or
   /// through a this or super constructor call.
   factory StaticUse.generatorBodyInvoke(FunctionEntity element) {
-    return new StaticUse.internal(element, StaticUseKind.INVOKE,
+    return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
         callStructure: CallStructure.NO_ARGS);
   }
 
@@ -459,7 +463,7 @@
         element.isField || element.isGetter,
         failedAt(element,
             "Direct get element $element must be a field or a getter."));
-    return new StaticUse.internal(element, StaticUseKind.GET);
+    return new StaticUse.internal(element, StaticUseKind.STATIC_GET);
   }
 
   /// Direct write access of a field [element].
@@ -470,7 +474,7 @@
             "Direct set element $element must be an instance member."));
     assert(element.isField,
         failedAt(element, "Direct set element $element must be a field."));
-    return new StaticUse.internal(element, StaticUseKind.SET);
+    return new StaticUse.internal(element, StaticUseKind.STATIC_SET);
   }
 
   /// Constructor invocation of [element] with the given [callStructure].
@@ -486,7 +490,7 @@
             element,
             "Not CallStructure for constructor invocation of element "
             "$element."));
-    return new StaticUse.internal(element, StaticUseKind.INVOKE,
+    return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
         callStructure: callStructure);
   }
 
@@ -559,7 +563,7 @@
         element.isInstanceMember || element is JRecordField,
         failedAt(element,
             "Field init element $element must be an instance or boxed field."));
-    return new StaticUse.internal(element, StaticUseKind.FIELD_GET);
+    return new StaticUse.internal(element, StaticUseKind.INSTANCE_FIELD_GET);
   }
 
   /// Write access of an instance field or boxed field [element].
@@ -568,7 +572,7 @@
         element.isInstanceMember || element is JRecordField,
         failedAt(element,
             "Field init element $element must be an instance or boxed field."));
-    return new StaticUse.internal(element, StaticUseKind.FIELD_SET);
+    return new StaticUse.internal(element, StaticUseKind.INSTANCE_FIELD_SET);
   }
 
   /// Read of a local function [element].
@@ -592,7 +596,7 @@
   /// Implicit method/constructor invocation of [element] created by the
   /// backend.
   factory StaticUse.implicitInvoke(FunctionEntity element) {
-    return new StaticUse.internal(element, StaticUseKind.INVOKE,
+    return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
         callStructure: element.parameterStructure.callStructure);
   }
 
diff --git a/pkg/compiler/lib/src/util/enumset.dart b/pkg/compiler/lib/src/util/enumset.dart
index 4872be1..8db885e 100644
--- a/pkg/compiler/lib/src/util/enumset.dart
+++ b/pkg/compiler/lib/src/util/enumset.dart
@@ -36,14 +36,16 @@
   /// value indices.
   void set value(int mask);
 
-  /// Adds [enumValue] to this set.
-  void add(E enumValue);
+  /// Adds [enumValue] to this set. Returns `true` if the set was changed by
+  /// this action.
+  bool add(E enumValue);
 
   /// Adds all enum values in [set] to this set.
   void addAll(EnumSet<E> set);
 
-  /// Removes [enumValue] from this set.
-  void remove(E enumValue);
+  /// Removes [enumValue] from this set. Returns `true` if the set was changed
+  /// by this action.
+  bool remove(E enumValue);
 
   /// Removes all enum values in [set] from this set. The set of removed values
   /// is returned.
@@ -149,8 +151,10 @@
   }
 
   @override
-  void add(E enumValue) {
+  bool add(E enumValue) {
+    int before = _value;
     _value |= 1 << (enumValue as dynamic).index;
+    return _value != before;
   }
 
   @override
@@ -159,8 +163,10 @@
   }
 
   @override
-  void remove(E enumValue) {
+  bool remove(E enumValue) {
+    int before = _value;
     _value &= ~(1 << (enumValue as dynamic).index);
+    return _value != before;
   }
 
   @override
@@ -201,7 +207,7 @@
   }
 
   @override
-  void add(E enumValue) {
+  bool add(E enumValue) {
     throw new UnsupportedError('EnumSet.add');
   }
 
@@ -212,16 +218,36 @@
 
   @override
   void clear() {
-    throw new UnsupportedError('EnumSet.clear');
+    if (isEmpty) {
+      // We allow this no-op operation on an immutable set to support using a
+      // constant empty set together with mutable sets where applicable.
+    } else {
+      throw new UnsupportedError('EnumSet.clear');
+    }
   }
 
   @override
-  void remove(E enumValue) {
+  bool remove(E enumValue) {
+    if (isEmpty) {
+      // We allow this no-op operation on an immutable set to support using a
+      // constant empty set together with mutable sets where applicable.
+      return false;
+    }
     throw new UnsupportedError('EnumSet.remove');
   }
 
   @override
   EnumSet<E> removeAll(EnumSet<E> set) {
+    if (isEmpty) {
+      // We allow this no-op operation on an immutable set to support using a
+      // constant empty set together with mutable sets where applicable.
+      return this;
+    }
+    if (set.isEmpty) {
+      // We allow this no-op operation on an immutable set to support using a
+      // constant empty set together with mutable sets where applicable.
+      return set.clone();
+    }
     throw new UnsupportedError('EnumSet.removeAll');
   }
 }
diff --git a/pkg/compiler/lib/src/world.dart b/pkg/compiler/lib/src/world.dart
index 39a31f1..c592196 100644
--- a/pkg/compiler/lib/src/world.dart
+++ b/pkg/compiler/lib/src/world.dart
@@ -198,6 +198,10 @@
   /// Returns the single [MemberEntity] that matches a call to [selector] on the
   /// [receiver]. If multiple targets exist, `null` is returned.
   MemberEntity locateSingleMember(Selector selector, AbstractValue receiver);
+
+  /// Returns the set of read, write, and invocation accesses found on [member]
+  /// during the closed world computation.
+  MemberAccess getMemberAccess(MemberEntity member);
 }
 
 abstract class OpenWorld implements World {
diff --git a/tests/compiler/dart2js/member_usage/data/fields.dart b/tests/compiler/dart2js/member_usage/data/fields.dart
index 5d95285..54679fb 100644
--- a/tests/compiler/dart2js/member_usage/data/fields.dart
+++ b/tests/compiler/dart2js/member_usage/data/fields.dart
@@ -11,6 +11,9 @@
 /*element: field1c:init,read,write*/
 var field1c;
 
+/*element: field1d:init,read*/
+var field1d;
+
 /*element: field2a:read*/
 get field2a => 42;
 
@@ -27,6 +30,11 @@
 /*element: field2c=:write*/
 set field2c(_) {}
 
+/*element: field2d:read*/
+get field2d => 42;
+
+set field2d(_) {}
+
 class Class {
   /*element: Class.field1a:init,read*/
   var field1a;
@@ -37,6 +45,9 @@
   /*element: Class.field1c:init,read,write*/
   var field1c;
 
+  /*element: Class.field1d:init,invoke,read=static*/
+  var field1d;
+
   /*element: Class.field2a:read*/
   get field2a => 42;
 
@@ -53,6 +64,11 @@
   /*element: Class.field2c=:write*/
   set field2c(_) {}
 
+  /*element: Class.field2d:invoke,read=static*/
+  get field2d => null;
+
+  set field2d(_) {}
+
   /*element: Class.field3a:init*/
   var field3a = 0;
 
@@ -67,10 +83,12 @@
     field1a;
     field1b = 42;
     field1c = field1c;
+    field1d();
 
     field2a;
     field2b = 42;
     field2c = field2c;
+    field2d();
   }
 }
 
@@ -79,10 +97,12 @@
   field1a;
   field1b = 42;
   field1c = field1c;
+  field1d();
 
   field2a;
   field2b = 42;
   field2c = field2c;
+  field2d();
 
   new Class().test();
 }
diff --git a/tests/compiler/dart2js/member_usage/data/general.dart b/tests/compiler/dart2js/member_usage/data/general.dart
index ffab22b..465f141 100644
--- a/tests/compiler/dart2js/member_usage/data/general.dart
+++ b/tests/compiler/dart2js/member_usage/data/general.dart
@@ -165,7 +165,7 @@
 
 /*element: Class2.:invoke*/
 class Class2 {
-  /*element: Class2.c:init,read*/
+  /*element: Class2.c:init,invoke,read=static*/
   Class1a c;
 }
 
diff --git a/tests/compiler/dart2js/member_usage/data/super.dart b/tests/compiler/dart2js/member_usage/data/super.dart
new file mode 100644
index 0000000..5b3b775
--- /dev/null
+++ b/tests/compiler/dart2js/member_usage/data/super.dart
@@ -0,0 +1,122 @@
+// Copyright (c) 2019, 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.
+
+class Super {
+  /*element: Super.field1:init,read=super*/
+  var field1;
+
+  /*element: Super.field2:init,write=super*/
+  var field2;
+
+  /*element: Super.field3:init,read=super*/
+  var field3;
+
+  /*element: Super.field4:init,read=super*/
+  final field4;
+
+  /*element: Super.field5:init,read=super*/
+  final field5;
+
+  /*element: Super.constructor1:invoke*/
+  Super.constructor1(this.field4, this.field5);
+
+  /*element: Super.constructor2:invoke=(0)*/
+  Super.constructor2([this.field4, this.field5]);
+
+  /*element: Super.constructor3:invoke=(1)*/
+  Super.constructor3([this.field4, this.field5]);
+
+  /*element: Super.method1:invoke=(1):super*/
+  method1([a, b]) {}
+
+  /*element: Super.method2:invoke,read=super*/
+  method2([a, b]) {}
+
+  /*element: Super.getter1:read=super*/
+  get getter1 => null;
+
+  /*element: Super.getter2:read=super*/
+  get getter2 => null;
+
+  /*element: Super.setter1=:write=super*/
+  set setter1(_) {}
+
+  /*element: Super.call:invoke=(0,a,b,c)*/
+  void call({a, b, c, d}) {}
+}
+
+class Sub extends Super {
+  /*element: Sub.constructor1:invoke=(1)*/
+  Sub.constructor1([field4, field5]) : super.constructor1(field4, field5);
+
+  /*element: Sub.constructor2:invoke*/
+  Sub.constructor2() : super.constructor2();
+
+  /*element: Sub.readSuperField:invoke*/
+  readSuperField() {
+    return super.field1;
+  }
+
+  /*element: Sub.writeSuperField:invoke*/
+  writeSuperField() {
+    super.field2 = null;
+  }
+
+  /*element: Sub.invokeSuperField:invoke*/
+  invokeSuperField() {
+    super.field3(a: 0);
+  }
+
+  /*element: Sub.readSuperFinalField:invoke*/
+  readSuperFinalField() {
+    return super.field4;
+  }
+
+  /*element: Sub.invokeSuperFinalField:invoke*/
+  invokeSuperFinalField() {
+    super.field5(b: 0);
+  }
+
+  /*element: Sub.invokeSuperMethod:invoke*/
+  invokeSuperMethod() {
+    super.method1(0);
+  }
+
+  /*element: Sub.readSuperMethod:invoke*/
+  readSuperMethod() {
+    return super.method2;
+  }
+
+  /*element: Sub.readSuperGetter:invoke*/
+  readSuperGetter() {
+    return super.getter1;
+  }
+
+  /*element: Sub.invokeSuperGetter:invoke*/
+  invokeSuperGetter() {
+    return super.getter2(c: 0);
+  }
+
+  /*element: Sub.writeSuperSetter:invoke*/
+  writeSuperSetter() {
+    super.setter1 = null;
+  }
+}
+
+/*element: main:invoke*/
+void main() {
+  new Super.constructor3(null);
+  new Sub.constructor1(null);
+  new Sub.constructor2()
+    ..readSuperField()
+    ..writeSuperField()
+    ..invokeSuperField()
+    ..readSuperFinalField()
+    ..invokeSuperFinalField()
+    ..invokeSuperMethod()
+    ..readSuperMethod()
+    ..readSuperGetter()
+    ..invokeSuperGetter()
+    ..writeSuperSetter();
+}
diff --git a/tests/compiler/dart2js/member_usage/member_usage_test.dart b/tests/compiler/dart2js/member_usage/member_usage_test.dart
index 9767dc5..c81ffb1 100644
--- a/tests/compiler/dart2js/member_usage/member_usage_test.dart
+++ b/tests/compiler/dart2js/member_usage/member_usage_test.dart
@@ -11,6 +11,7 @@
 import 'package:compiler/src/kernel/kernel_strategy.dart';
 import 'package:compiler/src/universe/member_usage.dart';
 import 'package:compiler/src/universe/resolution_world_builder.dart';
+import 'package:compiler/src/util/enumset.dart';
 import 'package:compiler/src/util/features.dart';
 import 'package:kernel/ast.dart' as ir;
 import '../equivalence/id_equivalence.dart';
@@ -55,6 +56,31 @@
     Enqueuer.skipEnqueuerCheckForTesting = skipEnqueuerCheck;
   }
 
+  /// Compute a short textual representation of [access] on member.
+  ///
+  /// Dynamic access on instance members and static access on non-instance
+  /// members is implicit, so we only annotate super access and static access
+  /// not implied by dynamic or super access.
+  String computeAccessText(MemberEntity member, EnumSet<Access> access,
+      [String prefix]) {
+    StringBuffer sb = new StringBuffer();
+    String delimiter = '';
+    if (prefix != null) {
+      sb.write(prefix);
+      delimiter = ':';
+    }
+    if (access.contains(Access.superAccess)) {
+      sb.write(delimiter);
+      sb.write('super');
+    } else if (member.isInstanceMember &&
+        access.contains(Access.staticAccess) &&
+        !access.contains(Access.dynamicAccess)) {
+      sb.write(delimiter);
+      sb.write('static');
+    }
+    return sb.toString();
+  }
+
   @override
   void computeMemberData(Compiler compiler, MemberEntity member,
       Map<Id, ActualData<Features>> actualMap,
@@ -71,15 +97,20 @@
         features.add(Tags.init);
       }
       if (memberUsage.hasRead) {
-        features.add(Tags.read);
+        features[Tags.read] = computeAccessText(member, memberUsage.reads);
       }
       if (memberUsage.hasWrite) {
-        features.add(Tags.write);
+        features[Tags.write] = computeAccessText(member, memberUsage.writes);
       }
-      if (memberUsage.isFullyInvoked) {
-        features.add(Tags.invoke);
-      } else if (memberUsage.hasInvoke) {
-        features[Tags.invoke] = memberUsage.invokedParameters.shortText;
+      if (memberUsage.hasInvoke) {
+        if (memberUsage is MethodUsage &&
+            !memberUsage.parameterUsage.isFullyUsed) {
+          features[Tags.invoke] = computeAccessText(member, memberUsage.invokes,
+              memberUsage.invokedParameters.shortText);
+        } else {
+          features[Tags.invoke] =
+              computeAccessText(member, memberUsage.invokes);
+        }
       }
     }
     Id id = computeEntityId(node);