Version 2.13.0-208.0.dev

Merge commit 'fc349bdbb5f9c6d4680c11858459abcb96670227' into 'dev'
diff --git a/DEPS b/DEPS
index f2572e7..2e25552 100644
--- a/DEPS
+++ b/DEPS
@@ -45,7 +45,7 @@
   # hashes. It requires access to the dart-build-access group, which EngProd
   # has.
   "co19_rev": "fddb1dce948cec277bf3dc23b45ee95e761b89fe",
-  "co19_2_rev": "6b71fed8c0f6cf396c085ba2d405d5a9af1daf45",
+  "co19_2_rev": "3642f24e2e6273c6fb65a8f60cd0edc95153942e",
 
   # The internal benchmarks to use. See go/dart-benchmarks-internal
   "benchmarks_internal_rev": "076df10d9b77af337f2d8029725787155eb1cd52",
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index e4c7261..bf501a0 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -736,8 +736,13 @@
   /// expression to the left hand side of the `.`, and [propertyName] should be
   /// the identifier to the right hand side of the `.`.  [staticType] should be
   /// the static type of the value returned by the property get.
+  ///
+  /// [propertyMember] should be whatever data structure the client uses to keep
+  /// track of the field or property being accessed.  In the event of
+  /// non-promotion of a property get, this value can be retrieved from
+  /// [PropertyNotPromoted.propertyMember].
   void propertyGet(Expression wholeExpression, Expression target,
-      String propertyName, Type staticType);
+      String propertyName, Object? propertyMember, Type staticType);
 
   /// Retrieves the SSA node associated with [variable], or `null` if [variable]
   /// is not associated with an SSA node because it is write captured.  For
@@ -788,8 +793,13 @@
   /// the whole property get, and [propertyName] should be the name of the
   /// property being read.  [staticType] should be the static type of the value
   /// returned by the property get.
-  void thisOrSuperPropertyGet(
-      Expression expression, String propertyName, Type staticType);
+  ///
+  /// [propertyMember] should be whatever data structure the client uses to keep
+  /// track of the field or property being accessed.  In the event of
+  /// non-promotion of a property get, this value can be retrieved from
+  /// [PropertyNotPromoted.propertyMember].
+  void thisOrSuperPropertyGet(Expression expression, String propertyName,
+      Object? propertyMember, Type staticType);
 
   /// Call this method just before visiting the body of a "try/catch" statement.
   ///
@@ -1339,11 +1349,12 @@
 
   @override
   void propertyGet(Expression wholeExpression, Expression target,
-      String propertyName, Type staticType) {
+      String propertyName, Object? propertyMember, Type staticType) {
     _wrap(
-        'propertyGet($wholeExpression, $target, $propertyName, $staticType)',
+        'propertyGet($wholeExpression, $target, $propertyName, '
+        '$propertyMember, $staticType)',
         () => _wrapped.propertyGet(
-            wholeExpression, target, propertyName, staticType));
+            wholeExpression, target, propertyName, propertyMember, staticType));
   }
 
   @override
@@ -1378,12 +1389,13 @@
   }
 
   @override
-  void thisOrSuperPropertyGet(
-      Expression expression, String propertyName, Type staticType) {
+  void thisOrSuperPropertyGet(Expression expression, String propertyName,
+      Object? propertyMember, Type staticType) {
     _wrap(
-        'thisOrSuperPropertyGet($expression, $propertyName, $staticType)',
+        'thisOrSuperPropertyGet($expression, $propertyName, $propertyMember, '
+        '$staticType)',
         () => _wrapped.thisOrSuperPropertyGet(
-            expression, propertyName, staticType));
+            expression, propertyName, propertyMember, staticType));
   }
 
   @override
@@ -2334,12 +2346,17 @@
   /// The name of the property.
   final String propertyName;
 
+  /// The field or property being accessed.  This matches a `propertyMember`
+  /// value that was passed to either [FlowAnalysis.propertyGet] or
+  /// [FlowAnalysis.thisOrSuperPropertyGet].
+  final Object? propertyMember;
+
   /// The static type of the property at the time of the access.  This is the
   /// type that was passed to [FlowAnalysis.whyNotPromoted]; it is provided to
   /// the client as a convenience for ID testing.
   final Type staticType;
 
-  PropertyNotPromoted(this.propertyName, this.staticType);
+  PropertyNotPromoted(this.propertyName, this.propertyMember, this.staticType);
 
   @override
   String get documentationLink => 'http://dart.dev/go/non-promo-property';
@@ -2517,8 +2534,10 @@
 
   /// Creates a reference representing a get of a property called [propertyName]
   /// on the reference represented by `this`.
-  Reference<Variable, Type> propertyGet(String propertyName) =>
-      new _PropertyGetReference<Variable, Type>(this, propertyName);
+  Reference<Variable, Type> propertyGet(
+          String propertyName, Object? propertyMember) =>
+      new _PropertyGetReference<Variable, Type>(
+          this, propertyName, propertyMember);
 
   /// Stores info for this reference in [variableInfo].
   void storeInfo(Map<Variable?, VariableModel<Variable, Type>> variableInfo,
@@ -3952,14 +3971,14 @@
 
   @override
   void propertyGet(Expression wholeExpression, Expression target,
-      String propertyName, Type staticType) {
+      String propertyName, Object? propertyMember, Type staticType) {
     Reference<Variable, Type>? reference =
         _getExpressionReference(target)?.reference;
     if (reference != null) {
       _storeExpressionReference(
           wholeExpression,
           new ReferenceWithType<Variable, Type>(
-              reference.propertyGet(propertyName), staticType));
+              reference.propertyGet(propertyName, propertyMember), staticType));
     }
   }
 
@@ -4016,12 +4035,13 @@
   }
 
   @override
