OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 // TODO(jmesserly): this was ported from package:dev_compiler, and needs to be | 5 // TODO(jmesserly): this was ported from package:dev_compiler, and needs to be |
6 // refactored to fit into analyzer. | 6 // refactored to fit into analyzer. |
7 library analyzer.src.task.strong.checker; | 7 library analyzer.src.task.strong.checker; |
8 | 8 |
9 import 'package:analyzer/analyzer.dart'; | 9 import 'package:analyzer/analyzer.dart'; |
10 import 'package:analyzer/dart/ast/ast.dart'; | 10 import 'package:analyzer/dart/ast/ast.dart'; |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
138 DartType expectedType = _elementType(element); | 138 DartType expectedType = _elementType(element); |
139 if (expectedType == null) expectedType = DynamicTypeImpl.instance; | 139 if (expectedType == null) expectedType = DynamicTypeImpl.instance; |
140 checkArgument(arg, expectedType); | 140 checkArgument(arg, expectedType); |
141 } | 141 } |
142 } | 142 } |
143 | 143 |
144 void checkAssignment(Expression expr, DartType type) { | 144 void checkAssignment(Expression expr, DartType type) { |
145 if (expr is ParenthesizedExpression) { | 145 if (expr is ParenthesizedExpression) { |
146 checkAssignment(expr.expression, type); | 146 checkAssignment(expr.expression, type); |
147 } else { | 147 } else { |
148 _recordMessage(_checkAssignment(expr, type)); | 148 _checkDowncast(expr, type); |
149 } | 149 } |
150 } | 150 } |
151 | 151 |
152 /// Analyzer checks boolean conversions, but we need to check too, because | 152 /// Analyzer checks boolean conversions, but we need to check too, because |
153 /// it uses the default assignability rules that allow `dynamic` and `Object` | 153 /// it uses the default assignability rules that allow `dynamic` and `Object` |
154 /// to be assigned to bool with no message. | 154 /// to be assigned to bool with no message. |
155 void checkBoolean(Expression expr) => | 155 void checkBoolean(Expression expr) => |
156 checkAssignment(expr, typeProvider.boolType); | 156 checkAssignment(expr, typeProvider.boolType); |
157 | 157 |
158 void checkFunctionApplication( | 158 void checkFunctionApplication( |
(...skipping 399 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
558 checkBoolean(node.condition); | 558 checkBoolean(node.condition); |
559 node.visitChildren(this); | 559 node.visitChildren(this); |
560 } | 560 } |
561 | 561 |
562 @override | 562 @override |
563 void visitYieldStatement(YieldStatement node) { | 563 void visitYieldStatement(YieldStatement node) { |
564 _checkReturnOrYield(node.expression, node, yieldStar: node.star != null); | 564 _checkReturnOrYield(node.expression, node, yieldStar: node.star != null); |
565 node.visitChildren(this); | 565 node.visitChildren(this); |
566 } | 566 } |
567 | 567 |
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 | |
578 void _checkCompoundAssignment(AssignmentExpression expr) { | 568 void _checkCompoundAssignment(AssignmentExpression expr) { |
579 var op = expr.operator.type; | 569 var op = expr.operator.type; |
580 assert(op.isAssignmentOperator && op != TokenType.EQ); | 570 assert(op.isAssignmentOperator && op != TokenType.EQ); |
581 var methodElement = expr.staticElement; | 571 var methodElement = expr.staticElement; |
582 if (methodElement == null) { | 572 if (methodElement == null) { |
583 // Dynamic invocation | 573 // Dynamic invocation. |
584 _recordDynamicInvoke(expr, expr.leftHandSide); | 574 _recordDynamicInvoke(expr, expr.leftHandSide); |
585 } else { | 575 } else { |
586 // Sanity check the operator | 576 // Sanity check the operator. |
587 assert(methodElement.isOperator); | 577 assert(methodElement.isOperator); |
588 var functionType = methodElement.type; | 578 var functionType = methodElement.type; |
589 var paramTypes = functionType.normalParameterTypes; | 579 var paramTypes = functionType.normalParameterTypes; |
590 assert(paramTypes.length == 1); | 580 assert(paramTypes.length == 1); |
591 assert(functionType.namedParameterTypes.isEmpty); | 581 assert(functionType.namedParameterTypes.isEmpty); |
592 assert(functionType.optionalParameterTypes.isEmpty); | 582 assert(functionType.optionalParameterTypes.isEmpty); |
593 | 583 |
594 // Check the lhs type | 584 // Check the LHS type. |
595 var staticInfo; | 585 var staticInfo; |
596 var rhsType = _getStaticType(expr.rightHandSide); | 586 var rhsType = _getStaticType(expr.rightHandSide); |
597 var lhsType = _getStaticType(expr.leftHandSide); | 587 var lhsType = _getStaticType(expr.leftHandSide); |
598 var returnType = _specializedBinaryReturnType( | 588 var returnType = _specializedBinaryReturnType( |
599 op, lhsType, rhsType, functionType.returnType); | 589 op, lhsType, rhsType, functionType.returnType); |
600 | 590 |
601 if (!rules.isSubtypeOf(returnType, lhsType)) { | 591 if (!rules.isSubtypeOf(returnType, lhsType)) { |
602 final numType = typeProvider.numType; | 592 final numType = typeProvider.numType; |
603 // Try to fix up the numerical case if possible. | 593 // Try to fix up the numerical case if possible. |
604 if (rules.isSubtypeOf(lhsType, numType) && | 594 if (rules.isSubtypeOf(lhsType, numType) && |
605 rules.isSubtypeOf(lhsType, rhsType)) { | 595 rules.isSubtypeOf(lhsType, rhsType)) { |
606 // This is also slightly different from spec, but allows us to keep | 596 // This is also slightly different from spec, but allows us to keep |
607 // compound operators in the int += num and num += dynamic cases. | 597 // compound operators in the int += num and num += dynamic cases. |
608 staticInfo = DownCast.create( | 598 staticInfo = DownCast.create( |
609 rules, expr.rightHandSide, Coercion.cast(rhsType, lhsType)); | 599 rules, expr.rightHandSide, new Cast(rhsType, lhsType)); |
610 rhsType = lhsType; | 600 rhsType = lhsType; |
611 } else { | 601 } else { |
612 // Static type error | |
613 staticInfo = new StaticTypeError(rules, expr, lhsType); | 602 staticInfo = new StaticTypeError(rules, expr, lhsType); |
614 } | 603 } |
615 _recordMessage(staticInfo); | 604 _recordMessage(staticInfo); |
616 } | 605 } |
617 | 606 |
618 // Check the rhs type | 607 // Check the rhs type |
619 if (staticInfo is! CoercionInfo) { | 608 if (staticInfo is! CoercionInfo) { |
620 var paramType = paramTypes.first; | 609 var paramType = paramTypes.first; |
621 staticInfo = _checkAssignment(expr.rightHandSide, paramType); | 610 _checkDowncast(expr.rightHandSide, paramType); |
622 _recordMessage(staticInfo); | |
623 } | 611 } |
624 } | 612 } |
625 } | 613 } |
626 | 614 |
627 void _checkFieldAccess(AstNode node, AstNode target, SimpleIdentifier field) { | 615 void _checkFieldAccess(AstNode node, AstNode target, SimpleIdentifier field) { |
628 if ((_isDynamicTarget(target) || field.staticElement == null) && | 616 if ((_isDynamicTarget(target) || field.staticElement == null) && |
629 !_isObjectProperty(target, field)) { | 617 !_isObjectProperty(target, field)) { |
630 _recordDynamicInvoke(node, target); | 618 _recordDynamicInvoke(node, target); |
631 } | 619 } |
632 node.visitChildren(this); | 620 node.visitChildren(this); |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
668 op.type == TokenType.MINUS_MINUS) { | 656 op.type == TokenType.MINUS_MINUS) { |
669 if (_isDynamicTarget(node.operand)) { | 657 if (_isDynamicTarget(node.operand)) { |
670 _recordDynamicInvoke(node, node.operand); | 658 _recordDynamicInvoke(node, node.operand); |
671 } | 659 } |
672 // For ++ and --, even if it is not dynamic, we still need to check | 660 // For ++ and --, even if it is not dynamic, we still need to check |
673 // that the user defined method accepts an `int` as the RHS. | 661 // that the user defined method accepts an `int` as the RHS. |
674 // We assume Analyzer has done this already. | 662 // We assume Analyzer has done this already. |
675 } | 663 } |
676 } | 664 } |
677 | 665 |
678 Coercion _coerceTo(DartType fromT, DartType toT) { | 666 /// Records a [DownCast] of [expr] to [toT], if there is one. |
679 // We can use anything as void | 667 /// |
680 if (toT.isVoid) return Coercion.identity(toT); | 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); |
681 | 672 |
682 // fromT <: toT, no coercion needed | 673 // We can use anything as void. |
683 if (rules.isSubtypeOf(fromT, toT)) return Coercion.identity(toT); | 674 if (toT.isVoid) return; |
| 675 |
| 676 // fromT <: toT, no coercion needed. |
| 677 if (rules.isSubtypeOf(fromT, toT)) return; |
684 | 678 |
685 // TODO(vsm): We can get rid of the second clause if we disallow | 679 // TODO(vsm): We can get rid of the second clause if we disallow |
686 // all sideways casts - see TODO below. | 680 // all sideways casts - see TODO below. |
687 // ------- | 681 // ------- |
688 // Note: a function type is never assignable to a class per the Dart | 682 // Note: a function type is never assignable to a class per the Dart |
689 // spec - even if it has a compatible call method. We disallow as | 683 // spec - even if it has a compatible call method. We disallow as |
690 // well for consistency. | 684 // well for consistency. |
691 if ((fromT is FunctionType && rules.getCallMethodType(toT) != null) || | 685 if ((fromT is FunctionType && rules.getCallMethodType(toT) != null) || |
692 (toT is FunctionType && rules.getCallMethodType(fromT) != null)) { | 686 (toT is FunctionType && rules.getCallMethodType(fromT) != null)) { |
693 return Coercion.error(); | 687 return; |
694 } | 688 } |
695 | 689 |
696 // Downcast if toT <: fromT | 690 // 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 } |
698 | 695 |
699 // TODO(vsm): Once we have generic methods, we should delete this | 696 // TODO(vsm): Once we have generic methods, we should delete this |
700 // workaround. These sideways casts are always ones we warn about | 697 // workaround. These sideways casts are always ones we warn about |
701 // - i.e., we think they are likely to fail at runtime. | 698 // - i.e., we think they are likely to fail at runtime. |
702 // ------- | 699 // ------- |
703 // Downcast if toT <===> fromT | 700 // Downcast if toT <===> fromT |
704 // The intention here is to allow casts that are sideways in the restricted | 701 // The intention here is to allow casts that are sideways in the restricted |
705 // type system, but allowed in the regular dart type system, since these | 702 // type system, but allowed in the regular dart type system, since these |
706 // are likely to succeed. The canonical example is List<dynamic> and | 703 // are likely to succeed. The canonical example is List<dynamic> and |
707 // Iterable<T> for some concrete T (e.g. Object). These are unrelated | 704 // Iterable<T> for some concrete T (e.g. Object). These are unrelated |
708 // in the restricted system, but List<dynamic> <: Iterable<T> in dart. | 705 // in the restricted system, but List<dynamic> <: Iterable<T> in dart. |
709 if (fromT.isAssignableTo(toT)) { | 706 if (fromT.isAssignableTo(toT)) { |
710 return Coercion.cast(fromT, toT); | 707 _recordMessage(DownCast.create(rules, expr, new Cast(fromT, toT))); |
711 } | 708 } |
712 | |
713 return Coercion.error(); | |
714 } | 709 } |
715 | 710 |
716 // Produce a coercion which coerces something of type fromT | 711 // Produce a coercion which coerces something of type fromT |
717 // to something of type toT. | 712 // to something of type toT. |
718 // Returns the error coercion if the types cannot be coerced | 713 // Returns the error coercion if the types cannot be coerced |
719 // according to our current criteria. | 714 // according to our current criteria. |
720 /// Gets the expected return type of the given function [body], either from | 715 /// Gets the expected return type of the given function [body], either from |
721 /// a normal return/yield, or from a yield*. | 716 /// a normal return/yield, or from a yield*. |
722 DartType _getExpectedReturnType(FunctionBody body, {bool yieldStar: false}) { | 717 DartType _getExpectedReturnType(FunctionBody body, {bool yieldStar: false}) { |
723 FunctionType functionType; | 718 FunctionType functionType; |
(...skipping 552 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1276 } while (!current.isObject && !visited.contains(current)); | 1271 } while (!current.isObject && !visited.contains(current)); |
1277 } | 1272 } |
1278 | 1273 |
1279 void _recordMessage(StaticInfo info) { | 1274 void _recordMessage(StaticInfo info) { |
1280 if (info == null) return; | 1275 if (info == null) return; |
1281 var error = info.toAnalysisError(); | 1276 var error = info.toAnalysisError(); |
1282 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; | 1277 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; |
1283 _reporter.onError(error); | 1278 _reporter.onError(error); |
1284 } | 1279 } |
1285 } | 1280 } |
OLD | NEW |