Track parameter use in invocations in MemberUsage

Change-Id: I53b4ad1ff533d913bdff7e48558fdc56ce016e3f
Reviewed-on: https://dart-review.googlesource.com/c/89525
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/elements/entities.dart b/pkg/compiler/lib/src/elements/entities.dart
index 0adff08..a3a5c4d 100644
--- a/pkg/compiler/lib/src/elements/entities.dart
+++ b/pkg/compiler/lib/src/elements/entities.dart
@@ -350,6 +350,24 @@
     return true;
   }
 
+  /// Short textual representation use for testing.
+  String get shortText {
+    StringBuffer sb = new StringBuffer();
+    if (typeParameters != 0) {
+      sb.write('<');
+      sb.write(typeParameters);
+      sb.write('>');
+    }
+    sb.write('(');
+    sb.write(positionalParameters);
+    if (namedParameters.length > 0) {
+      sb.write(',');
+      sb.write(namedParameters.join(','));
+    }
+    sb.write(')');
+    return sb.toString();
+  }
+
   String toString() {
     StringBuffer sb = new StringBuffer();
     sb.write('ParameterStructure(');
@@ -359,4 +377,7 @@
     sb.write('typeParameters=$typeParameters)');
     return sb.toString();
   }
+
+  int get size =>
+      positionalParameters + typeParameters + namedParameters.length;
 }
diff --git a/pkg/compiler/lib/src/universe/call_structure.dart b/pkg/compiler/lib/src/universe/call_structure.dart
index c574ac8..d40a26e 100644
--- a/pkg/compiler/lib/src/universe/call_structure.dart
+++ b/pkg/compiler/lib/src/universe/call_structure.dart
@@ -86,6 +86,19 @@
       ? this
       : new CallStructure(argumentCount, namedArguments);
 
+  /// Short textual representation use for testing.
+  String get shortText {
+    StringBuffer sb = new StringBuffer();
+    sb.write('(');
+    sb.write(positionalArgumentCount);
+    if (namedArgumentCount > 0) {
+      sb.write(',');
+      sb.write(getOrderedNamedArguments().join(','));
+    }
+    sb.write(')');
+    return sb.toString();
+  }
+
   /// A description of the argument structure.
   String structureToString() {
     StringBuffer sb = new StringBuffer();
diff --git a/pkg/compiler/lib/src/universe/codegen_world_builder.dart b/pkg/compiler/lib/src/universe/codegen_world_builder.dart
index 5f44099..4d4bab4 100644
--- a/pkg/compiler/lib/src/universe/codegen_world_builder.dart
+++ b/pkg/compiler/lib/src/universe/codegen_world_builder.dart
@@ -521,14 +521,14 @@
         useSet.addAll(usage.invoke(null));
       }
 