-  void thisOrSuperPropertyGet(
-      Expression expression, String propertyName, Type staticType) {
+  void thisOrSuperPropertyGet(Expression expression, String propertyName,
+      Object? propertyMember, Type staticType) {
     _storeExpressionReference(
         expression,
         new ReferenceWithType<Variable, Type>(
-            new _ThisReference<Variable, Type>().propertyGet(propertyName),
+            new _ThisReference<Variable, Type>()
+                .propertyGet(propertyName, propertyMember),
             staticType));
   }
 
@@ -4686,7 +4706,7 @@
 
   @override
   void propertyGet(Expression wholeExpression, Expression target,
-      String propertyName, Type staticType) {}
+      String propertyName, Object? propertyMember, Type staticType) {}
 
   @override
   SsaNode<Variable, Type>? ssaNodeForTesting(Variable variable) {
@@ -4706,8 +4726,8 @@
   void thisOrSuper(Expression expression, Type staticType) {}
 
   @override
-  void thisOrSuperPropertyGet(
-      Expression expression, String propertyName, Type staticType) {}
+  void thisOrSuperPropertyGet(Expression expression, String propertyName,
+      Object? propertyMember, Type staticType) {}
 
   @override
   void tryCatchStatement_bodyBegin() {}
@@ -4908,7 +4928,12 @@
   /// The name of the property.
   final String propertyName;
 
-  _PropertyGetReference(this.target, this.propertyName);
+  /// The field or property being accessed.  This matches a `propertyMember`
+  /// value that was passed to either [FlowAnalysis.propertyGet] or
+  /// [FlowAnalysis.thisOrSuperPropertyGet].
+  final Object? propertyMember;
+
+  _PropertyGetReference(this.target, this.propertyName, this.propertyMember);
 
   @override
   Map<Type, NonPromotionReason> Function() getNonPromotionReasons(
@@ -4920,7 +4945,8 @@
       return () {
         Map<Type, NonPromotionReason> result = <Type, NonPromotionReason>{};
         for (Type type in promotedTypes) {
-          result[type] = new PropertyNotPromoted(propertyName, staticType);
+          result[type] =
+              new PropertyNotPromoted(propertyName, propertyMember, staticType);
         }
         return result;
       };
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index 5e41049..fd0db4b 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -1510,7 +1510,7 @@
       Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
     var targetType = target._visit(h, flow);
     var propertyType = h.getMember(targetType, propertyName);
-    flow.propertyGet(this, target, propertyName, propertyType);
+    flow.propertyGet(this, target, propertyName, propertyName, propertyType);
     return propertyType;
   }
 
@@ -1614,7 +1614,7 @@
   @override
   Type _visit(
       Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
-    flow.thisOrSuperPropertyGet(this, propertyName, type);
+    flow.thisOrSuperPropertyGet(this, propertyName, propertyName, type);
     return type;
   }
 }
diff --git a/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
index aaaa182..3b63d6c 100644
--- a/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
@@ -121,8 +121,7 @@
       CompileTimeErrorCode.INVALID_ASSIGNMENT,
       right,
       [rightType, writeType],
-      _resolver.computeWhyNotPromotedMessages(
-          right, right, whyNotPromoted?.call()),
+      _resolver.computeWhyNotPromotedMessages(right, whyNotPromoted?.call()),
     );
   }
 
diff --git a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
index caff683..a6ab6e7 100644
--- a/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/method_invocation_resolver.dart
@@ -807,7 +807,11 @@
         );
       }
       _resolver.flowAnalysis?.flow?.propertyGet(
-          functionExpression, target, node.methodName.name, getterReturnType);
+          functionExpression,
+          target,
+          node.methodName.name,
+          node.methodName.staticElement,
+          getterReturnType);
       functionExpression.staticType = targetType;
     }
 
diff --git a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
index 8a64758..e2d5336 100644
--- a/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/property_element_resolver.dart
@@ -203,8 +203,8 @@
       readElementRequested = readLookup.requested;
       if (readElementRequested is PropertyAccessorElement &&
           !readElementRequested.isStatic) {
-        _resolver.flowAnalysis?.flow?.thisOrSuperPropertyGet(
-            node, node.name, readElementRequested.returnType);
+        _resolver.flowAnalysis?.flow?.thisOrSuperPropertyGet(node, node.name,
+            readElementRequested, readElementRequested.returnType);
       }
       _resolver.checkReadOfNotAssignedLocalVariable(node, readElementRequested);
     }
@@ -373,7 +373,11 @@
       nameErrorEntity: propertyName,
     );
 
-    _resolver.flowAnalysis?.flow?.propertyGet(node, target, propertyName.name,
+    _resolver.flowAnalysis?.flow?.propertyGet(
+        node,
+        target,
+        propertyName.name,
+        result.getter,
         result.getter?.returnType ?? _typeSystem.typeProvider.dynamicType);
 
     if (hasRead && result.needsGetterError) {
@@ -653,6 +657,7 @@
             node,
             target,
             propertyName.name,
+            readElement,
             readElement?.returnType ?? _typeSystem.typeProvider.dynamicType);
       }
 
diff --git a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
index cff8b7e..4ffbaaa 100644
--- a/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/type_property_resolver.dart
@@ -130,11 +130,11 @@
       if (flow != null) {
         if (receiver != null) {
           messages = _resolver.computeWhyNotPromotedMessages(
-              receiver, nameErrorEntity, flow.whyNotPromoted(receiver)());
+              nameErrorEntity, flow.whyNotPromoted(receiver)());
         } else {
           var thisType = _resolver.thisType;
           if (thisType != null) {
-            messages = _resolver.computeWhyNotPromotedMessages(receiver,
+            messages = _resolver.computeWhyNotPromotedMessages(
                 nameErrorEntity, flow.whyNotPromotedImplicitThis(thisType)());
           }
         }
diff --git a/pkg/analyzer/lib/src/error/bool_expression_verifier.dart b/pkg/analyzer/lib/src/error/bool_expression_verifier.dart
index 43d2462..e56e3ad 100644
--- a/pkg/analyzer/lib/src/error/bool_expression_verifier.dart
+++ b/pkg/analyzer/lib/src/error/bool_expression_verifier.dart
@@ -57,7 +57,7 @@
             errorCode: CompileTimeErrorCode
                 .UNCHECKED_USE_OF_NULLABLE_VALUE_AS_CONDITION,
             messages: _resolver.computeWhyNotPromotedMessages(
-                expression, expression, whyNotPromoted?.call()));
+                expression, whyNotPromoted?.call()));
       } else {
         _errorReporter.reportErrorForNode(errorCode, expression, arguments);
       }
