Implement more "why not promoted" functionality in the front end.

This CL ensures that we produce appropriate "why not promoted" context
messages in all the scenarios where the CFE reports the error code
`NullableExpressionCallError`.

Analyzer support for these scenarios will be in a follow-up CL.

Bug: https://github.com/dart-lang/sdk/issues/44898
Change-Id: Id1407b28a97fc917189e885f995a4efc0bf0c250
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/185920
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_expression_call_error.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_expression_call_error.dart
new file mode 100644
index 0000000..c603def
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_expression_call_error.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2020, 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.
+
+// This test contains a test case for each condition that can lead to the front
+// end's `NullableExpressionCallError`, for which we wish to report "why not
+// promoted" context information.
+
+class C1 {
+  C2? bad;
+}
+
+class C2 {
+  void call() {}
+}
+
+instance_method_invocation(C1 c) {
+  if (c.bad == null) return;
+  c.bad
+      /*cfe.invoke: notPromoted(propertyNotPromoted(member:C1.bad))*/
+      ();
+}
+
+class C3 {
+  C4? ok;
+  C5? bad;
+}
+
+class C4 {}
+
+class C5 {}
+
+extension on C4? {
+  void call() {}
+}
+
+extension on C5 {
+  void call() {}
+}
+
+extension_invocation_method(C3 c) {
+  if (c.ok == null) return;
+  c.ok();
+  if (c.bad == null) return;
+  c.bad
+      /*cfe.invoke: notPromoted(propertyNotPromoted(member:C3.bad))*/
+      ();
+}
+
+class C6 {
+  C7? bad;
+}
+
+class C7 {
+  void Function() get call => () {};
+}
+
+instance_getter_invocation(C6 c) {
+  if (c.bad == null) return;
+  c.bad
+      /*cfe.invoke: notPromoted(propertyNotPromoted(member:C6.bad))*/
+      ();
+}
+
+class C8 {
+  C9? ok;
+  C10? bad;
+}
+
+class C9 {}
+
+class C10 {}
+
+extension on C9? {
+  void Function() get call => () {};
+}
+
+extension on C10 {
+  void Function() get call => () {};
+}
+
+extension_invocation_getter(C8 c) {
+  if (c.ok == null) return;
+  c.ok();
+  if (c.bad == null) return;
+  c.bad
+      /*cfe.invoke: notPromoted(propertyNotPromoted(member:C8.bad))*/
+      ();
+}
+
+class C11 {
+  void Function()? bad;
+}
+
+function_invocation(C11 c) {
+  if (c.bad == null) return;
+  c.bad
+      /*cfe.invoke: notPromoted(propertyNotPromoted(member:C11.bad))*/
+      ();
+}
+
+class C12 {
+  C13? bad;
+}
+
+class C13 {
+  void Function() foo;
+  C13(this.foo);
+}
+
+instance_field_invocation(C12 c) {
+  if (c.bad == null) return;
+  c.bad
+      .
+      /*analyzer.notPromoted(propertyNotPromoted(member:C12.bad))*/
+      foo
+      /*cfe.invoke: notPromoted(propertyNotPromoted(member:C12.bad))*/
+      ();
+}
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_error_scenarios.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_method_call_error.dart
similarity index 91%
rename from pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_error_scenarios.dart
rename to pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_method_call_error.dart
index 5797fa4..40cc053 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_error_scenarios.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_method_call_error.dart
@@ -2,9 +2,9 @@
 // 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.
 
-// This test contains a test case for each kind of error that can arise from an
-// expression being nullable, for which we wish to report "why not promoted"
-// errors.
+// This test contains a test case for each condition that can lead to the front
+// end's `NullableMethodCallError`, for which we wish to report "why not
+// promoted" context information.
 
 class C {
   int? i;
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 13d0970..2f2df5f 100644
--- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -4736,7 +4736,8 @@
               propertyName.text, receiverType, inferrer.isNonNullableByDefault),
           read.fileOffset,
           propertyName.text.length,
