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],