[analyzer] resolve x?.y to return T?

Change-Id: I3ef319621753364b8d70e79b2ab32d7ec9c1e8d8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/92383
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index e115b57..9502121 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -3776,7 +3776,7 @@
     }
     this.inferenceContext = new InferenceContext._(
         typeProvider, typeSystem, strongModeHints, errorReporter);
-    this.typeAnalyzer = new StaticTypeAnalyzer(this);
+    this.typeAnalyzer = new StaticTypeAnalyzer(this, featureSet);
   }
 
   /// Return the element representing the function containing the current node,
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index b110a681..ceca4ee 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -4,6 +4,7 @@
 
 import 'dart:collection';
 
+import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/standard_resolution_map.dart';
 import 'package:analyzer/dart/ast/token.dart';
@@ -22,6 +23,7 @@
 import 'package:analyzer/src/generated/utilities_dart.dart';
 import 'package:analyzer/src/task/strong/checker.dart'
     show getExpressionType, getReadType;
+import 'package:meta/meta.dart';
 
 /**
  * Instances of the class `StaticTypeAnalyzer` perform two type-related tasks. First, they
@@ -69,12 +71,14 @@
    */
   TypePromotionManager _promoteManager;
 
+  bool nonNullableEnabled;
+
   /**
    * Initialize a newly created type analyzer.
    *
    * @param resolver the resolver driving this participant
    */
