Analyzer: first pass at reporting unchecked nullable value usage.

Change-Id: I095c7feff821535a17c7cbb0c4bdb9bb04253f16
Reviewed-on: https://dart-review.googlesource.com/c/88752
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index fdaed1b..2a13c74 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -648,6 +648,7 @@
   StaticWarningCode.UNDEFINED_IDENTIFIER_AWAIT,
   StaticWarningCode.UNDEFINED_NAMED_PARAMETER,
   StaticWarningCode.USE_OF_VOID_RESULT,
+  StaticWarningCode.UNCHECKED_USE_OF_NULLABLE_VALUE,
   StrongModeCode.ASSIGNMENT_CAST,
   StrongModeCode.COULD_NOT_INFER,
   StrongModeCode.DOWN_CAST_COMPOSITE,
diff --git a/pkg/analyzer/lib/src/error/codes.dart b/pkg/analyzer/lib/src/error/codes.dart
index bd26015..ddfb545 100644
--- a/pkg/analyzer/lib/src/error/codes.dart
+++ b/pkg/analyzer/lib/src/error/codes.dart
@@ -4597,6 +4597,19 @@
               "defining a new parameter with this name.");
 
   /**
+   * For the purposes of experimenting with potential non-null type semantics.
+   *
+   * Parameters: none
+   */
+  static const StaticWarningCode UNCHECKED_USE_OF_NULLABLE_VALUE =
+      const StaticWarningCode(
+          'UNCHECKED_USE_OF_NULLABLE_VALUE',
+          'The expression is nullable and must be null-checked before it can be'
+          ' used.',
+          correction:
+              'Try casting or check the value is not null before using it.');
+
+  /**
    * It is a static warning to assign void to any non-void type in dart.
    * compile-time error). Report that error specially for a better user
    * experience.
diff --git a/pkg/analyzer/lib/src/generated/element_resolver.dart b/pkg/analyzer/lib/src/generated/element_resolver.dart
index dbb487e..cc16f64 100644
--- a/pkg/analyzer/lib/src/generated/element_resolver.dart
+++ b/pkg/analyzer/lib/src/generated/element_resolver.dart
@@ -9,6 +9,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/error/error.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
 import 'package:analyzer/src/dart/ast/ast.dart'
     show
         ChildEntities,
@@ -109,6 +110,11 @@
    */
   InterfaceType _typeType;
 
+  /**
+   * The enabled experiments
+   */
+  ExperimentStatus _experimentStatus;
+
   /// Whether constant evaluation errors should be reported during resolution.
   final bool reportConstEvaluationErrors;
 
