Skip to content

Commit 2ef00b0

Browse files
committed
Don't report redundant type errors in strong mode.
Currently, "sideways casts" -- type errors where one type is assigned to an unrelated type -- are reported by both ErrorVerifier and strong mode's Checker. This leads to duplicate errors that the user can see. ErrorVerifier's errors are generally better: they give the user more contextual information and are easier to read. So this CL eliminates Checker's reporting of these errors and only uses ErrorVerifier's. However, in strong mode, type errors like this are fatal: DDC can't generate correct code. So this also automatically upgrades all static type warnings to errors when strong mode is enabled. [email protected], [email protected] Review URL: https://codereview.chromium.org/1780783002 .
1 parent 99517a1 commit 2ef00b0

File tree

11 files changed

+718
-593
lines changed

11 files changed

+718
-593
lines changed

pkg/analyzer/lib/source/error_processor.dart

+29-1
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,37 @@ class ErrorProcessor {
9393
if (context == null) {
9494
return null;
9595
}
96+
97+
// By default, the error is not processed.
98+
ErrorProcessor processor;
99+
100+
// Give strong mode a chance to upgrade it.
101+
if (context.analysisOptions.strongMode) {
102+
processor = _StrongModeTypeErrorProcessor.instance;
103+
}
104+
105+
// Let the user configure how specific errors are processed.
96106
List<ErrorProcessor> processors =
97107
context.getConfigurationData(CONFIGURED_ERROR_PROCESSORS);
108+
98109
return processors.firstWhere((ErrorProcessor p) => p.appliesTo(error),
99-
orElse: () => null);
110+
orElse: () => processor);
100111
}
101112
}
113+
114+
/// In strong mode, this upgrades static type warnings to errors.
115+
class _StrongModeTypeErrorProcessor implements ErrorProcessor {
116+
static final instance = new _StrongModeTypeErrorProcessor();
117+
118+
// TODO(rnystrom): As far as I know, this is only used to implement
119+
// appliesTo(). Consider making it private in ErrorProcessor if possible.
120+
String get code => throw new UnsupportedError(
121+
"_StrongModeTypeErrorProcessor is not specific to an error code.");
122+
123+
/// In strong mode, type warnings are upgraded to errors.
124+
ErrorSeverity get severity => ErrorSeverity.ERROR;
125+
126+
/// Check if this processor applies to the given [error].
127+
bool appliesTo(AnalysisError error) =>
128+
error.errorCode.type == ErrorType.STATIC_TYPE_WARNING;
129+
}

pkg/analyzer/lib/src/generated/error_verifier.dart

+50-60
Original file line numberDiff line numberDiff line change
@@ -846,8 +846,9 @@ class ErrorVerifier extends RecursiveAstVisitor<Object> {
846846
}
847847
}
848848
_checkForExpectedOneListTypeArgument(node, typeArguments);
849-
_checkForListElementTypeNotAssignable(node, typeArguments);
850849
}
850+
851+
_checkForListElementTypeNotAssignable(node);
851852
return super.visitListLiteral(node);
852853
}
853854

@@ -863,8 +864,9 @@ class ErrorVerifier extends RecursiveAstVisitor<Object> {
863864
}
864865
}
865866
_checkExpectedTwoMapTypeArguments(typeArguments);
866-
_checkForMapTypeNotAssignable(node, typeArguments);
867867
}
868+
869+
_checkForMapTypeNotAssignable(node);
868870
_checkForNonConstMapAsExpressionStatement(node);
869871
return super.visitMapLiteral(node);
870872
}
@@ -4038,91 +4040,81 @@ class ErrorVerifier extends RecursiveAstVisitor<Object> {
40384040
}
40394041