diff --git a/pkg/analyzer/lib/src/error/nullable_dereference_verifier.dart b/pkg/analyzer/lib/src/error/nullable_dereference_verifier.dart
index cf1f3b5..fb96d1b 100644
--- a/pkg/analyzer/lib/src/error/nullable_dereference_verifier.dart
+++ b/pkg/analyzer/lib/src/error/nullable_dereference_verifier.dart
@@ -76,8 +76,8 @@
 
     List<DiagnosticMessage>? messages;
     if (errorNode is Expression) {
-      messages = _resolver.computeWhyNotPromotedMessages(errorNode, errorNode,
-          _resolver.flowAnalysis?.flow?.whyNotPromoted(errorNode)());
+      messages = _resolver.computeWhyNotPromotedMessages(
+          errorNode, _resolver.flowAnalysis?.flow?.whyNotPromoted(errorNode)());
     }
     report(errorNode, receiverType, errorCode: errorCode, messages: messages);
     return true;
diff --git a/pkg/analyzer/lib/src/generated/error_detection_helpers.dart b/pkg/analyzer/lib/src/generated/error_detection_helpers.dart
index af745c8..ce69393 100644
--- a/pkg/analyzer/lib/src/generated/error_detection_helpers.dart
+++ b/pkg/analyzer/lib/src/generated/error_detection_helpers.dart
@@ -85,8 +85,8 @@
       }
       return;
     }