-      if (usage.pendingUse.contains(MemberUse.CLOSURIZE_INSTANCE)) {
+      if (usage.hasPendingClosurizationUse) {
         // Store the member in [instanceFunctionsByName] to catch
         // getters on the function.
         _instanceFunctionsByName
             .putIfAbsent(usage.entity.name, () => new Set<MemberUsage>())
             .add(usage);
       }
-      if (usage.pendingUse.contains(MemberUse.NORMAL)) {
+      if (usage.hasPendingNormalUse) {
         // The element is not yet used. Add it to the list of instance
         // members to still be processed.
         _instanceMembersByName
diff --git a/pkg/compiler/lib/src/universe/member_usage.dart b/pkg/compiler/lib/src/universe/member_usage.dart
index 91dbc6f..b7d31e9 100644
--- a/pkg/compiler/lib/src/universe/member_usage.dart
+++ b/pkg/compiler/lib/src/universe/member_usage.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:math' as Math;
+
 import '../common.dart';
 import '../elements/entities.dart';
 import '../js_model/elements.dart' show JSignatureMethod;
@@ -15,9 +17,6 @@
     _pendingUse.addAll(_originalUse);
   }
 
-  /// Returns the possible uses of [entity] that have not yet been registered.
-  EnumSet<T> get pendingUse => _pendingUse;
-
   /// Returns the uses of [entity] that have been registered.
   EnumSet<T> get appliedUse => _originalUse.minus(_pendingUse);
 
@@ -40,7 +39,8 @@
 
   MemberUsage.internal(this.entity);
 
-  factory MemberUsage(MemberEntity member, {bool isNative: false}) {
+  factory MemberUsage(MemberEntity member,
+      {bool isNative: false, bool trackParameters: false}) {
     if (member.isField) {
       if (member.isAssignable) {
         return new FieldUsage(member, isNative: isNative);
@@ -52,10 +52,18 @@
     } else if (member.isSetter) {
       return new SetterUsage(member);
     } else if (member.isConstructor) {
-      return new ConstructorUsage(member);
+      if (trackParameters) {
+        return new ParameterTrackingConstructorUsage(member);
+      } else {
+        return new ConstructorUsage(member);
+      }
     } else {
       assert(member.isFunction, failedAt(member, "Unexpected member: $member"));
-      return new FunctionUsage(member);
+      if (trackParameters) {
+        return new ParameterTrackingFunctionUsage(member);
+      } else {
+        return new FunctionUsage(member);
+      }
     }
   }
 
@@ -71,6 +79,29 @@
   /// followed by an invocation of the function-like value.
   bool get hasInvoke => false;
 
+  /// `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;
+
+  /// 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);
+
+  /// `true` if [entity] hasn't been closurized. This is only used for
+  /// functions.
+  bool get hasPendingClosurizationUse => false;
+
   /// `true` if [entity] has been used in all the ways possible.
   bool get fullyUsed;
 
@@ -216,9 +247,15 @@
     }
   }
 
+  FunctionEntity get entity => super.entity;
+
   EnumSet<MemberUse> get _originalUse =>
       entity.isInstanceMember ? MemberUses.ALL_INSTANCE : MemberUses.ALL_STATIC;
 
+  bool get hasPendingClosurizationUse => entity.isInstanceMember
+      ? _pendingUse.contains(MemberUse.CLOSURIZE_INSTANCE)
+      : _pendingUse.contains(MemberUse.CLOSURIZE_STATIC);
+
   @override
   EnumSet<MemberUse> read() => fullyUse();
 
@@ -255,6 +292,86 @@
 
   @override
   bool get fullyUsed => hasInvoke && hasRead;
+
+  @override
+  ParameterStructure get invokedParameters =>
+      hasInvoke ? entity.parameterStructure : null;
+}
+
+class ParameterTrackingFunctionUsage extends MemberUsage {
+  bool hasRead = false;
+
+  final ParameterUsage _parameterUsage;
+
+  ParameterTrackingFunctionUsage(FunctionEntity function)
+      : _parameterUsage = new ParameterUsage(function.parameterStructure),
+        super.internal(function) {
+    if (function is JSignatureMethod) {
+      // We mark signature methods as "always used" to prevent them from being
+      // optimized away.
+      // TODO(johnniwinther): Make this a part of the regular enqueueing.
+      invoke(CallStructure.NO_ARGS);
+    }
+  }
+
+  bool get hasInvoke => _parameterUsage.hasInvoke;
+
+  bool get hasPendingClosurizationUse => entity.isInstanceMember
+      ? _pendingUse.contains(MemberUse.CLOSURIZE_INSTANCE)
+      : _pendingUse.contains(MemberUse.CLOSURIZE_STATIC);
+
+  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;
+    }
+    bool alreadyHasInvoke = hasInvoke;
+    _parameterUsage.invoke(callStructure);
+    if (alreadyHasInvoke) {
+      return MemberUses.NONE;
+    }
+    return _pendingUse
+        .removeAll(hasRead ? MemberUses.NONE : MemberUses.NORMAL_ONLY);
+  }
+
+  @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);
+    }
+  }
+
+  @override
+  bool get hasPendingNormalUse => !isFullyInvoked;
+
+  bool get isFullyInvoked => _parameterUsage.isFullyUsed;
+
+  @override
+  bool get fullyUsed => isFullyInvoked && hasRead;
+
+  @override
+  ParameterStructure get invokedParameters => _parameterUsage.invokedParameters;
 }
 
 class GetterUsage extends MemberUsage {
@@ -327,6 +444,54 @@
 
   @override
   bool get fullyUsed => hasInvoke;
+
+  @override
+  ParameterStructure get invokedParameters =>
+      hasInvoke ? entity.parameterStructure : null;
+}
+
+class ParameterTrackingConstructorUsage extends MemberUsage {
+  final ParameterUsage _parameterUsage;
+
+  ParameterTrackingConstructorUsage(ConstructorEntity constructor)
+      : _parameterUsage = new ParameterUsage(constructor.parameterStructure),
+        super.internal(constructor);
+
+  ConstructorEntity get entity => super.entity;
+
+  EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
+
+  @override
+  EnumSet<MemberUse> invoke(CallStructure callStructure) {
+    if (isFullyInvoked) {
+      return MemberUses.NONE;
+    }
+    bool alreadyHasInvoke = hasInvoke;
+    _parameterUsage.invoke(callStructure);
+    if (alreadyHasInvoke) {
+      return MemberUses.NONE;
+    }
+    return _pendingUse
+        .removeAll(hasRead ? MemberUses.NONE : MemberUses.NORMAL_ONLY);
+  }
+
+  @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;
+
+  bool get isFullyInvoked => _parameterUsage.isFullyUsed;
+
+  @override
+  ParameterStructure get invokedParameters => _parameterUsage.invokedParameters;
 }
 
 /// Enum class for the possible kind of use of [MemberEntity] objects.
@@ -427,6 +592,12 @@
   EnumSet<MemberUse> fullyUse() => normalUse();
 
   @override
+  bool get hasPendingNormalUse => _pendingUse.contains(MemberUse.NORMAL);
+
+  @override
+  bool get isFullyInvoked => hasInvoke;
+
+  @override
   EnumSet<MemberUse> get _originalUse => MemberUses.NORMAL_ONLY;
 
   String toString() => '$entity:${appliedUse.iterable(MemberUse.values)}';
@@ -448,12 +619,20 @@
 
   @override
   bool get hasRead => hasNormalUse;