40404042
/**
4041-
* Verify that the elements given list [literal] are subtypes of the specified
4042-
* element type. The [typeArguments] are the type arguments.
4043+
* Verify that the elements given list [literal] are subtypes of the list's
4044+
* static type.
40434045
*
40444046
* See [CompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE], and
40454047
* [StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE].
40464048
*/
4047-
bool _checkForListElementTypeNotAssignable(
4048-
ListLiteral literal, TypeArgumentList typeArguments) {
4049-
NodeList<TypeName> typeNames = typeArguments.arguments;
4050-
if (typeNames.length < 1) {
4051-
return false;
4052-
}
4053-
DartType listElementType = typeNames[0].type;
4049+
void _checkForListElementTypeNotAssignable(ListLiteral literal) {
4050+
// Determine the list's element type. We base this on the static type and
4051+
// not the literal's type arguments because in strong mode, the type
4052+
// arguments may be inferred.
4053+
DartType listType = literal.staticType;
4054+
assert(listType is InterfaceTypeImpl);
4055+
4056+
List<DartType> typeArguments =
4057+
(listType as InterfaceTypeImpl).typeArguments;
4058+
assert(typeArguments.length == 1);
4059+
4060+
DartType listElementType = typeArguments[0];
4061+
40544062
// Check every list element.
4055-
bool hasProblems = false;
40564063
for (Expression element in literal.elements) {
40574064
if (literal.constKeyword != null) {
40584065
// TODO(paulberry): this error should be based on the actual type of the
40594066
// list element, not the static type. See dartbug.com/21119.
4060-
if (_checkForArgumentTypeNotAssignableWithExpectedTypes(
4067+
_checkForArgumentTypeNotAssignableWithExpectedTypes(
40614068
element,
40624069
listElementType,
4063-
CheckedModeCompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE)) {
4064-
hasProblems = true;
4065-
}
4066-
}
4067-
if (_checkForArgumentTypeNotAssignableWithExpectedTypes(
4068-
element,
4069-
listElementType,
4070-
StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE)) {
4071-
hasProblems = true;
4070+
CheckedModeCompileTimeErrorCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE);
40724071
}
4072+
_checkForArgumentTypeNotAssignableWithExpectedTypes(element,
4073+
listElementType, StaticWarningCode.LIST_ELEMENT_TYPE_NOT_ASSIGNABLE);
40734074
}
4074-
return hasProblems;
40754075
}
40764076

40774077
/**
40784078
* Verify that the key/value of entries of the given map [literal] are
4079-
* subtypes of the key/value types specified in the type arguments. The
4080-
* [typeArguments] are the type arguments.
4079+
* subtypes of the map's static type.
40814080
*
40824081
* See [CompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE],
40834082
* [CompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE],
40844083
* [StaticWarningCode.MAP_KEY_TYPE_NOT_ASSIGNABLE], and
40854084
* [StaticWarningCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE].
40864085
*/
4087-
bool _checkForMapTypeNotAssignable(
4088-
MapLiteral literal, TypeArgumentList typeArguments) {
4089-
// Prepare maps key/value types.
4090-
NodeList<TypeName> typeNames = typeArguments.arguments;
4091-
if (typeNames.length < 2) {
4092-
return false;
4093-
}
4094-
DartType keyType = typeNames[0].type;
4095-
DartType valueType = typeNames[1].type;
4096-
// Check every map entry.
4097-
bool hasProblems = false;
4086+
void _checkForMapTypeNotAssignable(MapLiteral literal) {
4087+
// Determine the map's key and value types. We base this on the static type
4088+
// and not the literal's type arguments because in strong mode, the type
4089+
// arguments may be inferred.
4090+
DartType mapType = literal.staticType;
4091+
assert(mapType is InterfaceTypeImpl);
4092+
4093+
List<DartType> typeArguments =
4094+
(mapType as InterfaceTypeImpl).typeArguments;
4095+
assert(typeArguments.length == 2);
4096+
DartType keyType = typeArguments[0];
4097+
DartType valueType = typeArguments[1];
4098+
40984099
NodeList<MapLiteralEntry> entries = literal.entries;
40994100
for (MapLiteralEntry entry in entries) {
41004101
Expression key = entry.key;
41014102
Expression value = entry.value;
41024103
if (literal.constKeyword != null) {
41034104
// TODO(paulberry): this error should be based on the actual type of the
41044105
// list element, not the static type. See dartbug.com/21119.
4105-
if (_checkForArgumentTypeNotAssignableWithExpectedTypes(key, keyType,
4106-
CheckedModeCompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE)) {
4107-
hasProblems = true;
4108-
}
4109-
if (_checkForArgumentTypeNotAssignableWithExpectedTypes(
4106+
_checkForArgumentTypeNotAssignableWithExpectedTypes(key, keyType,
4107+
CheckedModeCompileTimeErrorCode.MAP_KEY_TYPE_NOT_ASSIGNABLE);
4108+
_checkForArgumentTypeNotAssignableWithExpectedTypes(
41104109
value,
41114110
valueType,
4112-
CheckedModeCompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE)) {
4113-
hasProblems = true;
4114-
}
4115-
}
4116-
if (_checkForArgumentTypeNotAssignableWithExpectedTypes(
4117-
key, keyType, StaticWarningCode.MAP_KEY_TYPE_NOT_ASSIGNABLE)) {
4118-
hasProblems = true;
4119-
}
4120-
if (_checkForArgumentTypeNotAssignableWithExpectedTypes(
4121-
value, valueType, StaticWarningCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE)) {
4122-
hasProblems = true;
4111+
CheckedModeCompileTimeErrorCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE);
41234112
}
4113+
_checkForArgumentTypeNotAssignableWithExpectedTypes(
4114+
key, keyType, StaticWarningCode.MAP_KEY_TYPE_NOT_ASSIGNABLE);
4115+
_checkForArgumentTypeNotAssignableWithExpectedTypes(
4116+
value, valueType, StaticWarningCode.MAP_VALUE_TYPE_NOT_ASSIGNABLE);
41244117
}
4125-
return hasProblems;
41264118
}
41274119

41284120
/**
@@ -5694,25 +5686,23 @@ class ErrorVerifier extends RecursiveAstVisitor<Object> {
56945686
: _typeProvider.iterableType;
56955687

56965688
// Use an explicit string instead of [loopType] to remove the "<E>".
5697-
String loopTypeName = node.awaitKeyword != null
5698-
? "Stream"
5699-
: "Iterable";
5689+
String loopTypeName = node.awaitKeyword != null ? "Stream" : "Iterable";
57005690

57015691
// The object being iterated has to implement Iterable<T> for some T that
57025692
// is assignable to the variable's type.
57035693
// TODO(rnystrom): Move this into mostSpecificTypeArgument()?
57045694
iterableType = iterableType.resolveToBound(_typeProvider.objectType);
5705-
DartType bestIterableType = _typeSystem.mostSpecificTypeArgument(
5706-
iterableType, loopType);
5695+
DartType bestIterableType =
5696+
_typeSystem.mostSpecificTypeArgument(iterableType, loopType);
57075697
if (bestIterableType == null) {
57085698
_errorReporter.reportTypeErrorForNode(
57095699
StaticTypeWarningCode.FOR_IN_OF_INVALID_TYPE,
5710-
node,
5700+
node.iterable,
57115701
[iterableType, loopTypeName]);
57125702
} else if (!_typeSystem.isAssignableTo(bestIterableType, variableType)) {
57135703
_errorReporter.reportTypeErrorForNode(
57145704
StaticTypeWarningCode.FOR_IN_OF_INVALID_ELEMENT_TYPE,
5715-
node,
5705+
node.iterable,
57165706
[iterableType, loopTypeName, variableType]);
57175707
}
57185708
}

pkg/analyzer/lib/src/generated/type_system.dart

+12
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,18 @@ class StrongTypeSystemImpl extends TypeSystem {
206206
return false;
207207
}
208208

209+
// Don't allow a non-generic function where a generic one is expected. The
210+
// former wouldn't know how to handle type arguments being passed to it.
211+
// TODO(rnystrom): This same check also exists in FunctionTypeImpl.relate()
212+
// but we don't always reliably go through that code path. This should be
213+
// cleaned up to avoid the redundancy.
214+
if (fromType is FunctionType &&
215+
toType is FunctionType &&
216+
fromType.typeFormals.isEmpty &&
217+
toType.typeFormals.isNotEmpty) {
218+
return false;
219+
}
220+
209221
// If the subtype relation goes the other way, allow the implicit downcast.
210222
// TODO(leafp): Emit warnings and hints for these in some way.
211223
// TODO(leafp): Consider adding a flag to disable these? Or just rely on

pkg/analyzer/lib/src/task/strong/checker.dart

+23-28
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class CodeChecker extends RecursiveAstVisitor {
145145
if (expr is ParenthesizedExpression) {
146146
checkAssignment(expr.expression, type);
147147
} else {
148-
_recordMessage(_checkAssignment(expr, type));
148+
_checkDowncast(expr, type);
149149
}
150150
}
151151

@@ -565,33 +565,23 @@ class CodeChecker extends RecursiveAstVisitor {
565565
node.visitChildren(this);
566566
}
567567

568-
StaticInfo _checkAssignment(Expression expr, DartType toT) {
569-
final fromT = _getStaticType(expr);
570-
final Coercion c = _coerceTo(fromT, toT);
571-
if (c is Identity) return null;
572-
if (c is CoercionError) return new StaticTypeError(rules, expr, toT);
573-
if (c is Cast) return DownCast.create(rules, expr, c);
574-
assert(false);
575-
return null;
576-
}
577-
578568
void _checkCompoundAssignment(AssignmentExpression expr) {
579569
var op = expr.operator.type;
580570
assert(op.isAssignmentOperator && op != TokenType.EQ);
581571
var methodElement = expr.staticElement;
582572
if (methodElement == null) {
583-
// Dynamic invocation
573+
// Dynamic invocation.
584574
_recordDynamicInvoke(expr, expr.leftHandSide);
585575
} else {
586-
// Sanity check the operator
576+
// Sanity check the operator.
587577
assert(methodElement.isOperator);
588578
var functionType = methodElement.type;
589579
var paramTypes = functionType.normalParameterTypes;
590580
assert(paramTypes.length == 1);
591581
assert(functionType.namedParameterTypes.isEmpty);
592582
assert(functionType.optionalParameterTypes.isEmpty);
593583

594-
// Check the lhs type
584+
// Check the LHS type.
595585
var staticInfo;
596586
var rhsType = _getStaticType(expr.rightHandSide);
597587
var lhsType = _getStaticType(expr.leftHandSide);
@@ -606,10 +596,9 @@ class CodeChecker extends RecursiveAstVisitor {
606596
// This is also slightly different from spec, but allows us to keep
607597
// compound operators in the int += num and num += dynamic cases.
608598
staticInfo = DownCast.create(
609-
rules, expr.rightHandSide, Coercion.cast(rhsType, lhsType));
599+
rules, expr.rightHandSide, new Cast(rhsType, lhsType));
610600
rhsType = lhsType;
611601
} else {
612-
// Static type error
613602
staticInfo = new StaticTypeError(rules, expr, lhsType);
614603
}
615604
_recordMessage(staticInfo);
@@ -618,8 +607,7 @@ class CodeChecker extends RecursiveAstVisitor {
618607
// Check the rhs type
619608
if (staticInfo is! CoercionInfo) {
620609
var paramType = paramTypes.first;
621-
staticInfo = _checkAssignment(expr.rightHandSide, paramType);
622-
_recordMessage(staticInfo);
610+
_checkDowncast(expr.rightHandSide, paramType);
623611
}
624612
}
625613
}
@@ -675,12 +663,18 @@ class CodeChecker extends RecursiveAstVisitor {
675663
}
676664
}
677665

678-
Coercion _coerceTo(DartType fromT, DartType toT) {
679-
// We can use anything as void
680-
if (toT.isVoid) return Coercion.identity(toT);
666+
/// Records a [DownCast] of [expr] to [toT], if there is one.
667+
///
668+
/// If [expr] does not require a downcast because it is not related to [toT]
669+
/// or is already a subtype of it, does nothing.
670+
void _checkDowncast(Expression expr, DartType toT) {
671+
DartType fromT = _getStaticType(expr);
672+
673+
// We can use anything as void.
674+
if (toT.isVoid) return;
681675

682-
// fromT <: toT, no coercion needed
683-
if (rules.isSubtypeOf(fromT, toT)) return Coercion.identity(toT);
676+
// fromT <: toT, no coercion needed.
677+
if (rules.isSubtypeOf(fromT, toT)) return;
684678

685679
// TODO(vsm): We can get rid of the second clause if we disallow
686680
// all sideways casts - see TODO below.
@@ -690,11 +684,14 @@ class CodeChecker extends RecursiveAstVisitor {
690684
// well for consistency.
691685
if ((fromT is FunctionType && rules.getCallMethodType(toT) != null) ||
692686
(toT is FunctionType && rules.getCallMethodType(fromT) != null)) {
693-
return Coercion.error();
687+
return;
694688
}
695689

696690
// Downcast if toT <: fromT
697-
if (rules.isSubtypeOf(toT, fromT)) return Coercion.cast(fromT, toT);
691+
if (rules.isSubtypeOf(toT, fromT)) {
692+
_recordMessage(DownCast.create(rules, expr, new Cast(fromT, toT)));
693+
return;
694+
}
698695

699696
// TODO(vsm): Once we have generic methods, we should delete this
700697
// workaround. These sideways casts are always ones we warn about
@@ -707,10 +704,8 @@ class CodeChecker extends RecursiveAstVisitor {
707704
// Iterable<T> for some concrete T (e.g. Object). These are unrelated
708705
// in the restricted system, but List<dynamic> <: Iterable<T> in dart.
709706
if (fromT.isAssignableTo(toT)) {
710-
return Coercion.cast(fromT, toT);
707+
_recordMessage(DownCast.create(rules, expr, new Cast(fromT, toT)));
711708
}
712-
713-
return Coercion.error();
714709
}
715710

716711
// Produce a coercion which coerces something of type fromT

pkg/analyzer/lib/src/task/strong/info.dart

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class Cast extends Coercion {
3434
Cast(DartType fromType, DartType toType) : super(fromType, toType);
3535
}
3636

37+
38+
// TODO(rnystrom): Analyzer no longer produces or uses anything except Cast,
39+
// so this should be eliminated once DDC no longer uses it.
3740
// The abstract type of coercions mapping one type to another.
3841
// This class also exposes static builder functions which
3942
// check for errors and reduce redundant coercions to the identity.

0 commit comments

Comments
 (0)