-    var messages = computeWhyNotPromotedMessages(
-        expression, expression, whyNotPromoted?.call());
+    var messages =
+        computeWhyNotPromotedMessages(expression, whyNotPromoted?.call());
     // report problem
     if (isConstConstructor) {
       // TODO(paulberry): this error should be based on the actual type of the
@@ -229,7 +229,6 @@
   /// [whyNotPromoted] should be the non-promotion details returned by the flow
   /// analysis engine.
   List<DiagnosticMessage> computeWhyNotPromotedMessages(
-      Expression? expression,
       SyntacticEntity errorEntity,
       Map<DartType, NonPromotionReason>? whyNotPromoted);
 
@@ -313,8 +312,7 @@
         errorCode,
         getErrorNode(expression),
         [actualStaticType, expectedStaticType],
-        computeWhyNotPromotedMessages(
-            expression, expression, whyNotPromoted?.call()),
+        computeWhyNotPromotedMessages(expression, whyNotPromoted?.call()),
       );
       return false;
     }
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 9601c2e9..0d097e9 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -312,7 +312,6 @@
 
   @override
   List<DiagnosticMessage> computeWhyNotPromotedMessages(
-      Expression? expression,
       SyntacticEntity errorEntity,
       Map<DartType, NonPromotionReason>? whyNotPromoted) {
     return [];
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 1a1918d..b878793 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -533,17 +533,13 @@
 
   @override
   List<DiagnosticMessage> computeWhyNotPromotedMessages(
-      Expression? expression,
       SyntacticEntity errorEntity,
       Map<DartType, NonPromotionReason>? whyNotPromoted) {
-    if (expression is NamedExpression) {
-      expression = expression.expression;
-    }
     List<DiagnosticMessage> messages = [];
     if (whyNotPromoted != null) {
       for (var entry in whyNotPromoted.entries) {
         var whyNotPromotedVisitor = _WhyNotPromotedVisitor(
-            source, expression, errorEntity, flowAnalysis!.dataForTesting);
+            source, errorEntity, flowAnalysis!.dataForTesting);
         if (typeSystem.isPotentiallyNullable(entry.key)) continue;
         var message = entry.value.accept(whyNotPromotedVisitor);
         if (message != null) {
@@ -3447,10 +3443,6 @@
             PromotableElement, DartType> {
   final Source source;
 
-  /// The expression that was not promoted, or `null` if the thing that was not
-  /// promoted was an implicit `this`.
-  final Expression? _expression;
-
   final SyntacticEntity _errorEntity;
 
   final FlowAnalysisDataForTesting? _dataForTesting;
@@ -3459,8 +3451,7 @@
 
   DartType? propertyType;
 
-  _WhyNotPromotedVisitor(
-      this.source, this._expression, this._errorEntity, this._dataForTesting);
+  _WhyNotPromotedVisitor(this.source, this._errorEntity, this._dataForTesting);
 
   @override
   DiagnosticMessage? visitDemoteViaExplicitWrite(
@@ -3480,18 +3471,7 @@
   @override
   DiagnosticMessage? visitPropertyNotPromoted(
       PropertyNotPromoted<DartType> reason) {
-    var expression = _expression;
-    Element? receiverElement;
-    if (expression is SimpleIdentifier) {
-      receiverElement = expression.staticElement;
-    } else if (expression is PropertyAccess) {
-      receiverElement = expression.propertyName.staticElement;
-    } else if (expression is PrefixedIdentifier) {
-      receiverElement = expression.identifier.staticElement;
-    } else {
-      assert(false,
-          'Unrecognized property access expression: ${expression.runtimeType}');
-    }
+    var receiverElement = reason.propertyMember;
     if (receiverElement is PropertyAccessorElement) {
       propertyReference = receiverElement;
       propertyType = reason.staticType;
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index f6928ab..81f1761 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -414,7 +414,8 @@
       assert(graph.isValid(), 'Graph not valid after ${phase.name}');
     }
 
-    runPhase(new SsaInstructionSelection(_options, _closedWorld));
+    runPhase(
+        new SsaInstructionSelection(_options, _closedWorld, _interceptorData));
     runPhase(new SsaTypeKnownRemover());
     runPhase(new SsaTrustedCheckRemover(_options));
     runPhase(new SsaAssignmentChaining(_closedWorld));
diff --git a/pkg/compiler/lib/src/ssa/codegen_helpers.dart b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
index 5bf162b..d643936 100644
--- a/pkg/compiler/lib/src/ssa/codegen_helpers.dart
+++ b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
@@ -5,6 +5,7 @@
 import '../constants/values.dart';
 import '../elements/entities.dart';
 import '../inferrer/abstract_value_domain.dart';
+import '../js_backend/interceptor_data.dart';
 import '../options.dart';
 import '../universe/selector.dart' show Selector;
 import '../world.dart' show JClosedWorld;
@@ -26,14 +27,21 @@
 ///
 /// - Remove NullChecks where the next instruction would fail on the operand.
 ///
+/// - Dummy receiver optimization.
+///
+/// - One-shot interceptor optimization.
+///
 /// - Combine read/modify/write sequences into HReadModifyWrite instructions to
 ///   simplify codegen of expressions like `a.x += y`.
+
 class SsaInstructionSelection extends HBaseVisitor with CodegenPhase {
   final JClosedWorld _closedWorld;
+  final InterceptorData _interceptorData;
   final CompilerOptions _options;
   HGraph graph;
 
-  SsaInstructionSelection(this._options, this._closedWorld);
+  SsaInstructionSelection(
+      this._options, this._closedWorld, this._interceptorData);
 
   AbstractValueDomain get _abstractValueDomain =>
       _closedWorld.abstractValueDomain;
@@ -221,6 +229,128 @@
       .isPotentiallyTrue;
 
   @override
+  HInstruction visitInvokeDynamic(HInvokeDynamic node) {
+    if (!node.isInterceptedCall) return node;
+
+    tryReplaceExplicitReceiverWithDummy(
+        node, node.selector, node.element, node.receiverType);
+
+    // Try to replace
+    //
+    //     getInterceptor(o).method(o, ...)
+    //
+    // with a 'one shot interceptor' which is a call to a synthesized static
+    // helper function that combines the two operations.
+    //
+    //     oneShotMethod(o, 1, 2)
+    //
+    // This saves code size and makes the receiver of an intercepted call a
+    // candidate for being generated at use site.
+    //
+    // Avoid combining a hoisted interceptor back into a loop, and the faster
+    // almost-constant kind of interceptor.
+
+    HInstruction interceptor = node.inputs[0];
+    if (interceptor is HInterceptor &&
+        interceptor.usedBy.length == 1 &&
+        !interceptor.isConditionalConstantInterceptor &&
+        interceptor.hasSameLoopHeaderAs(node)) {
+      // Copy inputs and replace interceptor with `null`.
+      List<HInstruction> inputs = List.of(node.inputs);
+      inputs[0] = graph.addConstantNull(_closedWorld);
+
+      HOneShotInterceptor oneShot = HOneShotInterceptor(
+          node.selector,
+          node.receiverType,
+          inputs,
+          node.instructionType,
+          node.typeArguments,
+          interceptor.interceptedClasses);
+      oneShot.sourceInformation = node.sourceInformation;
+      oneShot.sourceElement = node.sourceElement;
+      oneShot.sideEffects.setTo(node.sideEffects);
+
+      HBasicBlock block = node.block;
+      block.addAfter(node, oneShot);
+      block.rewrite(node, oneShot);
+      block.remove(node);
+      interceptor.block.remove(interceptor);
+      return null;
+    }
+
+    return node;
+  }
+
+  @override
+  HInstruction visitInvokeSuper(HInvokeSuper node) {
+    tryReplaceExplicitReceiverWithDummy(
+        node, node.selector, node.element, null);
+    return node;
+  }
+
+  @override
+  HInstruction visitOneShotInterceptor(HOneShotInterceptor node) {
+    throw StateError('Should not see HOneShotInterceptor: $node');
+  }
+
+  void tryReplaceExplicitReceiverWithDummy(HInvoke node, Selector selector,
+      MemberEntity target, AbstractValue mask) {
+    // Calls of the form
+    //
+    //     a.foo$1(a, x)
+    //
+    // where the interceptor calling convention is used come from recognizing
+    // that 'a' is a 'self-interceptor'.  If the selector matches only methods
+    // that ignore the explicit receiver parameter, replace occurrences of the
+    // receiver argument with a dummy receiver '0':
+    //
+    //     a.foo$1(a, x)   --->   a.foo$1(0, x)
+    //
+    // This often reduces the number of references to 'a' to one, allowing 'a'
+    // to be generated at use to avoid a temporary, e.g.
+    //
+    //     t1 = b.get$thing();
+    //     t1.foo$1(t1, x)
+    // --->
+    //     b.get$thing().foo$1(0, x)
+    //
+    assert(target != null || mask != null);
+
+    if (!node.isInterceptedCall) return;
+
+    // TODO(15933): Make automatically generated property extraction closures
+    // work with the dummy receiver optimization.
+    if (selector.isGetter) return;
+
+    // This assignment of inputs is uniform for HInvokeDynamic and HInvokeSuper.
+    HInstruction interceptor = node.inputs[0];
+    HInstruction receiverArgument = node.inputs[1];
+
+    // A 'self-interceptor'?
+    if (interceptor.nonCheck() != receiverArgument.nonCheck()) return;
+
+    // TODO(sra): Should this be an assert?
+    if (!_interceptorData.isInterceptedSelector(selector)) return;
+
+    if (target != null) {
+      // A call that resolves to a single instance method (element) requires the
+      // calling convention consistent with the method.
+      ClassEntity cls = target.enclosingClass;
+      assert(_interceptorData.isInterceptedMethod(target));
+      if (_interceptorData.isInterceptedClass(cls)) return;
+    } else if (_interceptorData.isInterceptedMixinSelector(
+        selector, mask, _closedWorld)) {
+      return;
+    }
+
+    ConstantValue constant = DummyInterceptorConstantValue();
+    HConstant dummy = graph.addConstant(constant, _closedWorld);
+    receiverArgument.usedBy.remove(node);
+    node.inputs[1] = dummy;
+    dummy.usedBy.add(node);
+  }
+
+  @override
   HInstruction visitFieldSet(HFieldSet setter) {
     // Pattern match
     //     t1 = x.f; t2 = t1 + 1; x.f = t2; use(t2)   -->  ++x.f
diff --git a/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart b/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart
deleted file mode 100644
index 9ec829d..0000000
--- a/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright (c) 2021, 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 '../constants/values.dart';
-import '../elements/entities.dart';
-import '../inferrer/abstract_value_domain.dart';
-import '../js_backend/interceptor_data.dart';
-import '../universe/selector.dart' show Selector;
-import '../world.dart' show JClosedWorld;
-import 'nodes.dart';
-import 'optimize.dart';
-
-/// SsaFinalizeInterceptors makes adjustments for the interceptor calling
-/// convention.
-///
-/// 1. If the method cannot be invoked with an intercepted receiver, the
-///    receiver and interceptor are the same. In this case ignore the explicit
-///    receiver argument and use the interceptor (this) as the receiver.
-///
-/// 2. The call-site dual of the above is if a method ignores the explicit
-///    receiver, it can be replaced with a dummy value, i.e. a dummy receiver
-///    optimization.
-///
-/// 3. If an interceptor is used once for a call, replace the
-///    getInterceptor-call pair with a call to a 'one-shot interceptor' outlined
-///    method.
-///
-class SsaFinalizeInterceptors extends HBaseVisitor
-    implements OptimizationPhase {
-  @override
-  String get name => "SsaFinalizeInterceptors";
-  final JClosedWorld _closedWorld;
-  HGraph _graph;
-
-  SsaFinalizeInterceptors(this._closedWorld);
-
-  InterceptorData get _interceptorData => _closedWorld.interceptorData;
-
-  @override
-  void visitGraph(HGraph graph) {
-    _graph = graph;
-    MemberEntity element = graph.element;
-
-    if (usesSelfInterceptor(element)) {
-      _redirectReceiver();
-    }
-    visitDominatorTree(graph);
-  }
-
-  @override
-  visitBasicBlock(HBasicBlock node) {
-    HInstruction instruction = node.first;
-    while (instruction != null) {
-      final next = instruction.next;
-      instruction.accept(this);
-      instruction = next;
-    }
-  }
-
-  /// Returns `true` if [element] is an instance method that uses the
-  /// interceptor calling convention but the instance and interceptor arguments
-  /// will always be the same value.
-  bool usesSelfInterceptor(MemberEntity element) {
-    if (!_interceptorData.isInterceptedMethod(element)) return false;
-    ClassEntity cls = element.enclosingClass;
-    return !_interceptorData.isInterceptedClass(cls);
-  }
-
-  void _redirectReceiver() {
-    // The entry block contains the parameters in order, starting with `this`,
-    // and then the explicit receiver. There are other instructions in the
-    // block, like constants, which we ignore.
-    HThis thisParameter;
-    HParameterValue receiverParameter;
-    for (HInstruction node = _graph.entry.first;
-        node != null;
-        node = node.next) {
-      if (node is HParameterValue) {
-        if (node is HThis) {
-          thisParameter = node;
-        } else {
-          receiverParameter = node;
-          break;
-        }
-      }
-    }
-    assert(thisParameter != null,
-        '`this` parameter should be before other parameters');
-    assert(receiverParameter != null,
-        'Intercepted convention requires explicit receiver');
-    thisParameter.instructionType = receiverParameter.instructionType;
-    receiverParameter.block.rewrite(receiverParameter, thisParameter);
-    receiverParameter.sourceElement = const _RenameToUnderscore();
-  }
-
-  @override
-  void visitInvokeDynamic(HInvokeDynamic node) {
-    if (!node.isInterceptedCall) return;
-
-    if (node.element != null) {
-      tryReplaceExplicitReceiverForTargetWithDummy(
-          node, node.selector, node.element);
-    } else {
-      tryReplaceExplicitReceiverForSelectorWithDummy(
-          node, node.selector, node.receiverType);
-    }
-
-    // Try to replace
-    //
-    //     getInterceptor(o).method(o, ...)
-    //
-    // with a 'one shot interceptor' which is a call to a synthesized static
-    // helper function that combines the two operations.
-    //
-    //     oneShotMethod(o, 1, 2)
-    //
-    // This saves code size and makes the receiver of an intercepted call a
-    // candidate for being generated at use site.
-    //
-    // Avoid combining a hoisted interceptor back into a loop, and the faster
-    // almost-constant kind of interceptor.
-
-    HInstruction interceptor = node.inputs[0];
-    if (interceptor is HInterceptor &&
-        interceptor.usedBy.length == 1 &&
-        !interceptor.isConditionalConstantInterceptor &&
-        interceptor.hasSameLoopHeaderAs(node)) {
-      // Copy inputs and replace interceptor with `null`.
-      List<HInstruction> inputs = List.of(node.inputs);
-      inputs[0] = _graph.addConstantNull(_closedWorld);
-
-      HOneShotInterceptor oneShot = HOneShotInterceptor(
-          node.selector,
-          node.receiverType,
-          inputs,
-          node.instructionType,
-          node.typeArguments,
-          interceptor.interceptedClasses);
-      oneShot.sourceInformation = node.sourceInformation;
-      oneShot.sourceElement = node.sourceElement;
-      oneShot.sideEffects.setTo(node.sideEffects);
-
-      HBasicBlock block = node.block;
-      block.addAfter(node, oneShot);
-      block.rewrite(node, oneShot);
-      block.remove(node);
-      interceptor.block.remove(interceptor);
-    }
-  }
-
-  @override
-  void visitInvokeSuper(HInvokeSuper node) {
-    if (!node.isInterceptedCall) return;
-    tryReplaceExplicitReceiverForTargetWithDummy(
-        node, node.selector, node.element);
-  }
-
-  @override
-  void visitInvokeGeneratorBody(HInvokeGeneratorBody node) {
-    tryReplaceExplicitReceiverForTargetWithDummy(node, null, node.element);
-  }
-
-  @override
-  void visitOneShotInterceptor(HOneShotInterceptor node) {
-    throw StateError('Should not see HOneShotInterceptor: $node');
-  }
-
-  void tryReplaceExplicitReceiverForTargetWithDummy(
-      HInvoke node, Selector selector, MemberEntity target) {
-    assert(target != null);
-
-    // TODO(15933): Make automatically generated property extraction closures
-    // work with the dummy receiver optimization.
-    if (selector != null && selector.isGetter) return;
-
-    if (usesSelfInterceptor(target)) {
-      _replaceReceiverArgumentWithDummy(node, 1);
-    }
-  }
-
-  void tryReplaceExplicitReceiverForSelectorWithDummy(
-      HInvoke node, Selector selector, AbstractValue mask) {
-    assert(mask != null);
-    // Calls of the form
-    //
-    //     a.foo$1(a, x)
-    //
-    // where the interceptor calling convention is used come from recognizing
-    // that 'a' is a 'self-interceptor'.  If the selector matches only methods
-    // that ignore the explicit receiver parameter, replace occurrences of the
-    // receiver argument with a dummy receiver '0':
-    //
-    //     a.foo$1(a, x)   --->   a.foo$1(0, x)
-    //
-    // This often reduces the number of references to 'a' to one, allowing 'a'
-    // to be generated at use to avoid a temporary, e.g.
-    //
-    //     t1 = b.get$thing();
-    //     t1.foo$1(t1, x)
-    // --->
-    //     b.get$thing().foo$1(0, x)
-    //
-
-    // TODO(15933): Make automatically generated property extraction closures
-    // work with the dummy receiver optimization.
-    if (selector.isGetter) return;
-
-    // This assignment of inputs is uniform for HInvokeDynamic and HInvokeSuper.
-    HInstruction interceptor = node.inputs[0];
-    HInstruction receiverArgument = node.inputs[1];
-
-    // A 'self-interceptor'?
-    if (interceptor.nonCheck() != receiverArgument.nonCheck()) return;
-
-    // TODO(sra): Should this be an assert?
-    if (!_interceptorData.isInterceptedSelector(selector)) return;
-
-    if (!_interceptorData.isInterceptedMixinSelector(
-        selector, mask, _closedWorld)) {
-      _replaceReceiverArgumentWithDummy(node, 1);
-    }
-  }
-
-  void _replaceReceiverArgumentWithDummy(HInvoke node, int receiverIndex) {
-    HInstruction receiverArgument = node.inputs[receiverIndex];
-    ConstantValue constant = DummyInterceptorConstantValue();
-    HConstant dummy = _graph.addConstant(constant, _closedWorld);
-    receiverArgument.usedBy.remove(node);
-    node.inputs[receiverIndex] = dummy;
-    dummy.usedBy.add(node);
-  }
-}
-
-/// A simple Entity to rename the unused receiver to `_` in non-minified code.
-class _RenameToUnderscore implements Entity {
-  const _RenameToUnderscore();
-  @override
-  String get name => '_';
-}
diff --git a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
index c0b6254..968cf4f 100644
--- a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
+++ b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
@@ -12,16 +12,20 @@
 import 'nodes.dart';
 import 'optimize.dart';
 
-/// This phase computes the set of classes dispatched by an interceptor, and
-/// simplifies interceptors in multiple ways:
+/// This phase simplifies interceptors in multiple ways:
 ///
 /// 1) If the interceptor is for an object whose type is known, it
-///    tries to use a constant interceptor instead.
+/// tries to use a constant interceptor instead.
 ///
 /// 2) Interceptors are specialized based on the selector it is used with.
 ///
 /// 3) If we know the object is not intercepted, we just use the object
-///    instead.
+/// instead.
+///
+/// 4) Single use interceptors at dynamic invoke sites are replaced with 'one
+/// shot interceptors' which are synthesized static helper functions that fetch
+/// the interceptor and then call the method.  This saves code size and makes the
+/// receiver of an intercepted call a candidate for being generated at use site.
 ///
 class SsaSimplifyInterceptors extends HBaseVisitor
     implements OptimizationPhase {
diff --git a/pkg/compiler/lib/src/ssa/locals_handler.dart b/pkg/compiler/lib/src/ssa/locals_handler.dart
index 502d335..ea01915 100644
--- a/pkg/compiler/lib/src/ssa/locals_handler.dart
+++ b/pkg/compiler/lib/src/ssa/locals_handler.dart
@@ -288,7 +288,9 @@
         element.isGenerativeConstructor &&
         _nativeData.isNativeOrExtendsNative(cls);
     if (_interceptorData.isInterceptedMethod(element)) {
-      SyntheticLocal parameter = createLocal('receiver');
+      bool isInterceptedClass = _interceptorData.isInterceptedClass(cls);
+      String name = isInterceptedClass ? 'receiver' : '_';
+      SyntheticLocal parameter = createLocal(name);
       HParameterValue value = new HParameterValue(parameter, getTypeOfThis());
       builder.graph.explicitReceiverParameter = value;
       builder.graph.entry.addAfter(directLocals[scopeInfo.thisLocal], value);
@@ -296,7 +298,10 @@
         // If this is the first parameter inserted, make sure it stays first.
         builder.lastAddedParameter = value;
       }
-      directLocals[scopeInfo.thisLocal] = value;
+      if (isInterceptedClass) {
+        // Only use the extra parameter in intercepted classes.
+        directLocals[scopeInfo.thisLocal] = value;
+      }
     } else if (isNativeUpgradeFactory) {
       SyntheticLocal parameter = createLocal('receiver');
       // Unlike `this`, receiver is nullable since direct calls to generative
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index 8a819a1..e0f7369 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -34,7 +34,6 @@
 import '../util/util.dart';
 import '../world.dart' show JClosedWorld;
 import 'interceptor_simplifier.dart';
-import 'interceptor_finalizer.dart';
 import 'logging.dart';
 import 'nodes.dart';
 import 'types.dart';
@@ -130,9 +129,9 @@
       ];
       phases.forEach(runPhase);
 
-      // Simplifying interceptors is just an optimization, it is required for
-      // implementation correctness because the code generator assumes it is
-      // always performed to compute the intercepted classes sets.
+      // Simplifying interceptors is not strictly just an optimization, it is
+      // required for implementation correctness because the code generator
+      // assumes it is always performed.
       runPhase(new SsaSimplifyInterceptors(closedWorld, member.enclosingClass));
 
       SsaDeadCodeEliminator dce = new SsaDeadCodeEliminator(closedWorld, this);
@@ -164,13 +163,6 @@
       }
       phases.forEach(runPhase);
     });
-
-    // SsaFinalizeInterceptors must always be run to ensure consistent calling
-    // conventions between SSA-generated code and other code fragments generated
-    // by the emitter.
-    // TODO(sra): Generate these other fragments via SSA, then this phase
-    // becomes an opt-in optimization.
-    runPhase(SsaFinalizeInterceptors(closedWorld));
   }
 }
 
diff --git a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
index 89fdd5e..3458ef6 100644
--- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -1480,7 +1480,6 @@
               replacement = inferrer.helper.buildProblem(
                   messageNullableSpreadError, receiver.fileOffset, 1,
                   context: inferrer.getWhyNotPromotedContext(
-                      receiver,
                       inferrer.flowAnalysis?.whyNotPromoted(receiver)(),
                       element,
                       (type) => !type.isPotentiallyNullable));
@@ -1550,7 +1549,6 @@
             replacement = inferrer.helper.buildProblem(
                 messageNullableSpreadError, receiver.fileOffset, 1,
                 context: inferrer.getWhyNotPromotedContext(
-                    receiver,
                     inferrer.flowAnalysis?.whyNotPromoted(receiver)(),
                     element,
                     (type) => !type.isPotentiallyNullable));
@@ -1988,7 +1986,6 @@
               Expression problem = inferrer.helper.buildProblem(
                   messageNullableSpreadError, receiver.fileOffset, 1,
                   context: inferrer.getWhyNotPromotedContext(
-                      receiver,
                       inferrer.flowAnalysis?.whyNotPromoted(receiver)(),
                       entry,
                       (type) => !type.isPotentiallyNullable));
@@ -2008,7 +2005,6 @@
                 receiver.fileOffset,
                 1,
                 context: inferrer.getWhyNotPromotedContext(
-                    receiver,
                     inferrer.flowAnalysis?.whyNotPromoted(receiver)(),
                     entry,
                     (type) => !type.isPotentiallyNullable));
@@ -2119,7 +2115,6 @@
             keyError = inferrer.helper.buildProblem(
                 messageNullableSpreadError, receiver.fileOffset, 1,
                 context: inferrer.getWhyNotPromotedContext(
-                    receiver,
                     inferrer.flowAnalysis?.whyNotPromoted(receiver)(),
                     entry,
                     (type) => !type.isPotentiallyNullable));
@@ -2884,8 +2879,9 @@
     }
 
     ExpressionInferenceResult readResult = _computePropertyGet(node.readOffset,
-        readReceiver, receiverType, node.propertyName, const UnknownType(),
-        isThisReceiver: node.receiver is ThisExpression);
+            readReceiver, receiverType, node.propertyName, const UnknownType(),
+            isThisReceiver: node.receiver is ThisExpression)
+        .expressionInferenceResult;
 
     Expression read = readResult.expression;
     DartType readType = readResult.inferredType;
@@ -2941,8 +2937,9 @@
     Expression writeReceiver = createVariableGet(receiverVariable);
 
     ExpressionInferenceResult readResult = _computePropertyGet(node.readOffset,
-        readReceiver, receiverType, node.propertyName, const UnknownType(),
-        isThisReceiver: node.receiver is ThisExpression);
+            readReceiver, receiverType, node.propertyName, const UnknownType(),
+            isThisReceiver: node.receiver is ThisExpression)
+        .expressionInferenceResult;
 
     reportNonNullableInNullAwareWarningIfNeeded(
         readResult.inferredType, "??=", node.readOffset);
