| Index: pkg/analyzer/lib/src/task/strong/checker.dart
|
| diff --git a/pkg/analyzer/lib/src/task/strong/checker.dart b/pkg/analyzer/lib/src/task/strong/checker.dart
|
| index 837e129515622cb1394066b0b0c49cb972f30366..b32ff6e515e26aa2b913ea66939586deaee448f5 100644
|
| --- a/pkg/analyzer/lib/src/task/strong/checker.dart
|
| +++ b/pkg/analyzer/lib/src/task/strong/checker.dart
|
| @@ -186,9 +186,7 @@ class CodeChecker extends RecursiveAstVisitor {
|
| // so no need to insert an error for this here.
|
| continue;
|
| }
|
| - DartType expectedType = _elementType(element);
|
| - if (expectedType == null) expectedType = DynamicTypeImpl.instance;
|
| - checkArgument(arg, expectedType);
|
| + checkArgument(arg, _elementType(element));
|
| }
|
| }
|
|
|
| @@ -196,7 +194,7 @@ class CodeChecker extends RecursiveAstVisitor {
|
| if (expr is ParenthesizedExpression) {
|
| checkAssignment(expr.expression, type);
|
| } else {
|
| - _checkDowncast(expr, type);
|
| + _checkImplicitCast(expr, type);
|
| }
|
| }
|
|
|
| @@ -409,7 +407,7 @@ class CodeChecker extends RecursiveAstVisitor {
|
| sequenceInterface.instantiate([DynamicTypeImpl.instance]);
|
|
|
| if (rules.isSubtypeOf(sequenceType, iterableType)) {
|
| - _recordImplicitCast(node.iterable, iterableType, sequenceType);
|
| + _recordImplicitCast(node.iterable, sequenceType, from: iterableType);
|
| elementType = DynamicTypeImpl.instance;
|
| }
|
| }
|
| @@ -419,7 +417,7 @@ class CodeChecker extends RecursiveAstVisitor {
|
| if (elementType != null) {
|
| // Insert a cast from the sequence's element type to the loop variable's
|
| // if needed.
|
| - _checkDowncast(loopVariable, _getDefiniteType(loopVariable),
|
| + _checkImplicitCast(loopVariable, _getDefiniteType(loopVariable),
|
| from: elementType);
|
| }
|
| }
|
| @@ -577,7 +575,7 @@ class CodeChecker extends RecursiveAstVisitor {
|
|
|
| @override
|
| void visitPostfixExpression(PostfixExpression node) {
|
| - _checkUnary(node, node.staticElement);
|
| + _checkUnary(node.operand, node.operator, node.staticElement);
|
| node.visitChildren(this);
|
| }
|
|
|
| @@ -591,7 +589,7 @@ class CodeChecker extends RecursiveAstVisitor {
|
| if (node.operator.type == TokenType.BANG) {
|
| checkBoolean(node.operand);
|
| } else {
|
| - _checkUnary(node, node.staticElement);
|
| + _checkUnary(node.operand, node.operator, node.staticElement);
|
| }
|
| node.visitChildren(this);
|
| }
|
| @@ -699,72 +697,75 @@ class CodeChecker extends RecursiveAstVisitor {
|
| assert(functionType.namedParameterTypes.isEmpty);
|
| assert(functionType.optionalParameterTypes.isEmpty);
|
|
|
| - // Check the LHS type.
|
| + // Refine the return type.
|
| var rhsType = _getDefiniteType(expr.rightHandSide);
|
| var lhsType = _getDefiniteType(expr.leftHandSide);
|
| var returnType = rules.refineBinaryExpressionType(
|
| typeProvider, lhsType, op, rhsType, functionType.returnType);
|
|
|
| - if (!rules.isSubtypeOf(returnType, lhsType)) {
|
| - final numType = typeProvider.numType;
|
| - // TODO(jmesserly): this seems to duplicate logic in StaticTypeAnalyzer.
|
| - // Try to fix up the numerical case if possible.
|
| - if (rules.isSubtypeOf(lhsType, numType) &&
|
| - rules.isSubtypeOf(lhsType, rhsType)) {
|
| - // This is also slightly different from spec, but allows us to keep
|
| - // compound operators in the int += num and num += dynamic cases.
|
| - _recordImplicitCast(expr.rightHandSide, rhsType, lhsType);
|
| - } else {
|
| - // TODO(jmesserly): this results in a duplicate error, because
|
| - // ErrorVerifier also reports it.
|
| - _recordMessage(expr, StrongModeCode.STATIC_TYPE_ERROR,
|
| - [expr, returnType, lhsType]);
|
| - }
|
| - } else {
|
| - // Check the RHS type.
|
| - //
|
| - // This is only needed if we didn't already need a cast, and avoids
|
| - // emitting two messages for the same expression.
|
| - _checkDowncast(expr.rightHandSide, paramTypes.first);
|
| - }
|
| + // Check the argument for an implicit cast.
|
| + _checkImplicitCast(expr.rightHandSide, paramTypes[0], from: rhsType);
|
| +
|
| + // Check the return type for an implicit cast.
|
| + //
|
| + // If needed, mark the assignment to indicate a down cast when we assign
|
| + // back to it. So these two implicit casts are equivalent:
|
| + //
|
| + // y = /*implicit cast*/(y + 42);
|
| + // /*implicit assignment cast*/y += 42;
|
| + //
|
| + _checkImplicitCast(expr.leftHandSide, lhsType,
|
| + from: returnType, opAssign: true);
|
| }
|
| }
|
|
|
| - /// Records a [DownCast] of [expr] from [from] to [to], if there is one.
|
| + /// Returns true if we need an implicit cast of [expr] from [from] type to
|
| + /// [to] type, otherwise returns false.
|
| ///
|
| /// If [from] is omitted, uses the static type of [expr].
|
| - ///
|
| - /// If [expr] does not require a downcast because it is not related to [to]
|
| - /// or is already a subtype of it, does nothing.
|
| - void _checkDowncast(Expression expr, DartType to, {DartType from}) {
|
| - if (from == null) {
|
| - from = _getDefiniteType(expr);
|
| - }
|
| + bool _needsImplicitCast(Expression expr, DartType to, {DartType from}) {
|
| + from ??= _getDefiniteType(expr);
|
|
|
| - if (!_checkNonNullAssignment(expr, to, from)) return;
|
| + if (!_checkNonNullAssignment(expr, to, from)) return false;
|
|
|
| // We can use anything as void.
|
| - if (to.isVoid) return;
|
| + if (to.isVoid) return false;
|
|
|
| // fromT <: toT, no coercion needed.
|
| - if (rules.isSubtypeOf(from, to)) return;
|
| + if (rules.isSubtypeOf(from, to)) return false;
|
|
|
| // Note: a function type is never assignable to a class per the Dart
|
| // spec - even if it has a compatible call method. We disallow as
|
| // well for consistency.
|
| if (from is FunctionType && rules.getCallMethodType(to) != null) {
|
| - return;
|
| + return false;
|
| }
|
|
|
| // Downcast if toT <: fromT
|
| if (rules.isSubtypeOf(to, from)) {
|
| - _recordImplicitCast(expr, from, to);
|
| - return;
|
| + return true;
|
| }
|
|
|
| // Anything else is an illegal sideways cast.
|
| // However, these will have been reported already in error_verifier, so we
|
| // don't need to report them again.
|
| + return false;
|
| + }
|
| +
|
| + /// Checks if an implicit cast of [expr] from [from] type to [to] type is
|
| + /// needed, and if so records it.
|
| + ///
|
| + /// If [from] is omitted, uses the static type of [expr].
|
| + ///
|
| + /// If [expr] does not require an implicit cast because it is not related to
|
| + /// [to] or is already a subtype of it, does nothing.
|
| + void _checkImplicitCast(Expression expr, DartType to,
|
| + {DartType from, bool opAssign: false}) {
|
| + from ??= _getDefiniteType(expr);
|
| +
|
| + if (_needsImplicitCast(expr, to, from: from)) {
|
| + _recordImplicitCast(expr, to, from: from, opAssign: opAssign);
|
| + }
|
| }
|
|
|
| void _checkFieldAccess(AstNode node, AstNode target, SimpleIdentifier field) {
|
| @@ -908,19 +909,39 @@ class CodeChecker extends RecursiveAstVisitor {
|
| }
|
| }
|
|
|
| - void _checkUnary(
|
| - /*PrefixExpression|PostfixExpression*/ node,
|
| - Element element) {
|
| - var op = node.operator;
|
| - if (op.isUserDefinableOperator ||
|
| - op.type == TokenType.PLUS_PLUS ||
|
| - op.type == TokenType.MINUS_MINUS) {
|
| + void _checkUnary(Expression operand, Token op, MethodElement element) {
|
| + bool isIncrementAssign =
|
| + op.type == TokenType.PLUS_PLUS || op.type == TokenType.MINUS_MINUS;
|
| + if (op.isUserDefinableOperator || isIncrementAssign) {
|
| if (element == null) {
|
| - _recordDynamicInvoke(node, node.operand);
|
| + _recordDynamicInvoke(operand.parent, operand);
|
| + } else if (isIncrementAssign) {
|
| + // For ++ and --, even if it is not dynamic, we still need to check
|
| + // that the user defined method accepts an `int` as the RHS.
|
| + //
|
| + // We assume Analyzer has done this already (in ErrorVerifier).
|
| + //
|
| + // However, we also need to check the return type.
|
| +
|
| + // Refine the return type.
|
| + var functionType = element.type;
|
| + var rhsType = typeProvider.intType;
|
| + var lhsType = _getDefiniteType(operand);
|
| + var returnType = rules.refineBinaryExpressionType(typeProvider, lhsType,
|
| + TokenType.PLUS, rhsType, functionType.returnType);
|
| +
|
| + // Skip the argument check - `int` cannot be downcast.
|
| + //
|
| + // Check the return type for an implicit cast.
|
| + //
|
| + // If needed, mark the assignment to indicate a down cast when we assign
|
| + // back to it. So these two implicit casts are equivalent:
|
| + //
|
| + // y = /*implicit cast*/(y + 1);
|
| + // /*implicit assignment cast*/y++;
|
| + //
|
| + _checkImplicitCast(operand, lhsType, from: returnType, opAssign: true);
|
| }
|
| - // For ++ and --, even if it is not dynamic, we still need to check
|
| - // that the user defined method accepts an `int` as the RHS.
|
| - // We assume Analyzer has done this already.
|
| }
|
| }
|
|
|
| @@ -1021,61 +1042,49 @@ class CodeChecker extends RecursiveAstVisitor {
|
| if (target != null) setIsDynamicInvoke(target, true);
|
| }
|
|
|
| - /// Records an implicit cast for the [expression] from [fromType] to [toType].
|
| + /// Records an implicit cast for the [expr] from [from] to [to].
|
| ///
|
| /// This will emit the appropriate error/warning/hint message as well as mark
|
| /// the AST node.
|
| - void _recordImplicitCast(
|
| - Expression expression, DartType fromType, DartType toType) {
|
| - // toT <:_R fromT => to <: fromT
|
| - // NB: classes with call methods are subtypes of function
|
| - // types, but the function type is not assignable to the class
|
| - assert(toType.isSubtypeOf(fromType));
|
| + void _recordImplicitCast(Expression expr, DartType to,
|
| + {DartType from, bool opAssign: false}) {
|
| + assert(rules.isSubtypeOf(to, from));
|
|
|
| // Inference "casts":
|
| - if (expression is Literal || expression is FunctionExpression) {
|
| + if (expr is Literal || expr is FunctionExpression) {
|
| // fromT should be an exact type - this will almost certainly fail at
|
| // runtime.
|
| - _recordMessage(expression, StrongModeCode.STATIC_TYPE_ERROR,
|
| - [expression, fromType, toType]);
|
| + _recordMessage(expr, StrongModeCode.STATIC_TYPE_ERROR, [expr, from, to]);
|
| return;
|
| }
|
|
|
| - if (expression is InstanceCreationExpression) {
|
| - ConstructorElement e = expression.staticElement;
|
| + if (expr is InstanceCreationExpression) {
|
| + ConstructorElement e = expr.staticElement;
|
| if (e == null || !e.isFactory) {
|
| // fromT should be an exact type - this will almost certainly fail at
|
| // runtime.
|
|
|
| - _recordMessage(expression, StrongModeCode.STATIC_TYPE_ERROR,
|
| - [expression, fromType, toType]);
|
| + _recordMessage(
|
| + expr, StrongModeCode.STATIC_TYPE_ERROR, [expr, from, to]);
|
| return;
|
| }
|
| }
|
|
|
| - if (isKnownFunction(expression)) {
|
| - _recordMessage(expression, StrongModeCode.STATIC_TYPE_ERROR,
|
| - [expression, fromType, toType]);
|
| + if (isKnownFunction(expr)) {
|
| + _recordMessage(expr, StrongModeCode.STATIC_TYPE_ERROR, [expr, from, to]);
|
| return;
|
| }
|
|
|
| - // TODO(vsm): Change this to an assert when we have generic methods and
|
| - // fix TypeRules._coerceTo to disallow implicit sideways casts.
|
| - bool downCastComposite = false;
|
| - if (!rules.isSubtypeOf(toType, fromType)) {
|
| - assert(toType.isSubtypeOf(fromType) || fromType.isAssignableTo(toType));
|
| - downCastComposite = true;
|
| - }
|
| -
|
| // Composite cast: these are more likely to fail.
|
| - if (!rules.isGroundType(toType)) {
|
| + bool downCastComposite = false;
|
| + if (!rules.isGroundType(to)) {
|
| // This cast is (probably) due to our different treatment of dynamic.
|
| // It may be more likely to fail at runtime.
|
| - if (fromType is InterfaceType) {
|
| + if (from is InterfaceType) {
|
| // For class types, we'd like to allow non-generic down casts, e.g.,
|
| // Iterable<T> to List<T>. The intuition here is that raw (generic)
|
| // casts are problematic, and we should complain about those.
|
| - var typeArgs = fromType.typeArguments;
|
| + var typeArgs = from.typeArguments;
|
| downCastComposite =
|
| typeArgs.isEmpty || typeArgs.any((t) => t.isDynamic);
|
| } else {
|
| @@ -1083,21 +1092,25 @@ class CodeChecker extends RecursiveAstVisitor {
|
| }
|
| }
|
|
|
| - var parent = expression.parent;
|
| + var parent = expr.parent;
|
| ErrorCode errorCode;
|
| if (downCastComposite) {
|
| errorCode = StrongModeCode.DOWN_CAST_COMPOSITE;
|
| - } else if (fromType.isDynamic) {
|
| + } else if (from.isDynamic) {
|
| errorCode = StrongModeCode.DYNAMIC_CAST;
|
| - } else if (parent is VariableDeclaration &&
|
| - parent.initializer == expression) {
|
| + } else if (parent is VariableDeclaration && parent.initializer == expr) {
|
| errorCode = StrongModeCode.ASSIGNMENT_CAST;
|
| } else {
|
| - errorCode = StrongModeCode.DOWN_CAST_IMPLICIT;
|
| + errorCode = opAssign
|
| + ? StrongModeCode.DOWN_CAST_IMPLICIT_ASSIGN
|
| + : StrongModeCode.DOWN_CAST_IMPLICIT;
|
| + }
|
| + _recordMessage(expr, errorCode, [from, to]);
|
| + if (opAssign) {
|
| + setImplicitAssignmentCast(expr, to);
|
| + } else {
|
| + setImplicitCast(expr, to);
|
| }
|
| -
|
| - _recordMessage(expression, errorCode, [fromType, toType]);
|
| - setImplicitCast(expression, toType);
|
| _hasImplicitCasts = true;
|
| }
|
|
|
|
|