-  StaticTypeAnalyzer(this._resolver) {
+  StaticTypeAnalyzer(this._resolver, FeatureSet featureSet) {
     _typeProvider = _resolver.typeProvider;
     _typeSystem = _resolver.typeSystem;
     _dynamicType = _typeProvider.dynamicType;
@@ -82,6 +86,7 @@
     AnalysisOptionsImpl analysisOptions =
         _resolver.definingLibrary.context.analysisOptions;
     _strictInference = analysisOptions.strictInference;
+    nonNullableEnabled = featureSet.isEnabled(Feature.non_nullable);
   }
 
   /**
@@ -502,7 +507,8 @@
   @override
   void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
     _inferGenericInvocationExpression(node);
-    DartType staticType = _computeInvokeReturnType(node.staticInvokeType);
+    DartType staticType = _computeInvokeReturnType(node.staticInvokeType,
+        isNullableInvoke: false);
     _recordStaticType(node, staticType);
   }
 
@@ -672,8 +678,9 @@
         _inferMethodInvocationInlineJS(node);
 
     if (!inferredStaticType) {
-      DartType staticStaticType =
-          _computeInvokeReturnType(node.staticInvokeType);
+      DartType staticStaticType = _computeInvokeReturnType(
+          node.staticInvokeType,
+          isNullableInvoke: node.operator?.type == TokenType.QUESTION_PERIOD);
       _recordStaticType(node, staticStaticType);
     }
   }
@@ -777,6 +784,7 @@
     } else if (staticElement is VariableElement) {
       staticType = staticElement.type;
     }
+
     staticType = _inferGenericInstantiationFromContext(node, staticType);
     if (!_inferObjectAccess(node, staticType, prefixedIdentifier)) {
       _recordStaticType(prefixedIdentifier, staticType);
@@ -865,7 +873,13 @@
     } else {
       // TODO(brianwilkerson) Report this internal error.
     }
+
+    if (node.operator.type == TokenType.QUESTION_PERIOD && nonNullableEnabled) {
+      staticType =
+          (staticType as TypeImpl).withNullability(NullabilitySuffix.question);
+    }
     staticType = _inferGenericInstantiationFromContext(node, staticType);
+
     if (!_inferObjectAccess(node, staticType, propertyName)) {
       _recordStaticType(propertyName, staticType);
       _recordStaticType(node, staticType);
@@ -1161,15 +1175,22 @@
    * Compute the return type of the method or function represented by the given
    * type that is being invoked.
    */
-  DartType _computeInvokeReturnType(DartType type) {
+  DartType _computeInvokeReturnType(DartType type,
+      {@required bool isNullableInvoke}) {
+    TypeImpl returnType;
     if (type is InterfaceType) {
       MethodElement callMethod = type.lookUpMethod(
           FunctionElement.CALL_METHOD_NAME, _resolver.definingLibrary);
-      return callMethod?.type?.returnType ?? _dynamicType;
+      returnType = callMethod?.type?.returnType;
     } else if (type is FunctionType) {
-      return type.returnType ?? _dynamicType;
+      returnType = type.returnType;
     }
-    return _dynamicType;
+
+    if (isNullableInvoke && nonNullableEnabled) {
+      returnType = returnType?.withNullability(NullabilitySuffix.question);
+    }
+
+    return returnType ?? _dynamicType;
   }
 
   /**
@@ -1210,13 +1231,14 @@
       //
       FunctionType propertyType = element.type;
       if (propertyType != null) {
-        return _computeInvokeReturnType(propertyType.returnType);
+        return _computeInvokeReturnType(propertyType.returnType,
+            isNullableInvoke: false);
       }
     } else if (element is ExecutableElement) {
-      return _computeInvokeReturnType(element.type);
+      return _computeInvokeReturnType(element.type, isNullableInvoke: false);
     } else if (element is VariableElement) {
       DartType variableType = _promoteManager.getStaticType(element);
-      return _computeInvokeReturnType(variableType);
+      return _computeInvokeReturnType(variableType, isNullableInvoke: false);
     }
     return _dynamicType;
   }
diff --git a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
index 820678a..089d66d 100644
--- a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
@@ -26,6 +26,35 @@
   @override
   bool get typeToStringWithNullability => true;
 
+  test_local_getterNullAwareAccess_interfaceType() async {
+    addTestFile(r'''
+m() {
+  int? x;
+  return x?.isEven;
+}
+''');
+
+    await resolveTestFile();
+    assertNoTestErrors();
+    assertType(findNode.propertyAccess('x?.isEven'), 'bool?');
+  }
+
+  test_local_methodNullAwareCall_interfaceType() async {
+    await addTestFile(r'''
+class C {
+  bool x() => true;
+}
+m() {
+  C? c;
+  return c?.x();
+}
+''');
+
+    await resolveTestFile();
+    assertNoTestErrors();
+    assertType(findNode.methodInvocation('c?.x()'), 'bool?');
+  }
+
   test_local_parameter_interfaceType() async {
     addTestFile('''
 main() {
diff --git a/tests/language_2/nnbd/resolution/question_dot_produces_nullable_type_test.dart b/tests/language_2/nnbd/resolution/question_dot_produces_nullable_type_test.dart
new file mode 100644
index 0000000..bfb9388
--- /dev/null
+++ b/tests/language_2/nnbd/resolution/question_dot_produces_nullable_type_test.dart
@@ -0,0 +1,33 @@
+// 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.
+
+// SharedOptions=--enable-experiment=non-nullable
+
+bool c = false;
+
+// Test that the type produced from expression `x?.y` is nullable.
+void main() {
+  int? x;
+  x?.bitLength + 1; //# 01: compile-time error
+  x?.round(2) + 1; //# 02: compile-time error
+}
+
+// Ensure it works correctly on type parameters
+void f<T extends num>(Generic<T>? generic, Generic<T?> nullableGeneric) {
+  generic?.getter + 1; //# 03: compile-time error
+  generic?.method() + 1; //# 04: compile-time error
+  generic?.nullableGetter + 1; //# 05: compile-time error
+  generic?.nullableMethod() + 1; //# 06: compile-time error
+  nullableGeneric?.getter + 1; //# 07: compile-time error
+  nullableGeneric?.method() + 1; //# 08: compile-time error
+  nullableGeneric?.nullableGetter + 1; //# 09: compile-time error
+  nullableGeneric?.nullableMethod() + 1; //# 10: compile-time error
+}
+
+class Generic<T> {
+  T get getter => throw Exception('unreachable');
+  T method() => throw Exception('unreachable');
+  T? nullableGetter = null;
+  T? nullableMethod() => null;
+}