+
+  @override
+  bool get hasPendingClosurizationUse => false;
+
+  @override
+  ParameterStructure get invokedParameters => null;
 }
 
 class StaticFunctionUsage extends StaticMemberUsage {
   bool hasClosurization = false;
 
-  StaticFunctionUsage(MemberEntity entity) : super.internal(entity);
+  StaticFunctionUsage(FunctionEntity entity) : super.internal(entity);
+
+  FunctionEntity get entity => super.entity;
 
   EnumSet<MemberUse> tearOff() {
     if (hasClosurization) {
@@ -467,6 +646,10 @@
   EnumSet<MemberUse> get _originalUse => MemberUses.ALL_STATIC;
 
   @override
+  bool get hasPendingClosurizationUse =>
+      _pendingUse.contains(MemberUse.CLOSURIZE_STATIC);
+
+  @override
   bool get fullyUsed => hasNormalUse && hasClosurization;
 
   @override
@@ -477,4 +660,100 @@
 
   @override
   bool get hasRead => hasClosurization;
+
+  @override
+  ParameterStructure get invokedParameters =>
+      hasInvoke ? entity.parameterStructure : null;
+}
+
+/// Object used for tracking parameter use in constructor and method
+/// invocations.
+class ParameterUsage {
+  /// The original parameter structure of the method or constructor.
+  final ParameterStructure _parameterStructure;
+
+  /// `true` if the method or constructor has at least one invocation.
+  bool _hasInvoke;
+
+  /// The maximum number of (optional) positional parameters provided in
+  /// invocations of the method or constructor.
+  ///
+  /// If all positional parameters having been provided this is set to `null`.
+  int _providedPositionalParameters;
+
+  /// `true` if all type parameters have been provided in at least one
+  /// invocation of the method or constructor.
+  bool _areAllTypeParametersProvided;
+
+  /// The set of named parameters that have been provided in at least one
+  /// invocation of the method or constructor.
+  ///
+  /// If all named parameters have been provided this is set to `null`.
+  Set<String> _providedNamedParameters;
+
+  ParameterUsage(this._parameterStructure) {
+    _hasInvoke = false;
+    _areAllTypeParametersProvided = _parameterStructure.typeParameters == 0;
+    _providedPositionalParameters = _parameterStructure.positionalParameters ==
+            _parameterStructure.requiredParameters
+        ? null
+        : 0;
+    if (!_parameterStructure.namedParameters.isEmpty) {
+      _providedNamedParameters =
+          new Set<String>.from(_parameterStructure.namedParameters);
+    }
+  }
+
+  void invoke(CallStructure callStructure) {
+    if (isFullyUsed) return;
+    _hasInvoke = true;
+    if (_providedPositionalParameters != null) {
+      _providedPositionalParameters = Math.max(
+          _providedPositionalParameters, callStructure.positionalArgumentCount);
+      if (_providedPositionalParameters >=
+          _parameterStructure.positionalParameters) {
+        _providedPositionalParameters = null;
+      }
+    }
+    if (_providedNamedParameters != null &&
+        callStructure.namedArguments.isNotEmpty) {
+      _providedNamedParameters.removeAll(callStructure.namedArguments);
+      if (_providedNamedParameters.isEmpty) {
+        _providedNamedParameters = null;
+      }
+    }
+    if (!_areAllTypeParametersProvided && callStructure.typeArgumentCount > 0) {
+      _areAllTypeParametersProvided = true;
+    }
+  }
+
+  bool get hasInvoke => _hasInvoke;
+
+  bool get isFullyUsed =>
+      _hasInvoke &&
+      _providedPositionalParameters == null &&
+      _providedNamedParameters == null &&
+      _areAllTypeParametersProvided;
+
+  void fullyUse() {
+    _hasInvoke = true;
+    _providedPositionalParameters = null;
+    _providedNamedParameters = null;
+    _areAllTypeParametersProvided = true;
+  }
+
+  ParameterStructure get invokedParameters {
+    if (!_hasInvoke) return null;
+    if (isFullyUsed) return _parameterStructure;
+    return new ParameterStructure(
+        _parameterStructure.requiredParameters,
+        _providedPositionalParameters ??
+            _parameterStructure.positionalParameters,
+        _providedNamedParameters == null
+            ? _parameterStructure.namedParameters
+            : _parameterStructure.namedParameters
+                .where((n) => !_providedNamedParameters.contains(n))
+                .toList(),
+        _areAllTypeParametersProvided ? _parameterStructure.typeParameters : 0);
+  }
 }
diff --git a/pkg/compiler/lib/src/universe/resolution_world_builder.dart b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
index 77a8db9..c3e3aa3 100644
--- a/pkg/compiler/lib/src/universe/resolution_world_builder.dart
+++ b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
@@ -22,11 +22,12 @@
 import '../kernel/kernel_world.dart';
 import '../native/enqueue.dart' show NativeResolutionEnqueuer;
 import '../options.dart';
-import '../universe/class_set.dart';
 import '../util/enumset.dart';
 import '../util/util.dart';
 import '../world.dart' show KClosedWorld, OpenWorld;
+import 'call_structure.dart';
 import 'class_hierarchy.dart' show ClassHierarchyBuilder, ClassQueries;
+import 'class_set.dart';
 import 'member_usage.dart';
 import 'selector.dart' show Selector;
 import 'use.dart'
@@ -328,10 +329,12 @@
 
   Map<ClassEntity, ClassUsage> get classUsageForTesting => _processedClasses;
 
-  /// Map of registered usage of static members of live classes.
+  /// Map of registered usage of members of live classes.
   final Map<MemberEntity, MemberUsage> _memberUsage =
       <MemberEntity, MemberUsage>{};
 
+  Map<MemberEntity, MemberUsage> get memberUsageForTesting => _memberUsage;
+
   Map<MemberEntity, MemberUsage> get staticMemberUsageForTesting {
     Map<MemberEntity, MemberUsage> map = <MemberEntity, MemberUsage>{};
     _memberUsage.forEach((MemberEntity member, MemberUsage usage) {
@@ -352,8 +355,8 @@
     return map;
   }
 
-  /// Map containing instance members of live classes that are not yet live
-  /// themselves.
+  /// Map containing instance members of live classes that are not yet fully
+  /// live themselves.
   final Map<String, Set<MemberUsage>> _instanceMembersByName =
       <String, Set<MemberUsage>>{};
 
@@ -581,6 +584,22 @@
     getInstantiationMap().forEach(f);
   }
 
+  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, this)) {
+          callStructures ??= new Set<CallStructure>();
+          callStructures.add(selector.callStructure);
+        }
+      }
+    }
+    return callStructures ?? const <CallStructure>[];
+  }
+
   bool _hasMatchingSelector(
       Map<Selector, SelectorConstraints> selectors, MemberEntity member) {
     if (selectors == null) return false;
@@ -600,8 +619,8 @@
     return _instantiationInfo;
   }
 
