Disable implicit casts but keep dynamic casts when NNBD enabled
Change-Id: Ia0a3271c9137bc8c7fa4cdbd469cf93d9453b69c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103405
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
index 3c98239..53e8356 100644
--- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart
+++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart
@@ -1606,6 +1606,7 @@
typeSystem.isAssignableTo(staticType, _typeProvider.boolType)) {
// If the static type is not assignable, then we will have already
// reported this error.
+ // TODO(mfairhurst) get the FeatureSet to suppress this for nnbd too.
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.CONST_EVAL_THROWS_EXCEPTION, condition);
}
diff --git a/pkg/analyzer/lib/src/error/inheritance_override.dart b/pkg/analyzer/lib/src/error/inheritance_override.dart
index d120057..9602922 100644
--- a/pkg/analyzer/lib/src/error/inheritance_override.dart
+++ b/pkg/analyzer/lib/src/error/inheritance_override.dart
@@ -2,12 +2,14 @@
// 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 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/constant/value.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager2.dart';
@@ -37,6 +39,7 @@
typeProvider: _typeProvider,
inheritance: _inheritance,
reporter: _reporter,
+ featureSet: unit.featureSet,
library: library,
classNameNode: declaration.name,
implementsClause: declaration.implementsClause,
@@ -50,6 +53,7 @@
typeProvider: _typeProvider,
inheritance: _inheritance,
reporter: _reporter,
+ featureSet: unit.featureSet,
library: library,
classNameNode: declaration.name,
implementsClause: declaration.implementsClause,
@@ -62,6 +66,7 @@
typeProvider: _typeProvider,
inheritance: _inheritance,
reporter: _reporter,
+ featureSet: unit.featureSet,
library: library,
classNameNode: declaration.name,
implementsClause: declaration.implementsClause,
@@ -85,6 +90,7 @@
final InheritanceManager2 inheritance;
final ErrorReporter reporter;
+ final FeatureSet featureSet;
final LibraryElement library;
final Uri libraryUri;
final ClassElementImpl classElement;
@@ -108,6 +114,7 @@
this.typeProvider,
this.inheritance,
this.reporter,
+ this.featureSet,
this.library,
this.classNameNode,
this.implementsClause,
@@ -382,7 +389,8 @@
if (setter != null && setter.parameters.length == 1) {
var getterType = getter.returnType;
var setterType = setter.parameters[0].type;
- if (!typeSystem.isAssignableTo(getterType, setterType)) {
+ if (!typeSystem.isAssignableTo(getterType, setterType,
+ featureSet: featureSet)) {
var getterElement = getter.element;
var setterElement = setter.element;
diff --git a/pkg/analyzer/lib/src/error/literal_element_verifier.dart b/pkg/analyzer/lib/src/error/literal_element_verifier.dart
index 89dcdce..ba32987 100644
--- a/pkg/analyzer/lib/src/error/literal_element_verifier.dart
+++ b/pkg/analyzer/lib/src/error/literal_element_verifier.dart
@@ -2,6 +2,7 @@
// 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 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/type.dart';
@@ -15,6 +16,7 @@
final TypeProvider typeProvider;
final TypeSystem typeSystem;
final ErrorReporter errorReporter;
+ final FeatureSet featureSet;
final bool Function(Expression) checkForUseOfVoidResult;
final bool forList;
@@ -36,6 +38,7 @@
this.forMap = false,
this.mapKeyType,
this.mapValueType,
+ this.featureSet,
});
void verify(CollectionElement element) {
@@ -45,7 +48,7 @@
/// Check that the given [type] is assignable to the [elementType], otherwise
/// report the list or set error on the [errorNode].
void _checkAssignableToElementType(DartType type, AstNode errorNode) {
- if (!typeSystem.isAssignableTo(type, elementType)) {
+ if (!typeSystem.isAssignableTo(type, elementType, featureSet: featureSet)) {
var errorCode = forList
? StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
: StaticWarningCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE;
@@ -106,7 +109,8 @@
}
var keyType = entry.key.staticType;
- if (!typeSystem.isAssignableTo(keyType, mapKeyType)) {
+ if (!typeSystem.isAssignableTo(keyType, mapKeyType,
+ featureSet: featureSet)) {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.MAP_KEY_TYPE_NOT_ASSIGNABLE,
entry.key,
@@ -115,7 +119,8 @@
}
var valueType = entry.value.staticType;
- if (!typeSystem.isAssignableTo(valueType, mapValueType)) {
+ if (!typeSystem.isAssignableTo(valueType, mapValueType,
+ featureSet: featureSet)) {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE,
entry.value,
@@ -157,7 +162,8 @@
}
var iterableElementType = iterableType.typeArguments[0];
- if (!typeSystem.isAssignableTo(iterableElementType, elementType)) {
+ if (!typeSystem.isAssignableTo(iterableElementType, elementType,
+ featureSet: featureSet)) {
var errorCode = forList
? StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE
: StaticWarningCode.SET_ELEMENT_TYPE_NOT_ASSIGNABLE;
@@ -200,7 +206,8 @@
}
var keyType = mapType.typeArguments[0];
- if (!typeSystem.isAssignableTo(keyType, mapKeyType)) {
+ if (!typeSystem.isAssignableTo(keyType, mapKeyType,
+ featureSet: featureSet)) {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.MAP_KEY_TYPE_NOT_ASSIGNABLE,
expression,
@@ -209,7 +216,8 @@
}
var valueType = mapType.typeArguments[1];
- if (!typeSystem.isAssignableTo(valueType, mapValueType)) {
+ if (!typeSystem.isAssignableTo(valueType, mapValueType,
+ featureSet: featureSet)) {
errorReporter.reportTypeErrorForNode(
StaticWarningCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE,
expression,
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 1ea57d9..84d247d 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -291,7 +291,8 @@
/// fixed.
final bool disableConflictingGenericsCheck;
- bool _isNonNullable = false;
+ /// The features enabled in the unit currently being checked for errors.
+ FeatureSet _featureSet;
/**
* Initialize a newly created error verifier.
@@ -343,6 +344,9 @@
_enclosingClass = classElement;
}
+ bool get _isNonNullable =>
+ _featureSet?.isEnabled(Feature.non_nullable) ?? false;
+
@override
void visitAnnotation(Annotation node) {
_checkForInvalidAnnotationFromDeferredLibrary(node);
@@ -558,11 +562,11 @@
@override
void visitCompilationUnit(CompilationUnit node) {
- _isNonNullable = node.featureSet.isEnabled(Feature.non_nullable);
+ _featureSet = node.featureSet;
_checkDuplicateUnitMembers(node);
_checkForDeferredPrefixCollisions(node);
super.visitCompilationUnit(node);
- _isNonNullable = false;
+ _featureSet = null;
}
@override
@@ -2103,8 +2107,8 @@
FunctionType constructorType =
resolutionMap.elementDeclaredByConstructorDeclaration(declaration).type;
DartType constructorReturnType = constructorType.returnType;
- if (!_typeSystem.isAssignableTo(
- redirectedReturnType, constructorReturnType)) {
+ if (!_typeSystem.isAssignableTo(redirectedReturnType, constructorReturnType,
+ featureSet: _featureSet)) {
_errorReporter.reportErrorForNode(
StaticWarningCode.REDIRECT_TO_INVALID_RETURN_TYPE,
redirectedConstructor,
@@ -2323,7 +2327,8 @@
if (expressionType == null) {
return;
}
- if (_typeSystem.isAssignableTo(expressionType, type)) {
+ if (_typeSystem.isAssignableTo(expressionType, type,
+ featureSet: _featureSet)) {
return;
}
_errorReporter.reportErrorForNode(errorCode, expression, arguments);
@@ -2342,7 +2347,8 @@
DartType actualStaticType,
DartType expectedStaticType,
ErrorCode errorCode) {
- if (!_typeSystem.isAssignableTo(actualStaticType, expectedStaticType)) {
+ if (!_typeSystem.isAssignableTo(actualStaticType, expectedStaticType,
+ featureSet: _featureSet)) {
_errorReporter.reportTypeErrorForNode(
errorCode, expression, [actualStaticType, expectedStaticType]);
return false;
@@ -3049,7 +3055,8 @@
StaticTypeWarningCode.FOR_IN_OF_INVALID_TYPE,
node.iterable,
[iterableType, loopTypeName]);
- } else if (!_typeSystem.isAssignableTo(bestIterableType, variableType)) {
+ } else if (!_typeSystem.isAssignableTo(bestIterableType, variableType,
+ featureSet: _featureSet)) {
_errorReporter.reportTypeErrorForNode(
StaticTypeWarningCode.FOR_IN_OF_INVALID_ELEMENT_TYPE,
node.iterable,
@@ -3223,7 +3230,8 @@
if (staticType == null) {
return;
}
- if (_typeSystem.isAssignableTo(staticType, fieldType)) {
+ if (_typeSystem.isAssignableTo(staticType, fieldType,
+ featureSet: _featureSet)) {
return;
}
// report problem
@@ -3847,7 +3855,8 @@
}
DartType leftType = getStaticType(lhs);
DartType rightType = getStaticType(assignment);
- if (!_typeSystem.isAssignableTo(rightType, leftType)) {
+ if (!_typeSystem.isAssignableTo(rightType, leftType,
+ featureSet: _featureSet)) {
_errorReporter.reportTypeErrorForNode(
StaticTypeWarningCode.INVALID_ASSIGNMENT, rhs, [rightType, leftType]);
}
@@ -3960,6 +3969,7 @@
_checkForUseOfVoidResult,
forList: true,
elementType: listElementType,
+ featureSet: _featureSet,
);
for (CollectionElement element in literal.elements) {
verifier.verify(element);
@@ -3995,6 +4005,7 @@
forMap: true,
mapKeyType: keyType,
mapValueType: valueType,
+ featureSet: _featureSet,
);
for (CollectionElement element in literal.elements) {
verifier.verify(element);
@@ -4069,7 +4080,8 @@
// (if the getter is null, it is dynamic which is assignable to everything).
if (setterType != null &&
getterType != null &&
- !_typeSystem.isAssignableTo(getterType, setterType)) {
+ !_typeSystem.isAssignableTo(getterType, setterType,
+ featureSet: _featureSet)) {
_errorReporter.reportTypeErrorForNode(
StaticWarningCode.MISMATCHED_GETTER_AND_SETTER_TYPES,
accessorDeclaration,
@@ -4539,7 +4551,8 @@
if (!_checkForNullableDereference(condition) &&
!_checkForUseOfVoidResult(condition) &&
conditionType != null &&
- !_typeSystem.isAssignableTo(conditionType, _boolType)) {
+ !_typeSystem.isAssignableTo(conditionType, _boolType,
+ featureSet: _featureSet)) {
_errorReporter.reportErrorForNode(
StaticTypeWarningCode.NON_BOOL_CONDITION, condition);
}
@@ -4555,7 +4568,8 @@
Expression expression = assertion.condition;
DartType type = getStaticType(expression);
if (type is InterfaceType) {
- if (!_typeSystem.isAssignableTo(type, _boolType)) {
+ if (!_typeSystem.isAssignableTo(type, _boolType,
+ featureSet: _featureSet)) {
_errorReporter.reportErrorForNode(
StaticTypeWarningCode.NON_BOOL_EXPRESSION, expression);
}
@@ -4573,7 +4587,8 @@
void _checkForNonBoolNegationExpression(Expression expression) {
DartType conditionType = getStaticType(expression);
if (conditionType != null &&
- !_typeSystem.isAssignableTo(conditionType, _boolType)) {
+ !_typeSystem.isAssignableTo(conditionType, _boolType,
+ featureSet: _featureSet)) {
_errorReporter.reportErrorForNode(
StaticTypeWarningCode.NON_BOOL_NEGATION_EXPRESSION, expression);
}
@@ -5162,7 +5177,8 @@
var checkWithType = (!_inAsync)
? fromType
: _typeProvider.futureType.instantiate(<DartType>[fromType]);
- if (_typeSystem.isAssignableTo(checkWithType, expectedType)) {
+ if (_typeSystem.isAssignableTo(checkWithType, expectedType,
+ featureSet: _featureSet)) {
return;
}
}
@@ -5200,6 +5216,7 @@
_checkForUseOfVoidResult,
forSet: true,
elementType: setElementType,
+ featureSet: _featureSet,
);
for (CollectionElement element in literal.elements) {
verifier.verify(element);
@@ -5266,7 +5283,8 @@
DartType caseType = getStaticType(caseExpression);
// check types
- if (!_typeSystem.isAssignableTo(expressionType, caseType)) {
+ if (!_typeSystem.isAssignableTo(expressionType, caseType,
+ featureSet: _featureSet)) {
_errorReporter.reportErrorForNode(
StaticWarningCode.SWITCH_EXPRESSION_NOT_ASSIGNABLE,
expression,
@@ -5592,7 +5610,8 @@
[parameter.identifier.name]);
} else if (declaredType != null &&
fieldType != null &&
- !_typeSystem.isAssignableTo(declaredType, fieldType)) {
+ !_typeSystem.isAssignableTo(declaredType, fieldType,
+ featureSet: _featureSet)) {
_errorReporter.reportTypeErrorForNode(
StaticWarningCode.FIELD_INITIALIZING_FORMAL_NOT_ASSIGNABLE,
parameter,
@@ -5741,7 +5760,8 @@
} else {
requiredReturnType = _typeProvider.iterableDynamicType;
}
- if (!_typeSystem.isAssignableTo(impliedReturnType, requiredReturnType)) {
+ if (!_typeSystem.isAssignableTo(impliedReturnType, requiredReturnType,
+ featureSet: _featureSet)) {
_errorReporter.reportTypeErrorForNode(
StaticTypeWarningCode.YIELD_OF_INVALID_TYPE,
yieldExpression,
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index dc2e4ce..5749304 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -602,8 +602,10 @@
var context = InferenceContext.getContext(
(node as IntegerLiteralImpl).immediatelyNegated ? node.parent : node);
if (context == null ||
- _typeSystem.isAssignableTo(_typeProvider.intType, context) ||
- !_typeSystem.isAssignableTo(_typeProvider.doubleType, context)) {
+ _typeSystem.isAssignableTo(_typeProvider.intType, context,
+ featureSet: _featureSet) ||
+ !_typeSystem.isAssignableTo(_typeProvider.doubleType, context,
+ featureSet: _featureSet)) {
_recordStaticType(node, _nonNullable(_typeProvider.intType));
} else {
_recordStaticType(node, _nonNullable(_typeProvider.doubleType));
@@ -1165,7 +1167,8 @@
void _checkForInvalidAssignmentIncDec(
AstNode node, Expression operand, DartType type) {
var operandWriteType = _getStaticType(operand);
- if (!_typeSystem.isAssignableTo(type, operandWriteType)) {
+ if (!_typeSystem.isAssignableTo(type, operandWriteType,
+ featureSet: _featureSet)) {
_resolver.errorReporter.reportTypeErrorForNode(
StaticTypeWarningCode.INVALID_ASSIGNMENT,
node,
diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart
index 4ef1a61..6b28896 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -75,7 +75,8 @@
new HashSet<TypeComparison>();
/**
- * True if implicit casts should be allowed, otherwise false.
+ * False if implicit casts should always be disallowed, otherwise the
+ * [FeatureSet] will be used.
*
* This affects the behavior of [isAssignableTo].
*/
@@ -448,7 +449,8 @@
}
@override
- bool isAssignableTo(DartType fromType, DartType toType) {
+ bool isAssignableTo(DartType fromType, DartType toType,
+ {FeatureSet featureSet}) {
// An actual subtype
if (isSubtypeOf(fromType, toType)) {
return true;
@@ -457,15 +459,23 @@
// A call method tearoff
if (fromType is InterfaceType && acceptsFunctionType(toType)) {
var callMethodType = getCallMethodType(fromType);
- if (callMethodType != null && isAssignableTo(callMethodType, toType)) {
+ if (callMethodType != null &&
+ isAssignableTo(callMethodType, toType, featureSet: featureSet)) {
return true;
}
}
+ // First make sure --no-implicit-casts disables all downcasts, including
+ // dynamic casts.
if (!implicitCasts) {
return false;
}
+ // Now handle NNBD default behavior, where we disable non-dynamic downcasts.
+ if (featureSet != null && featureSet.isEnabled(Feature.non_nullable)) {
+ return fromType.isDynamic;
+ }
+
// Don't allow implicit downcasts between function types
// and call method objects, as these will almost always fail.
if (fromType is FunctionType && getCallMethodType(toType) != null) {
@@ -484,8 +494,7 @@
return false;
}
- // If the subtype relation goes the other way, allow the implicit
- // downcast.
+ // If the subtype relation goes the other way, allow the implicit downcast.
if (isSubtypeOf(toType, fromType)) {
// TODO(leafp,jmesserly): we emit warnings/hints for these in
// src/task/strong/checker.dart, which is a bit inconsistent. That
@@ -1978,9 +1987,11 @@
/**
* Return `true` if the [leftType] is assignable to the [rightType] (that is,
- * if leftType <==> rightType).
+ * if leftType <==> rightType). Accepts a [FeatureSet] to correctly handle
+ * NNBD implicit downcasts.
*/
- bool isAssignableTo(DartType leftType, DartType rightType);
+ bool isAssignableTo(DartType leftType, DartType rightType,
+ {FeatureSet featureSet});
/**
* Return `true` if the [leftType] is more specific than the [rightType]
diff --git a/pkg/analyzer/lib/src/task/strong/checker.dart b/pkg/analyzer/lib/src/task/strong/checker.dart
index e451ec6..2ca6313 100644
--- a/pkg/analyzer/lib/src/task/strong/checker.dart
+++ b/pkg/analyzer/lib/src/task/strong/checker.dart
@@ -1118,7 +1118,7 @@
}
// Down cast or legal sideways cast, coercion needed.
- if (rules.isAssignableTo(from, to)) {
+ if (rules.isAssignableTo(from, to, featureSet: _featureSet)) {
return true;
}
diff --git a/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart b/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart
index 28b285e..06192b0 100644
--- a/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart
@@ -2,7 +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.
+import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/error/codes.dart';
+import 'package:analyzer/src/generated/engine.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../dart/resolution/driver_resolution.dart';
@@ -10,6 +12,7 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ArgumentTypeNotAssignableTest);
+ defineReflectiveTests(ArgumentTypeNotAssignableTest_NNBD);
});
}
@@ -41,3 +44,45 @@
]);
}
}
+
+@reflectiveTest
+class ArgumentTypeNotAssignableTest_NNBD extends ArgumentTypeNotAssignableTest {
+ @override
+ AnalysisOptionsImpl get analysisOptions =>
+ AnalysisOptionsImpl()..enabledExperiments = [EnableString.non_nullable];
+
+ test_downcast() async {
+ await assertErrorsInCode(r'''
+m() {
+ num y = 1;
+ n(y);
+}
+n(int x) {}
+''', [
+ error(StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 23, 1),
+ ]);
+ }
+
+ @failingTest
+ test_downcast_nullableNonNullable() async {
+ await assertErrorsInCode(r'''
+m() {
+ int? y;
+ n(y);
+}
+n(int x) {}
+''', [
+ error(StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 24, 1),
+ ]);
+ }
+
+ test_dynamicCast() async {
+ await assertNoErrorsInCode(r'''
+m() {
+ dynamic i;
+ n(i);
+}
+n(int i) {}
+''');
+ }
+}
diff --git a/tests/language_2/nnbd/static_errors/implicit_downcasts.dart b/tests/language_2/nnbd/static_errors/implicit_downcasts.dart
new file mode 100644
index 0000000..6d8b7b4
--- /dev/null
+++ b/tests/language_2/nnbd/static_errors/implicit_downcasts.dart
@@ -0,0 +1,19 @@
+// 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
+
+// Test that it is an error to do what was once allowed as an "implicit
+// downcast."
+main() {
+ num asNum = 1;
+ Object asObject = 1;
+ dynamic asDynamic = 1;
+ int asInt1 = asNum; //# 01: compile-time error
+ double asDouble1 = asNum; //# 02: compile-time error
+ int asInt2 = asObject; //# 03: compile-time error
+ double asDouble2 = asObject; //# 04: compile-time error
+ String asString1 = asObject; //# 05: compile-time error
+ int asInt3 = asDynamic; //# 06: ok
+}