-          context: inferrer.getWhyNotPromotedContext(receiver, read));
+          context: inferrer.getWhyNotPromotedContext(
+              receiver, inferrer.flowAnalysis?.whyNotPromoted(receiver), read));
     }
     return readResult;
   }
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 3acf8bb..0c457c2 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
@@ -305,10 +305,8 @@
   /// Computes a list of context messages explaining why [receiver] was not
   /// promoted, to be used when reporting an error for a larger expression
   /// containing [receiver].  [expression] is the containing expression.
-  List<LocatedMessage> getWhyNotPromotedContext(
-      Expression receiver, Expression expression) {
-    Map<DartType, NonPromotionReason> whyNotPromoted =
-        flowAnalysis?.whyNotPromoted(receiver);
+  List<LocatedMessage> getWhyNotPromotedContext(Expression receiver,
+      Map<DartType, NonPromotionReason> whyNotPromoted, Expression expression) {
     List<LocatedMessage> context;
     if (whyNotPromoted != null && whyNotPromoted.isNotEmpty) {
       _WhyNotPromotedVisitor whyNotPromotedVisitor =
@@ -2755,12 +2753,22 @@
           implicitInvocationPropertyName: name);
 
       if (!isTopLevel && target.isNullable) {
+        // Handles cases like:
+        //   C? c;
+        //   c();
+        // where there is an extension on C defined as:
+        //   extension on C {
+        //     void Function() get call => () {};
+        //   }
+        List<LocatedMessage> context = getWhyNotPromotedContext(
+            receiver, flowAnalysis?.whyNotPromoted(receiver), staticInvocation);
         result = wrapExpressionInferenceResultInProblem(
             result,
             templateNullableExpressionCallError.withArguments(
                 receiverType, isNonNullableByDefault),
             fileOffset,
-            noLength);
+            noLength,
+            context: context);
       }
 
       return result;
@@ -2780,13 +2788,23 @@
 
       Expression replacement = result.applyResult(staticInvocation);
       if (!isTopLevel && target.isNullable) {
+        List<LocatedMessage> context = getWhyNotPromotedContext(
+            receiver, flowAnalysis?.whyNotPromoted(receiver), staticInvocation);
         if (isImplicitCall) {
+          // Handles cases like:
+          //   int? i;
+          //   i();
+          // where there is an extension:
+          //   extension on int {
+          //     void call() {}
+          //   }
           replacement = helper.wrapInProblem(
               replacement,
               templateNullableExpressionCallError.withArguments(
                   receiverType, isNonNullableByDefault),
               fileOffset,
-              noLength);
+              noLength,
+              context: context);
         } else {
           // Handles cases like:
           //   int? i;
@@ -2801,7 +2819,7 @@
                   name.text, receiverType, isNonNullableByDefault),
               fileOffset,
               name.text.length,
-              context: getWhyNotPromotedContext(receiver, staticInvocation));
+              context: context);
         }
       }
       return createNullAwareExpressionInferenceResult(
@@ -2865,13 +2883,19 @@
     }
     Expression replacement = result.applyResult(expression);
     if (!isTopLevel && target.isNullableCallFunction) {
+      List<LocatedMessage> context = getWhyNotPromotedContext(
+          receiver, flowAnalysis?.whyNotPromoted(receiver), expression);
       if (isImplicitCall) {
+        // Handles cases like:
+        //   void Function()? f;
+        //   f();
         replacement = helper.wrapInProblem(
             replacement,
             templateNullableExpressionCallError.withArguments(
                 receiverType, isNonNullableByDefault),
             fileOffset,
-            noLength);
+            noLength,
+            context: context);
       } else {
         // Handles cases like:
         //   void Function()? f;
@@ -2882,7 +2906,7 @@
                 callName.text, receiverType, isNonNullableByDefault),
             fileOffset,
             callName.text.length,