@@ -4695,7 +4692,7 @@
   /// [typeContext] is used to create implicit generic tearoff instantiation
   /// if necessary. [isThisReceiver] must be set to `true` if the receiver is a
   /// `this` expression.
-  ExpressionInferenceResult _computePropertyGet(
+  PropertyGetInferenceResult _computePropertyGet(
       int fileOffset,
       Expression receiver,
       DartType receiverType,
@@ -4879,12 +4876,11 @@
           read.fileOffset,
           propertyName.text.length,
           context: inferrer.getWhyNotPromotedContext(
-              receiver,
               inferrer.flowAnalysis?.whyNotPromoted(receiver)(),
               read,
               (type) => !type.isPotentiallyNullable));
     }
-    return readResult;
+    return new PropertyGetInferenceResult(readResult, readTarget.member);
   }
 
   /// Creates a property set operation of [writeTarget] on [receiver] using
@@ -5216,12 +5212,13 @@
     DartType nonNullReceiverType = receiverType.toNonNull();
 
     ExpressionInferenceResult readResult = _computePropertyGet(
-        node.readOffset,
-        readReceiver,
-        nonNullReceiverType,
-        node.propertyName,
-        const UnknownType(),
-        isThisReceiver: node.receiver is ThisExpression);
+            node.readOffset,
+            readReceiver,
+            nonNullReceiverType,
+            node.propertyName,
+            const UnknownType(),
+            isThisReceiver: node.receiver is ThisExpression)
+        .expressionInferenceResult;
     Expression read = readResult.expression;
     DartType readType = readResult.inferredType;
 