@@ -124,6 +130,9 @@
         _methodInvocationResolver = new MethodInvocationResolver(_resolver) {
     _dynamicType = _resolver.typeProvider.dynamicType;
     _typeType = _resolver.typeProvider.typeType;
+    _experimentStatus = (_resolver.definingLibrary.context.analysisOptions
+            as AnalysisOptionsImpl)
+        .experimentStatus;
   }
 
   /**
@@ -145,14 +154,21 @@
     Expression leftHandSide = node.leftHandSide;
     DartType staticType = _getStaticType(leftHandSide, read: true);
 
-    // For any compound assignments to a void variable, report bad void usage.
+    // For any compound assignments to a void or nullable variable, report it.
     // Example: `y += voidFn()`, not allowed.
-    if (operatorType != TokenType.EQ &&
-        staticType != null &&
-        staticType.isVoid) {
-      _recordUndefinedToken(
-          null, StaticWarningCode.USE_OF_VOID_RESULT, operator, []);
-      return;
+    if (operatorType != TokenType.EQ) {
+      if (staticType != null && staticType.isVoid) {
+        _recordUndefinedToken(
+            null, StaticWarningCode.USE_OF_VOID_RESULT, operator, []);
+        return;
+      }
+      if (_experimentStatus.non_nullable &&
+          staticType != null &&
+          (staticType as TypeImpl).nullability == Nullability.nullable) {
+        _recordUndefinedToken(null,
+            StaticWarningCode.UNCHECKED_USE_OF_NULLABLE_VALUE, operator, []);
+        return;
+      }
     }
 
     if (operatorType != TokenType.AMPERSAND_AMPERSAND_EQ &&
@@ -165,7 +181,7 @@
         MethodElement staticMethod =
             _lookUpMethod(leftHandSide, staticType, methodName);
         node.staticElement = staticMethod;
-        if (_shouldReportMissingMember(staticType, staticMethod)) {
+        if (_shouldReportInvalidMember(staticType, staticMethod)) {
           _recordUndefinedToken(
               staticType.element,
               StaticTypeWarningCode.UNDEFINED_METHOD,
@@ -533,7 +549,7 @@
     DartType staticType = _getStaticType(operand);
     MethodElement staticMethod = _lookUpMethod(operand, staticType, methodName);
     node.staticElement = staticMethod;
-    if (_shouldReportMissingMember(staticType, staticMethod)) {
+    if (_shouldReportInvalidMember(staticType, staticMethod)) {
       if (operand is SuperExpression) {
         _recordUndefinedToken(
             staticType.element,
@@ -646,7 +662,7 @@
       MethodElement staticMethod =
           _lookUpMethod(operand, staticType, methodName);
       node.staticElement = staticMethod;
-      if (_shouldReportMissingMember(staticType, staticMethod)) {
+      if (_shouldReportInvalidMember(staticType, staticMethod)) {
         if (operand is SuperExpression) {
           _recordUndefinedToken(
               staticType.element,
@@ -891,7 +907,7 @@
       String methodName,
       MethodElement staticMethod,
       DartType staticType) {
-    if (_shouldReportMissingMember(staticType, staticMethod)) {
+    if (_shouldReportInvalidMember(staticType, staticMethod)) {
       Token leftBracket = expression.leftBracket;
       Token rightBracket = expression.rightBracket;
       ErrorCode errorCode;
@@ -901,6 +917,11 @@
       } else if (staticType != null && staticType.isVoid) {
         errorCode = StaticWarningCode.USE_OF_VOID_RESULT;
         errorArguments = [];
+      } else if (staticType != null &&
+          _experimentStatus.non_nullable &&
+          (staticType as TypeImpl).nullability == Nullability.nullable) {
+        errorCode = StaticWarningCode.UNCHECKED_USE_OF_NULLABLE_VALUE;
+        errorArguments = [];
       } else {
         errorCode = StaticTypeWarningCode.UNDEFINED_OPERATOR;
       }
@@ -1443,7 +1464,7 @@
       var invokeElement = invokeType?.element;
       node.staticElement = invokeElement;
       node.staticInvokeType = invokeType;
-      if (_shouldReportMissingMember(leftType, invokeElement)) {
+      if (_shouldReportInvalidMember(leftType, invokeElement)) {
         if (isSuper) {
           _recordUndefinedToken(
               leftType.element,
@@ -1608,7 +1629,7 @@
       return;
     }
     propertyName.staticElement = staticElement;
-    if (_shouldReportMissingMember(staticType, staticElement)) {
+    if (_shouldReportInvalidMember(staticType, staticElement)) {
       Element staticOrPropagatedEnclosingElt = staticType.element;
       bool isStaticProperty = _isStatic(staticOrPropagatedEnclosingElt);
       // Special getter cases.
@@ -1641,6 +1662,11 @@
           if (staticType.isVoid) {
             errorCode = StaticWarningCode.USE_OF_VOID_RESULT;
             arguments = [];
+          } else if ((staticType as TypeImpl).nullability ==
+                  Nullability.nullable &&
+              _experimentStatus.non_nullable) {
+            errorCode = StaticWarningCode.UNCHECKED_USE_OF_NULLABLE_VALUE;
+            arguments = [];
           } else {
             errorCode = StaticTypeWarningCode.UNDEFINED_SETTER;
           }
@@ -1656,6 +1682,11 @@
           if (staticType.isVoid) {
             errorCode = StaticWarningCode.USE_OF_VOID_RESULT;
             arguments = [];
+          } else if ((staticType as TypeImpl).nullability ==
+                  Nullability.nullable &&
+              _experimentStatus.non_nullable) {
+            errorCode = StaticWarningCode.UNCHECKED_USE_OF_NULLABLE_VALUE;
+            arguments = [];
           } else {
             errorCode = StaticTypeWarningCode.UNDEFINED_GETTER;
           }
@@ -1731,12 +1762,15 @@
       type?.resolveToBound(_resolver.typeProvider.objectType);
 
   /**
-   * Return `true` if we should report an error as a result of looking up a
-   * [member] in the given [type] and not finding any member.
+   * Return `true` if we should report an error for a [member] lookup that found
+   * no match on the given [type], or accessing a [member] on a nullable type.
    */
-  bool _shouldReportMissingMember(DartType type, Element member) {
-    return member == null &&
+  bool _shouldReportInvalidMember(DartType type, Element member) {
+    bool unchecked = _experimentStatus.non_nullable &&
         type != null &&
+        (type as TypeImpl).nullability == Nullability.nullable;
+    return type != null &&
+        (member == null || unchecked) &&
         !type.isDynamic &&
         !type.isDartCoreNull;
   }
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 5283d92..37ee16d 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -394,10 +394,17 @@
       _checkForAssignability(node.rightOperand, _boolType,
           StaticTypeWarningCode.NON_BOOL_OPERAND, [lexeme]);
       _checkForUseOfVoidResult(node.rightOperand);
+      _checkForNullableDereference(node.rightOperand);
     } else {
       _checkForArgumentTypeNotAssignableForArgument(node.rightOperand);
     }
+
+    if (type != TokenType.EQ_EQ && type != TokenType.BANG_EQ) {
+      _checkForNullableDereference(node.leftOperand);
+    }
+
     _checkForUseOfVoidResult(node.leftOperand);