-  bool _hasInvocation(MemberEntity member) {
-    return _hasMatchingSelector(_invokedNames[member.name], member);
+  Iterable<CallStructure> _getInvocationCallStructures(MemberEntity member) {
+    return _getMatchingCallStructures(_invokedNames[member.name], member);
   }
 
   bool _hasInvokedGetter(MemberEntity member) {
@@ -618,8 +637,10 @@
     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 shouldBeRemove(MemberUsage usage)) {
       _processSet(memberMap, methodName, (MemberUsage usage) {
         if (selector.appliesUnnamed(usage.entity) &&
             _selectorConstraintsStrategy.appliedUnnamed(
@@ -636,19 +657,24 @@
         registerDynamicInvocation(
             dynamicUse.selector, dynamicUse.typeArguments);
         if (_registerNewSelector(dynamicUse, _invokedNames)) {
-          _process(_instanceMembersByName,
-              (m) => m.invoke(dynamicUse.selector.callStructure));
+          _process(
+              _instanceMembersByName,
+              (m) => m.invoke(dynamicUse.selector.callStructure),
+              (u) => !u.hasPendingNormalUse);
         }
         break;
       case DynamicUseKind.GET:
         if (_registerNewSelector(dynamicUse, _invokedGetters)) {
-          _process(_instanceMembersByName, (m) => m.read());
-          _process(_instanceFunctionsByName, (m) => m.read());
+          _process(_instanceMembersByName, (m) => m.read(),
+              (u) => !u.hasPendingNormalUse);
+          _process(_instanceFunctionsByName, (m) => m.read(),
+              (u) => !u.hasPendingClosurizationUse);
         }
         break;
       case DynamicUseKind.SET:
         if (_registerNewSelector(dynamicUse, _invokedSetters)) {
-          _process(_instanceMembersByName, (m) => m.write());
+          _process(_instanceMembersByName, (m) => m.write(),
+              (u) => !u.hasPendingNormalUse);
         }
         break;
     }
@@ -703,7 +729,7 @@
     MemberEntity element = staticUse.element;
     EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
     MemberUsage usage = _memberUsage.putIfAbsent(element, () {
-      MemberUsage usage = new MemberUsage(element);
+      MemberUsage usage = new MemberUsage(element, trackParameters: true);
       useSet.addAll(usage.appliedUse);
       return usage;
     });
@@ -843,7 +869,8 @@
       newUsage = true;
       bool isNative = _nativeBasicData.isNativeClass(cls);
       EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
-      MemberUsage usage = new MemberUsage(member, isNative: isNative);
+      MemberUsage usage =
+          new MemberUsage(member, isNative: isNative, trackParameters: true);
       useSet.addAll(usage.appliedUse);
       if (member.isField && isNative) {
         registerUsedElement(member);
@@ -857,21 +884,28 @@
       if (!usage.hasRead && _hasInvokedGetter(member)) {
         useSet.addAll(usage.read());
       }
-      if (!usage.hasInvoke && _hasInvocation(member)) {
-        useSet.addAll(usage.invoke(null));
+      if (!usage.isFullyInvoked) {
+        Iterable<CallStructure> callStructures =
+            _getInvocationCallStructures(member);
+        for (CallStructure callStructure in callStructures) {
+          useSet.addAll(usage.invoke(callStructure));
+          if (usage.isFullyInvoked) {
+            break;
+          }
+        }
       }
       if (!usage.hasWrite && hasInvokedSetter(member)) {
         useSet.addAll(usage.write());
       }
 
-      if (usage.pendingUse.contains(MemberUse.NORMAL)) {
+      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>())
             .add(usage);
       }
-      if (usage.pendingUse.contains(MemberUse.CLOSURIZE_INSTANCE)) {
+      if (usage.hasPendingClosurizationUse) {
         // Store the member in [instanceFunctionsByName] to catch
         // getters on the function.
         _instanceFunctionsByName
@@ -886,16 +920,23 @@
       if (!usage.hasRead && _hasInvokedGetter(member)) {
         useSet.addAll(usage.read());
       }
-      if (!usage.hasInvoke && _hasInvocation(member)) {
-        useSet.addAll(usage.invoke(null));
+      if (!usage.isFullyInvoked) {
+        Iterable<CallStructure> callStructures =
+            _getInvocationCallStructures(member);
+        for (CallStructure callStructure in callStructures) {
+          useSet.addAll(usage.invoke(callStructure));
+          if (usage.isFullyInvoked) {
+            break;
+          }
+        }
       }
       if (!usage.hasWrite && hasInvokedSetter(member)) {
         useSet.addAll(usage.write());
       }
-      if (!usage.pendingUse.contains(MemberUse.NORMAL)) {
+      if (!usage.hasPendingNormalUse) {
         _instanceMembersByName[memberName]?.remove(usage);
       }
-      if (!usage.pendingUse.contains(MemberUse.CLOSURIZE_INSTANCE)) {
+      if (!usage.hasPendingClosurizationUse) {
         _instanceFunctionsByName[memberName]?.remove(usage);
       }
       memberUsed(usage.entity, useSet);
diff --git a/pkg/compiler/lib/src/universe/use.dart b/pkg/compiler/lib/src/universe/use.dart
index ab8e69c..ab3ffbb 100644
--- a/pkg/compiler/lib/src/universe/use.dart
+++ b/pkg/compiler/lib/src/universe/use.dart
@@ -64,13 +64,7 @@
       sb.write('>');
     }
     if (selector.isCall) {
-      sb.write('(');
-      sb.write(selector.callStructure.positionalArgumentCount);
-      if (selector.callStructure.namedArgumentCount > 0) {
-        sb.write(',');
-        sb.write(selector.callStructure.getOrderedNamedArguments().join(','));
-      }
-      sb.write(')');
+      sb.write(selector.callStructure.shortText);
     } else if (selector.isSetter) {
       sb.write('=');
     }
diff --git a/pkg/compiler/lib/src/util/features.dart b/pkg/compiler/lib/src/util/features.dart
index 5263b40..652f8ab 100644
--- a/pkg/compiler/lib/src/util/features.dart
+++ b/pkg/compiler/lib/src/util/features.dart
@@ -68,6 +68,7 @@
   /// encoded in `[...]` will be parsed as lists of strings.
   static Features fromText(String text) {
     Features features = new Features();
+    if (text == null) return features;
     int index = 0;
     while (index < text.length) {
       int eqPos = text.indexOf('=', index);
diff --git a/tests/compiler/dart2js/equivalence/id_equivalence_helper.dart b/tests/compiler/dart2js/equivalence/id_equivalence_helper.dart
index 84bad06..cfd8f5c 100644
--- a/tests/compiler/dart2js/equivalence/id_equivalence_helper.dart
+++ b/tests/compiler/dart2js/equivalence/id_equivalence_helper.dart
@@ -750,7 +750,7 @@
           }
         } else if (expectedValue != actualValue) {
           errorsFound.add(
-              "Mismatch for $key: expected '$expectedValue', found '${actualValue}");
+              "Mismatch for $key: expected '$expectedValue', found '${actualValue}'");
         }
       });
       return errorsFound.isNotEmpty ? errorsFound.join('\n ') : null;
diff --git a/tests/compiler/dart2js/member_usage/data/constructors.dart b/tests/compiler/dart2js/member_usage/data/constructors.dart
new file mode 100644
index 0000000..fe9889d
--- /dev/null
+++ b/tests/compiler/dart2js/member_usage/data/constructors.dart
@@ -0,0 +1,95 @@
+// 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 Class {
+  /*element: Class.constructor1:invoke*/
+  Class.constructor1() {}
+
+  /*element: Class.constructor2a:invoke=(0)*/
+  Class.constructor2a([a]) {}
+
+  /*element: Class.constructor2b:invoke*/
+  Class.constructor2b([a]) {}
+
+  /*element: Class.constructor2c:invoke*/
+  Class.constructor2c([a]) {}
+
+  /*element: Class.constructor3a:invoke=(0)*/
+  Class.constructor3a([a, b]) {}
+
+  /*element: Class.constructor3b:invoke=(1)*/
+  Class.constructor3b([a, b]) {}
+
+  /*element: Class.constructor3c:invoke*/
+  Class.constructor3c([a, b]) {}
+
+  /*element: Class.constructor4a:invoke=(0)*/
+  Class.constructor4a({a}) {}
+
+  /*element: Class.constructor4b:invoke*/
+  Class.constructor4b({a}) {}
+
+  /*element: Class.constructor4c:invoke*/
+  Class.constructor4c({a}) {}
+
+  /*element: Class.constructor5a:invoke=(0)*/
+  Class.constructor5a({a, b}) {}
+
+  /*element: Class.constructor5b:invoke=(0,a)*/
+  Class.constructor5b({a, b}) {}
+
+  /*element: Class.constructor5c:invoke=(0,b)*/
+  Class.constructor5c({a, b}) {}
+
+  /*element: Class.constructor6a:invoke=(1)*/
+  Class.constructor6a(a, [b, c]) {}
+
+  /*element: Class.constructor6b:invoke=(2)*/
+  Class.constructor6b(a, [b, c]) {}
+
+  /*element: Class.constructor6c:invoke*/
+  Class.constructor6c(a, [b, c]) {}
+
+  /*element: Class.constructor7a:invoke=(1)*/
+  Class.constructor7a(a, {b, c}) {}
+
+  /*element: Class.constructor7b:invoke=(1,b)*/
+  Class.constructor7b(a, {b, c}) {}
+
+  /*element: Class.constructor7c:invoke=(1,c)*/
+  Class.constructor7c(a, {b, c}) {}
+}
+
+/*element: main:invoke*/
+main() {
+  new Class.constructor1();
+
+  new Class.constructor2a();
+  new Class.constructor2b(null);
+  new Class.constructor2c();
+  new Class.constructor2c(null);
+
+  new Class.constructor3a();
+  new Class.constructor3b();
+  new Class.constructor3b(null);
+  new Class.constructor3c(null, null);
+
+  new Class.constructor4a();
+  new Class.constructor4b(a: null);
+  new Class.constructor4c();
+  new Class.constructor4c(a: null);
+
+  new Class.constructor5a();
+  new Class.constructor5b(a: null);
+  new Class.constructor5c(b: null);
+
+  new Class.constructor6a(null);
+  new Class.constructor6b(null);
+  new Class.constructor6b(null, null);
+  new Class.constructor6c(null, null, null);
+
+  new Class.constructor7a(null);
+  new Class.constructor7b(null, b: null);
+  new Class.constructor7c(null, c: null);
+}
diff --git a/tests/compiler/dart2js/member_usage/data/fields.dart b/tests/compiler/dart2js/member_usage/data/fields.dart
new file mode 100644
index 0000000..22a24a9
--- /dev/null
+++ b/tests/compiler/dart2js/member_usage/data/fields.dart
@@ -0,0 +1,80 @@
+// 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.
+
+/*element: field1a:read*/
+var field1a;
+
+/*element: field1b:write*/
+var field1b;
+
+/*element: field1c:read,write*/
+var field1c;
+
+/*element: field2a:read*/
+get field2a => 42;
+
+set field2a(_) {}
+
+get field2b => 42;
+
+/*element: field2b=:write*/
+set field2b(_) {}
+
+/*element: field2c:read*/
+get field2c => 42;
+
+/*element: field2c=:write*/
+set field2c(_) {}
+
+/*element: Class.:invoke*/
+class Class {
+  /*element: Class.field1a:read*/
+  var field1a;
+
+  /*element: Class.field1b:write*/
+  var field1b;
+
+  /*element: Class.field1c:read,write*/
+  var field1c;
+
+  /*element: Class.field2a:read*/
+  get field2a => 42;
+
+  set field2a(_) {}
+
+  get field2b => 42;
+
+  /*element: Class.field2b=:write*/
+  set field2b(_) {}
+
+  /*element: Class.field2c:read*/
+  get field2c => 42;
+
+  /*element: Class.field2c=:write*/
+  set field2c(_) {}
+
+  /*element: Class.test:invoke*/
+  test() {
+    field1a;
+    field1b = 42;
+    field1c = field1c;
+
+    field2a;
+    field2b = 42;
+    field2c = field2c;
+  }
+}
+
+/*element: main:invoke*/
+main() {
+  field1a;
+  field1b = 42;
+  field1c = field1c;
+
+  field2a;
+  field2b = 42;
+  field2c = field2c;
+
+  new Class().test();
+}
diff --git a/tests/compiler/dart2js/member_usage/data/general.dart b/tests/compiler/dart2js/member_usage/data/general.dart
new file mode 100644
index 0000000..0cb1266
--- /dev/null
+++ b/tests/compiler/dart2js/member_usage/data/general.dart
@@ -0,0 +1,212 @@
+// 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.
+
+/*element: A.:invoke*/
+class A {
+  /*element: A.method1:invoke*/
+  method1() {}
+
+  method2() {}
+
+  /*element: A.method4:invoke*/
+  method4() {}
+
+  /*element: A.getter:read*/
+  get getter => 42;
+
+  set setter(_) {}
+}
+
+/*element: B.:invoke*/
+class B {
+  method1() {}
+  method2() {}
+
+  /*element: B.method5:invoke*/
+  method5() {}
+  get getter => 42;
+
+  /*element: B.setter=:write*/
+  set setter(_) {}
+}
+
+/*element: C.:invoke*/
+class C extends A {
+  /*element: C.method1:invoke*/
+  method1() {}
+
+  /*element: B.method2:invoke*/
+  method2() {}
+  method4() {}
+
+  /*element: C.getter:read*/
+  get getter => 42;
+  set setter(_) {}
+}
+
+/*element: D.:invoke*/
+class D implements B {
+  method1() {}
+
+  /*element: D.method2:invoke*/
+  method2() {}
+  method5() {}
+  get getter => 42;
+
+  /*element: D.setter=:write*/
+  set setter(_) {}
+}
+
+class E implements A {
+  method1() {}
+  method2() {}
+  method4() {}
+  get getter => 42;
+  set setter(_) {}
+}
+
+class F extends B {
+  method1() {}
+  method2() {}
+  method5() {}
+  get getter => 42;
+  set setter(_) {}
+}
+
+class G {
+  /*element: G.method1:invoke*/
+  method1() {}
+  method2() {}
+  method4() {}
+
+  /*element: G.getter:read*/
+  get getter => 42;
+  set setter(_) {}
+}
+
+/*element: H.:invoke*/
+class H extends Object with G implements A {}
+
+/*element: I.:invoke*/
+class I {
+  /*element: I.method1:invoke*/
+  method1() {}
+  method2() {}
+  method4() {}
+
+  /*element: I.getter:read*/
+  get getter => 42;
+  set setter(_) {}
+}
+
+/*element: J.:invoke*/
+class J extends I implements A {}
+
+class K {
+  /*element: K.method1:invoke*/
+  method1() {}
+  method2() {}
+
+  /*element: K.getter:read*/
+  get getter => 42;
+  set setter(_) {}
+}
+
+class L = Object with K;
+class L2 = Object with L;
+
+/*element: M.:invoke*/
+class M extends L {}
+
+/*element: M2.:invoke*/
+class M2 extends L2 {}
+
+/*element: N.:invoke*/
+class N {
+  method1() {}
+  get getter => 42;
+  set setter(_) {}
+}
+
+abstract class O extends N {}
+
+/*element: P.:invoke*/
+class P implements O {
+  /*element: P.method1:invoke*/
+  method1() {}
+
+  /*element: P.getter:read*/
+  get getter => 42;
+
+  /*element: P.setter=:write*/
+  set setter(_) {}
+}
+
+/*element: Q.:invoke*/
+class Q {
+  /*element: Q.method3:invoke*/
+  method3() {}
+}
+
+/*element: R.:invoke*/
+class R extends Q {}
+
+/*element: Class1a.:invoke*/
+class Class1a {
+  /*element: Class1a.call:invoke*/
+  call(a, b, c) {} // Call structure only used in Class1a and Class2b.
+}
+
+/*element: Class1b.:invoke*/
+class Class1b {
+  call(a, b, c) {}
+}
+
+/*element: Class2.:invoke*/
+class Class2 {
+  /*element: Class2.c:read,write*/
+  Class1a c;
+}
+
+/*element: main:invoke*/
+main() {
+  method1();
+  method2();
+}
+
+/*element: method1:invoke*/
+@pragma('dart2js:disableFinal')
+method1() {
+  A a = new A();
+  B b = new B();
+  a.method1();
+  a.getter;
+  b.method2();
+  b.setter = 42;
+  new C();
+  new D();
+  new H();
+  new J();
+  new M().method1();
+  new M2().getter;
+  new N();
+  O o = new P();
+  o.method1();
+  o.getter;
+  o.setter = 42;
+  R r;
+  r.method3();
+  r = new R(); // Create R after call.
+  new Class1a();
+  new Class1b();
+  new Class2().c(0, 1, 2);
+}
+
+/*element: method2:invoke*/
+method2() {
+  A a = new A();
+  B b = new B();
+  a.method4();
+  b.method5();
+}
diff --git a/tests/compiler/dart2js/member_usage/data/instance_method_parameters.dart b/tests/compiler/dart2js/member_usage/data/instance_method_parameters.dart
new file mode 100644
index 0000000..28754c5
--- /dev/null
+++ b/tests/compiler/dart2js/member_usage/data/instance_method_parameters.dart
@@ -0,0 +1,134 @@
+// 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.
+
+/*element: Class.:invoke*/
+class Class {
+  /*element: Class.method1:invoke*/
+  method1() {}
+
+  /*element: Class.method2a:invoke=(0)*/
+  method2a([a]) {}
+
+  /*element: Class.method2b:invoke*/
+  method2b([a]) {}
+
+  /*element: Class.method2c:invoke*/
+  method2c([a]) {}
+
+  /*element: Class.method2d:invoke,read*/
+  method2d([a]) {}
+
+  /*element: Class.method3a:invoke=(0)*/
+  method3a([a, b]) {}
+
+  /*element: Class.method3b:invoke=(1)*/
+  method3b([a, b]) {}
+
+  /*element: Class.method3c:invoke*/
+  method3c([a, b]) {}
+
+  /*element: Class.method3d:invoke,read*/
+  method3d([a, b]) {}
+
+  /*element: Class.method4a:invoke=(0)*/
+  method4a({a}) {}
+
+  /*element: Class.method4b:invoke*/
+  method4b({a}) {}
+
+  /*element: Class.method4c:invoke*/
+  method4c({a}) {}
+
+  /*element: Class.method4d:invoke,read*/
+  method4d({a}) {}
+
+  /*element: Class.method5a:invoke=(0)*/
+  method5a({a, b}) {}
+
+  /*element: Class.method5b:invoke=(0,a)*/
+  method5b({a, b}) {}
+
+  /*element: Class.method5c:invoke=(0,b)*/
+  method5c({a, b}) {}
+
+  /*element: Class.method5d:invoke,read*/
+  method5d({a, b}) {}
+
+  /*element: Class.method6a:invoke*/
+  method6a<T>() {}
+
+  /*element: Class.method6b:invoke,read*/
+  method6b<T>() {}
+
+  /*element: Class.method7a:invoke=(1)*/
+  method7a(a, [b, c]) {}
+
+  /*element: Class.method7b:invoke=(2)*/
+  method7b(a, [b, c]) {}
+
+  /*element: Class.method7c:invoke*/
+  method7c(a, [b, c]) {}
+
+  /*element: Class.method7d:invoke,read*/
+  method7d(a, [b, c]) {}
+
+  /*element: Class.method8a:invoke=(1)*/
+  method8a(a, {b, c}) {}
+
+  /*element: Class.method8b:invoke=(1,b)*/
+  method8b(a, {b, c}) {}
+
+  /*element: Class.method8c:invoke=(1,c)*/
+  method8c(a, {b, c}) {}
+
+  /*element: Class.method8d:invoke,read*/
+  method8d(a, {b, c}) {}
+
+  /*element: Class.test:invoke*/
+  test() {
+    method1();
+
+    method2a();
+    method2b(null);
+    method2c();
+    method2c(null);
+    method2d;
+
+    method3a();
+    method3b();
+    method3b(null);
+    method3c(null, null);
+    method3d;
+
+    method4a();
+    method4b(a: null);
+    method4c();
+    method4c(a: null);
+    method4d;
+
+    method5a();
+    method5b(a: null);
+    method5c(b: null);
+    method5d;
+
+    method6a();
+    method6b;
+
+    method7a(null);
+    method7b(null);
+    method7b(null, null);
+    method7c(null, null, null);
+    method7d;
+
+    method8a(null);
+    method8b(null, b: null);
+    method8c(null, c: null);
+    method8d;
+  }
+}
+
+/*element: main:invoke*/
+main() {
+  new Class().test();
+}
diff --git a/tests/compiler/dart2js/member_usage/data/static_method_parameters.dart b/tests/compiler/dart2js/member_usage/data/static_method_parameters.dart
new file mode 100644
index 0000000..bb133a2
--- /dev/null
+++ b/tests/compiler/dart2js/member_usage/data/static_method_parameters.dart
@@ -0,0 +1,126 @@
+// 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.
+
+/*element: method1:invoke*/
+method1() {}
+
+/*element: method2a:invoke=(0)*/
+method2a([a]) {}
+
+/*element: method2b:invoke*/
+method2b([a]) {}
+
+/*element: method2c:invoke*/
+method2c([a]) {}
+
+/*element: method2d:invoke,read*/
+method2d([a]) {}
+
+/*element: method3a:invoke=(0)*/
+method3a([a, b]) {}
+
+/*element: method3b:invoke=(1)*/
+method3b([a, b]) {}
+
+/*element: method3c:invoke*/
+method3c([a, b]) {}
+
+/*element: method3d:invoke,read*/
+method3d([a, b]) {}
+
+/*element: method4a:invoke=(0)*/
+method4a({a}) {}
+
+/*element: method4b:invoke*/
+method4b({a}) {}
+
+/*element: method4c:invoke*/
+method4c({a}) {}
+
+/*element: method4d:invoke,read*/
+method4d({a}) {}
+
+/*element: method5a:invoke=(0)*/
+method5a({a, b}) {}
+
+/*element: method5b:invoke=(0,a)*/
+method5b({a, b}) {}
+
+/*element: method5c:invoke=(0,b)*/
+method5c({a, b}) {}
+
+/*element: method5d:invoke,read*/
+method5d({a, b}) {}
+
+/*element: method6a:invoke*/
+method6a<T>() {}
+
+/*element: method6b:invoke,read*/
+method6b<T>() {}
+
+/*element: method7a:invoke=(1)*/
+method7a(a, [b, c]) {}
+
+/*element: method7b:invoke=(2)*/
+method7b(a, [b, c]) {}
+
+/*element: method7c:invoke*/
+method7c(a, [b, c]) {}
+
+/*element: method7d:invoke,read*/
+method7d(a, [b, c]) {}
+
+/*element: method8a:invoke=(1)*/
+method8a(a, {b, c}) {}
+
+/*element: method8b:invoke=(1,b)*/
+method8b(a, {b, c}) {}
+
+/*element: method8c:invoke=(1,c)*/
+method8c(a, {b, c}) {}
+
+/*element: method8d:invoke,read*/
+method8d(a, {b, c}) {}
+
+/*element: main:invoke*/
+main() {
+  method1();
+
+  method2a();
+  method2b(null);
+  method2c();
+  method2c(null);
+  method2d;
+
+  method3a();
+  method3b();
+  method3b(null);
+  method3c(null, null);
+  method3d;
+
+  method4a();
+  method4b(a: null);
+  method4c();
+  method4c(a: null);
+  method4d;
+
+  method5a();
+  method5b(a: null);
+  method5c(b: null);
+  method5d;
+
+  method6a();
+  method6b;
+
+  method7a(null);
+  method7b(null);
+  method7b(null, null);
+  method7c(null, null, null);
+  method7d;
+
+  method8a(null);
+  method8b(null, b: null);
+  method8c(null, c: null);
+  method8d;
+}
diff --git a/tests/compiler/dart2js/member_usage/member_usage_test.dart b/tests/compiler/dart2js/member_usage/member_usage_test.dart
new file mode 100644
index 0000000..0590221
--- /dev/null
+++ b/tests/compiler/dart2js/member_usage/member_usage_test.dart
@@ -0,0 +1,67 @@
+// 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.
+
+import 'dart:io';
+import 'package:async_helper/async_helper.dart';
+import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/elements/entities.dart';
+import 'package:compiler/src/ir/util.dart';
+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/features.dart';
+import 'package:kernel/ast.dart' as ir;
+import '../equivalence/id_equivalence.dart';
+import '../equivalence/id_equivalence_helper.dart';
+
+main(List<String> args) {
+  asyncTest(() async {
+    Directory dataDir = new Directory.fromUri(Platform.script.resolve('data'));
+    await checkTests(dataDir, const ClosedWorldDataComputer(),
+        args: args, testOmit: false, testFrontend: true);
+  });
+}
+
+class Tags {
+  static const String read = 'read';
+  static const String write = 'write';
+  static const String invoke = 'invoke';
+}
+
+class ClosedWorldDataComputer extends DataComputer<Features> {
+  const ClosedWorldDataComputer();
+
+  @override
+  void computeMemberData(Compiler compiler, MemberEntity member,
+      Map<Id, ActualData<Features>> actualMap,
+      {bool verbose: false}) {
+    KernelFrontEndStrategy frontendStrategy = compiler.frontendStrategy;
+    ResolutionWorldBuilderImpl resolutionWorldBuilder =
+        compiler.resolutionWorldBuilder;
+    ir.Member node = frontendStrategy.elementMap.getMemberNode(member);
+    Features features = new Features();
+    MemberUsage memberUsage =
+        resolutionWorldBuilder.memberUsageForTesting[member];
+    if (memberUsage != null) {
+      if (memberUsage.hasRead) {
+        features.add(Tags.read);
+      }
+      if (memberUsage.hasWrite) {
+        features.add(Tags.write);
+      }
+      if (memberUsage.isFullyInvoked) {
+        features.add(Tags.invoke);
+      } else if (memberUsage.hasInvoke) {
+        features[Tags.invoke] = memberUsage.invokedParameters.shortText;
+      }
+    }
+    Id id = computeEntityId(node);
+    actualMap[id] = new ActualData<Features>(
+        id, features, computeSourceSpanFromTreeNode(node), member);
+  }
+
+  @override
+  DataInterpreter<Features> get dataValidator =>
+      const FeaturesDataInterpreter();
+}