@@ -5752,8 +5749,9 @@
     DartType nonNullReceiverType = receiverType.toNonNull();
 
     ExpressionInferenceResult readResult = _computePropertyGet(node.readOffset,
-        readReceiver, nonNullReceiverType, node.name, typeContext,
-        isThisReceiver: node.receiver is ThisExpression);
+            readReceiver, nonNullReceiverType, node.name, typeContext,
+            isThisReceiver: node.receiver is ThisExpression)
+        .expressionInferenceResult;
     Expression read = readResult.expression;
     DartType readType = readResult.inferredType;
     inferrer.flowAnalysis.ifNullExpression_rightBegin(read, readType);
@@ -5845,11 +5843,13 @@
     DartType receiverType = result.nullAwareActionType;
 
     node.receiver = receiver..parent = node;
-    ExpressionInferenceResult readResult = _computePropertyGet(
+    PropertyGetInferenceResult propertyGetInferenceResult = _computePropertyGet(
         node.fileOffset, receiver, receiverType, node.name, typeContext,
         isThisReceiver: node.receiver is ThisExpression);
-    inferrer.flowAnalysis.propertyGet(
-        node, node.receiver, node.name.text, readResult.inferredType);
+    ExpressionInferenceResult readResult =
+        propertyGetInferenceResult.expressionInferenceResult;
+    inferrer.flowAnalysis.propertyGet(node, node.receiver, node.name.text,
+        propertyGetInferenceResult.member, readResult.inferredType);
     ExpressionInferenceResult expressionInferenceResult =
         inferrer.createNullAwareExpressionInferenceResult(
             readResult.inferredType, readResult.expression, nullAwareGuards);
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
index 15606b8..463c05b 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
@@ -290,14 +290,13 @@
   /// promoted, to be used when reporting an error for a larger expression
   /// containing [receiver].  [node] is the containing tree node.
   List<LocatedMessage> getWhyNotPromotedContext(
-      Expression receiver,
       Map<DartType, NonPromotionReason> whyNotPromoted,
       TreeNode node,
       bool Function(DartType) typeFilter) {
     List<LocatedMessage> context;
     if (whyNotPromoted != null && whyNotPromoted.isNotEmpty) {
       _WhyNotPromotedVisitor whyNotPromotedVisitor =
-          new _WhyNotPromotedVisitor(this, receiver);
+          new _WhyNotPromotedVisitor(this);
       for (core.MapEntry<DartType, NonPromotionReason> entry
           in whyNotPromoted.entries) {
         if (!typeFilter(entry.key)) continue;
@@ -615,7 +614,6 @@
                 nullabilityErrorTemplate.withArguments(expressionType,
                     declaredContextType ?? contextType, isNonNullableByDefault),
                 context: getWhyNotPromotedContext(
-                    expression,
                     flowAnalysis?.whyNotPromoted(expression)(),
                     expression,
                     (type) => typeSchemaEnvironment.isSubtypeOf(type,
@@ -2826,7 +2824,6 @@
         //     void Function() get call => () {};
         //   }
         List<LocatedMessage> context = getWhyNotPromotedContext(
-            receiver,
             flowAnalysis?.whyNotPromoted(receiver)(),
             staticInvocation,
             (type) => !type.isPotentiallyNullable);
@@ -2858,7 +2855,6 @@
       Expression replacement = result.applyResult(staticInvocation);
       if (!isTopLevel && target.isNullable) {
         List<LocatedMessage> context = getWhyNotPromotedContext(
-            receiver,
             flowAnalysis?.whyNotPromoted(receiver)(),
             staticInvocation,
             (type) => !type.isPotentiallyNullable);
@@ -2972,7 +2968,6 @@
     Expression replacement = result.applyResult(expression);
     if (!isTopLevel && target.isNullableCallFunction) {
       List<LocatedMessage> context = getWhyNotPromotedContext(
-          receiver,
           flowAnalysis?.whyNotPromoted(receiver)(),
           expression,
           (type) => !type.isPotentiallyNullable);
@@ -3147,7 +3142,6 @@
     replacement = result.applyResult(replacement);
     if (!isTopLevel && target.isNullable) {
       List<LocatedMessage> context = getWhyNotPromotedContext(
-          receiver,
           flowAnalysis?.whyNotPromoted(receiver)(),
           expression,
           (type) => !type.isPotentiallyNullable);
@@ -3308,7 +3302,6 @@
       //     void Function() get foo => () {};
       //   }
       List<LocatedMessage> context = getWhyNotPromotedContext(
-          receiver,
           flowAnalysis?.whyNotPromoted(receiver)(),
           invocationResult.expression,
           (type) => !type.isPotentiallyNullable);
@@ -3465,8 +3458,8 @@
           new PropertyGet(originalReceiver, originalName, originalTarget)
             ..fileOffset = fileOffset;
     }
-    flowAnalysis.propertyGet(
-        originalPropertyGet, originalReceiver, originalName.text, calleeType);
+    flowAnalysis.propertyGet(originalPropertyGet, originalReceiver,
+        originalName.text, originalTarget, calleeType);
     Expression propertyGet = originalPropertyGet;
     if (receiver is! ThisExpression &&
         calleeType is! DynamicType &&
@@ -3518,11 +3511,8 @@
       //   }
       // TODO(paulberry): would it be better to report NullableMethodCallError
       // in this scenario?
-      List<LocatedMessage> context = getWhyNotPromotedContext(
-          receiver,
-          whyNotPromoted(),
-          invocationResult.expression,
-          (type) => !type.isPotentiallyNullable);
+      List<LocatedMessage> context = getWhyNotPromotedContext(whyNotPromoted(),
+          invocationResult.expression, (type) => !type.isPotentiallyNullable);
       invocationResult = wrapExpressionInferenceResultInProblem(
           invocationResult,
           templateNullableExpressionCallError.withArguments(
@@ -3853,7 +3843,7 @@
       return instantiateTearOff(inferredType, typeContext, expression);
     }
     flowAnalysis.thisOrSuperPropertyGet(
-        expression, expression.name.name, inferredType);
+        expression, expression.name.name, member, inferredType);
     return new ExpressionInferenceResult(inferredType, expression);
   }
 
@@ -4644,6 +4634,17 @@
   }
 }
 
+/// The result of inference of a property get expression.
+class PropertyGetInferenceResult {
+  /// The main inference result.
+  final ExpressionInferenceResult expressionInferenceResult;
+
+  /// The property that was looked up, or `null` if no property was found.
+  final Member member;
+
+  PropertyGetInferenceResult(this.expressionInferenceResult, this.member);
+}
+
 /// The result of an expression inference.
 class ExpressionInferenceResult {
   /// The inferred type of the expression.
@@ -5134,13 +5135,11 @@
             DartType> {
   final TypeInferrerImpl inferrer;
 
-  final Expression receiver;
-
   Member propertyReference;
 
   DartType propertyType;
 
-  _WhyNotPromotedVisitor(this.inferrer, this.receiver);
+  _WhyNotPromotedVisitor(this.inferrer);
 
   @override
   LocatedMessage visitDemoteViaExplicitWrite(
@@ -5158,26 +5157,16 @@
 
   @override
   LocatedMessage visitPropertyNotPromoted(PropertyNotPromoted reason) {
-    Member member;
-    Expression receiver = this.receiver;
-    if (receiver is InstanceGet) {
-      member = receiver.interfaceTarget;
-    } else if (receiver is SuperPropertyGet) {
-      member = receiver.interfaceTarget;
-    } else if (receiver is StaticInvocation) {
-      member = receiver.target;
-    } else if (receiver is PropertyGet) {
-      member = receiver.interfaceTarget;
-    } else {
-      assert(false, 'Unrecognized receiver: ${receiver.runtimeType}');
-    }
-    if (member != null) {
+    Object member = reason.propertyMember;
+    if (member is Member) {
       propertyReference = member;
       propertyType = reason.staticType;
       return templateFieldNotPromoted
           .withArguments(reason.propertyName, reason.documentationLink)
           .withLocation(member.fileUri, member.fileOffset, noLength);
     } else {
+      assert(member == null,
+          'Unrecognized property member: ${member.runtimeType}');
       return null;
     }
   }
diff --git a/tools/VERSION b/tools/VERSION
index 207f847..9bcc89c 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 207
+PRERELEASE 208
 PRERELEASE_PATCH 0
\ No newline at end of file