-            context: getWhyNotPromotedContext(receiver, expression));
+            context: context);
       }
     }
     // TODO(johnniwinther): Check that type arguments against the bounds.
@@ -3031,13 +3055,23 @@
 
     replacement = result.applyResult(replacement);
     if (!isTopLevel && target.isNullable) {
+      List<LocatedMessage> context = getWhyNotPromotedContext(
+          receiver, flowAnalysis?.whyNotPromoted(receiver), expression);
       if (isImplicitCall) {
+        // Handles cases like:
+        //   C? c;
+        //   c();
+        // Where C is defined as:
+        //   class C {
+        //     void call();
+        //   }
         replacement = helper.wrapInProblem(
             replacement,
             templateNullableExpressionCallError.withArguments(
                 receiverType, isNonNullableByDefault),
             fileOffset,
-            noLength);
+            noLength,
+            context: context);
       } else {
         // Handles cases like:
         //   int? i;
@@ -3048,7 +3082,7 @@
                 methodName.text, receiverType, isNonNullableByDefault),
             fileOffset,
             methodName.text.length,
-            context: getWhyNotPromotedContext(receiver, expression));
+            context: context);
       }
     }
 
@@ -3172,12 +3206,22 @@
     }
 
     if (!isTopLevel && target.isNullable) {
+      // Handles cases like:
+      //   C? c;
+      //   c.foo();
+      // Where C is defined as:
+      //   class C {
+      //     void Function() get foo => () {};
+      //   }
+      List<LocatedMessage> context = getWhyNotPromotedContext(receiver,
+          flowAnalysis?.whyNotPromoted(receiver), invocationResult.expression);
       invocationResult = wrapExpressionInferenceResultInProblem(
           invocationResult,
           templateNullableExpressionCallError.withArguments(
               receiverType, isNonNullableByDefault),
           fileOffset,
-          noLength);
+          noLength,
+          context: context);
     }
 
     if (!library.loader.target.backendTarget.supportsExplicitGetterCalls) {
@@ -3274,6 +3318,14 @@
       receiver = _hoist(receiver, receiverType, hoistedExpressions);
     }
 
+    Map<DartType, NonPromotionReason> whyNotPromotedInfo;
+    if (!isTopLevel && target.isNullable) {
+      // We won't report the error until later (after we have an
+      // invocationResult), but we need to gather "why not promoted" info now,
+      // before we tell flow analysis about the property get.
+      whyNotPromotedInfo = flowAnalysis?.whyNotPromoted(receiver);
+    }
+
     Name originalName = field.name;
     Expression originalReceiver = receiver;
     Member originalTarget = field;
@@ -3297,6 +3349,8 @@
           kind, originalReceiver, originalName,
           resultType: calleeType, interfaceTarget: originalTarget)
         ..fileOffset = fileOffset;
+      flowAnalysis.propertyGet(
+          originalPropertyGet, originalReceiver, originalName.name);
     } else {
       originalPropertyGet =
           new PropertyGet(originalReceiver, originalName, originalTarget)
@@ -3343,12 +3397,25 @@
     }
 
     if (!isTopLevel && target.isNullable) {
+      // Handles cases like:
+      //   C? c;
+      //   c.foo();
+      // Where C is defined as:
+      //   class C {
+      //     void Function() foo;
+      //     C(this.foo);
+      //   }
+      // TODO(paulberry): would it be better to report NullableMethodCallError
+      // in this scenario?
+      List<LocatedMessage> context = getWhyNotPromotedContext(
+          receiver, whyNotPromotedInfo, invocationResult.expression);
       invocationResult = wrapExpressionInferenceResultInProblem(
           invocationResult,
           templateNullableExpressionCallError.withArguments(
               receiverType, isNonNullableByDefault),
           fileOffset,
-          noLength);
+          noLength,
+          context: context);
     }
 
     if (!library.loader.target.backendTarget.supportsExplicitGetterCalls) {