+
     super.visitBinaryExpression(node);
   }
 
@@ -532,9 +539,6 @@
   @override
   void visitConditionalExpression(ConditionalExpression node) {
     _checkForNonBoolCondition(node.condition);
-    // TODO(mfairhurst) Enable this and get code compliant.
-    //_checkForUseOfVoidResult(node.thenExpression);
-    //_checkForUseOfVoidResult(node.elseExpression);
     super.visitConditionalExpression(node);
   }
 
@@ -775,7 +779,8 @@
   void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
     Expression functionExpression = node.function;
     DartType expressionType = functionExpression.staticType;
-    if (!_checkForUseOfVoidResult(functionExpression) &&
+    if (!_checkForNullableDereference(functionExpression) &&
+        !_checkForUseOfVoidResult(functionExpression) &&
         !_isFunctionType(expressionType)) {
       _errorReporter.reportErrorForNode(
           StaticTypeWarningCode.INVOCATION_OF_NON_FUNCTION_EXPRESSION,
@@ -1025,7 +1030,7 @@
 
       _initializeInitialFieldElementsMap(_enclosingClass.fields);
       _checkForFinalNotInitializedInClass(members);
-//      _checkForBadFunctionUse(node);
+      //      _checkForBadFunctionUse(node);
       super.visitMixinDeclaration(node);
     } finally {
       _initialFieldElementsMap = null;
@@ -1079,6 +1084,7 @@
       _checkForAssignmentToFinal(operand);
     }
     _checkForIntNotAssignable(operand);
+    _checkForNullableDereference(operand);
     _checkForUseOfVoidResult(operand);
     super.visitPrefixExpression(node);
   }
@@ -1208,6 +1214,7 @@
   @override
   void visitThrowExpression(ThrowExpression node) {
     _checkForConstEvalThrowsException(node);
+    _checkForNullableDereference(node.expression);
     _checkForUseOfVoidResult(node.expression);
     super.visitThrowExpression(node);
   }
@@ -1312,6 +1319,9 @@
   void visitYieldStatement(YieldStatement node) {
     if (_inGenerator) {
       _checkForYieldOfInvalidType(node.expression, node.star != null);
+      if (node.star != null) {
+        _checkForNullableDereference(node.expression);
+      }
     } else {
       CompileTimeErrorCode errorCode;
       if (node.star != null) {
@@ -3586,6 +3596,10 @@
       return;
     }
 
+    if (_checkForNullableDereference(node.iterable)) {
+      return;
+    }
+
     if (_checkForUseOfVoidResult(node.iterable)) {
       return;
     }
@@ -3599,9 +3613,6 @@
     SimpleIdentifier variable = node.identifier ?? loopVariable.identifier;
     DartType variableType = getStaticType(variable);
 
-    // TODO(mfairhurst) Check and guard against `for(void x in _)`?
-    //_checkForUseOfVoidResult(variable);
-
     DartType loopType = node.awaitKeyword != null
         ? _typeProvider.streamType
         : _typeProvider.iterableType;
@@ -4423,7 +4434,8 @@
    */
   void _checkForNonBoolCondition(Expression condition) {
     DartType conditionType = getStaticType(condition);
-    if (!_checkForUseOfVoidResult(condition) &&
+    if (!_checkForNullableDereference(condition) &&
+        !_checkForUseOfVoidResult(condition) &&
         conditionType != null &&
         !_typeSystem.isAssignableTo(conditionType, _boolType)) {
       _errorReporter.reportErrorForNode(
@@ -4536,6 +4548,34 @@
   }
 
   /**
+   * Check for illegal derefences of nullables, ie, "unchecked" usages of
+   * nullable values. Note that *any* usage of a null value is an "unchecked"
+   * usage, because proper checks will promote the type to a non-nullable value.
+   *
+   * See [StaticWarningCode.UNCHECKED_USE_OF_NULLABLE_VALUE]
+   */
+  bool _checkForNullableDereference(Expression expression,
+      {bool allowNullableDereference = false}) {
+    if (expression == null ||
+        !_options.experimentStatus.non_nullable ||
+        (expression.staticType as TypeImpl).nullability !=
+            Nullability.nullable) {
+      return false;
+    }
+
+    StaticWarningCode code = StaticWarningCode.UNCHECKED_USE_OF_NULLABLE_VALUE;
+
+    if (expression is MethodInvocation) {
+      SimpleIdentifier methodName = expression.methodName;
+      _errorReporter.reportErrorForNode(code, methodName, []);
+    } else {
+      _errorReporter.reportErrorForNode(code, expression, []);
+    }
+
+    return true;
+  }
+
+  /**
    * Verify that all classes of the given [onClause] are valid.
    *
    * See [CompileTimeErrorCode.MIXIN_SUPER_CLASS_CONSTRAINT_DISALLOWED_CLASS],