| 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'; |
| 11 import 'package:analyzer/dart/ast/token.dart' show TokenType; | 11 import 'package:analyzer/dart/ast/token.dart' show TokenType; |
| 12 import 'package:analyzer/dart/ast/visitor.dart'; | 12 import 'package:analyzer/dart/ast/visitor.dart'; |
| 13 import 'package:analyzer/dart/element/element.dart'; | 13 import 'package:analyzer/dart/element/element.dart'; |
| 14 import 'package:analyzer/dart/element/type.dart'; | 14 import 'package:analyzer/dart/element/type.dart'; |
| 15 import 'package:analyzer/src/dart/element/type.dart'; | 15 import 'package:analyzer/src/dart/element/type.dart'; |
| 16 import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; |
| 16 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; | 17 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
| 17 import 'package:analyzer/src/generated/type_system.dart'; | 18 import 'package:analyzer/src/generated/type_system.dart'; |
| 18 | 19 |
| 19 import 'info.dart'; | 20 import 'info.dart'; |
| 20 | 21 |
| 21 DartType _elementType(Element e) { | 22 DartType _elementType(Element e) { |
| 22 if (e == null) { | 23 if (e == null) { |
| 23 // Malformed code - just return dynamic. | 24 // Malformed code - just return dynamic. |
| 24 return DynamicTypeImpl.instance; | 25 return DynamicTypeImpl.instance; |
| 25 } | 26 } |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 94 } | 95 } |
| 95 | 96 |
| 96 typedef FunctionType _MemberTypeGetter(InterfaceType type); | 97 typedef FunctionType _MemberTypeGetter(InterfaceType type); |
| 97 | 98 |
| 98 /// Checks the body of functions and properties. | 99 /// Checks the body of functions and properties. |
| 99 class CodeChecker extends RecursiveAstVisitor { | 100 class CodeChecker extends RecursiveAstVisitor { |
| 100 final StrongTypeSystemImpl rules; | 101 final StrongTypeSystemImpl rules; |
| 101 final TypeProvider typeProvider; | 102 final TypeProvider typeProvider; |
| 102 final AnalysisErrorListener reporter; | 103 final AnalysisErrorListener reporter; |
| 103 final _OverrideChecker _overrideChecker; | 104 final _OverrideChecker _overrideChecker; |
| 104 final bool _hints; | 105 final AnalysisOptionsImpl _options; |
| 105 | 106 |
| 106 bool _failure = false; | 107 bool _failure = false; |
| 108 |
| 107 CodeChecker(TypeProvider typeProvider, StrongTypeSystemImpl rules, | 109 CodeChecker(TypeProvider typeProvider, StrongTypeSystemImpl rules, |
| 108 AnalysisErrorListener reporter, | 110 AnalysisErrorListener reporter, this._options) |
| 109 {bool hints: false}) | |
| 110 : typeProvider = typeProvider, | 111 : typeProvider = typeProvider, |
| 111 rules = rules, | 112 rules = rules, |
| 112 reporter = reporter, | 113 reporter = reporter, |
| 113 _hints = hints, | |
| 114 _overrideChecker = new _OverrideChecker(typeProvider, rules, reporter); | 114 _overrideChecker = new _OverrideChecker(typeProvider, rules, reporter); |
| 115 | 115 |
| 116 bool get failure => _failure || _overrideChecker._failure; | 116 bool get failure => _failure || _overrideChecker._failure; |
| 117 | 117 |
| 118 void checkArgument(Expression arg, DartType expectedType) { | 118 void checkArgument(Expression arg, DartType expectedType) { |
| 119 // Preserve named argument structure, so their immediate parent is the | 119 // Preserve named argument structure, so their immediate parent is the |
| 120 // method invocation. | 120 // method invocation. |
| 121 Expression baseExpression = arg is NamedExpression ? arg.expression : arg; | 121 Expression baseExpression = arg is NamedExpression ? arg.expression : arg; |
| 122 checkAssignment(baseExpression, expectedType); | 122 checkAssignment(baseExpression, expectedType); |
| 123 } | 123 } |
| (...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 339 | 339 |
| 340 // If the sequence is not an Iterable (or Stream for await for) but is a | 340 // If the sequence is not an Iterable (or Stream for await for) but is a |
| 341 // supertype of it, do an implicit downcast to Iterable<dynamic>. Then | 341 // supertype of it, do an implicit downcast to Iterable<dynamic>. Then |
| 342 // we'll do a separate cast of the dynamic element to the variable's type. | 342 // we'll do a separate cast of the dynamic element to the variable's type. |
| 343 if (elementType == null) { | 343 if (elementType == null) { |
| 344 var sequenceType = | 344 var sequenceType = |
| 345 sequenceInterface.instantiate([DynamicTypeImpl.instance]); | 345 sequenceInterface.instantiate([DynamicTypeImpl.instance]); |
| 346 | 346 |
| 347 if (rules.isSubtypeOf(sequenceType, iterableType)) { | 347 if (rules.isSubtypeOf(sequenceType, iterableType)) { |
| 348 _recordMessage(DownCast.create( | 348 _recordMessage(DownCast.create( |
| 349 rules, node.iterable, iterableType, sequenceType)); | 349 rules, node.iterable, iterableType, sequenceType, _options)); |
| 350 elementType = DynamicTypeImpl.instance; | 350 elementType = DynamicTypeImpl.instance; |
| 351 } | 351 } |
| 352 } | 352 } |
| 353 | 353 |
| 354 // If the sequence doesn't implement the interface at all, [ErrorVerifier] | 354 // If the sequence doesn't implement the interface at all, [ErrorVerifier] |
| 355 // will report the error, so ignore it here. | 355 // will report the error, so ignore it here. |
| 356 if (elementType != null) { | 356 if (elementType != null) { |
| 357 // Insert a cast from the sequence's element type to the loop variable's | 357 // Insert a cast from the sequence's element type to the loop variable's |
| 358 // if needed. | 358 // if needed. |
| 359 _checkDowncast(loopVariable, _getStaticType(loopVariable), | 359 _checkDowncast(loopVariable, _getStaticType(loopVariable), |
| (...skipping 265 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 625 var returnType = rules.refineBinaryExpressionType( | 625 var returnType = rules.refineBinaryExpressionType( |
| 626 typeProvider, lhsType, op, rhsType, functionType.returnType); | 626 typeProvider, lhsType, op, rhsType, functionType.returnType); |
| 627 | 627 |
| 628 if (!rules.isSubtypeOf(returnType, lhsType)) { | 628 if (!rules.isSubtypeOf(returnType, lhsType)) { |
| 629 final numType = typeProvider.numType; | 629 final numType = typeProvider.numType; |
| 630 // Try to fix up the numerical case if possible. | 630 // Try to fix up the numerical case if possible. |
| 631 if (rules.isSubtypeOf(lhsType, numType) && | 631 if (rules.isSubtypeOf(lhsType, numType) && |
| 632 rules.isSubtypeOf(lhsType, rhsType)) { | 632 rules.isSubtypeOf(lhsType, rhsType)) { |
| 633 // This is also slightly different from spec, but allows us to keep | 633 // This is also slightly different from spec, but allows us to keep |
| 634 // compound operators in the int += num and num += dynamic cases. | 634 // compound operators in the int += num and num += dynamic cases. |
| 635 staticInfo = | 635 staticInfo = DownCast.create( |
| 636 DownCast.create(rules, expr.rightHandSide, rhsType, lhsType); | 636 rules, expr.rightHandSide, rhsType, lhsType, _options); |
| 637 rhsType = lhsType; | 637 rhsType = lhsType; |
| 638 } else { | 638 } else { |
| 639 staticInfo = new StaticTypeError(rules, expr, lhsType); | 639 staticInfo = new StaticTypeError(expr, lhsType); |
| 640 } | 640 } |
| 641 _recordMessage(staticInfo); | 641 _recordMessage(staticInfo); |
| 642 } | 642 } |
| 643 | 643 |
| 644 // Check the rhs type | 644 // Check the rhs type |
| 645 if (staticInfo is! CoercionInfo) { | 645 if (staticInfo is! CoercionInfo) { |
| 646 var paramType = paramTypes.first; | 646 var paramType = paramTypes.first; |
| 647 _checkDowncast(expr.rightHandSide, paramType); | 647 _checkDowncast(expr.rightHandSide, paramType); |
| 648 } | 648 } |
| 649 } | 649 } |
| (...skipping 22 matching lines...) Expand all Loading... |
| 672 // Note: a function type is never assignable to a class per the Dart | 672 // Note: a function type is never assignable to a class per the Dart |
| 673 // spec - even if it has a compatible call method. We disallow as | 673 // spec - even if it has a compatible call method. We disallow as |
| 674 // well for consistency. | 674 // well for consistency. |
| 675 if ((from is FunctionType && rules.getCallMethodType(to) != null) || | 675 if ((from is FunctionType && rules.getCallMethodType(to) != null) || |
| 676 (to is FunctionType && rules.getCallMethodType(from) != null)) { | 676 (to is FunctionType && rules.getCallMethodType(from) != null)) { |
| 677 return; | 677 return; |
| 678 } | 678 } |
| 679 | 679 |
| 680 // Downcast if toT <: fromT | 680 // Downcast if toT <: fromT |
| 681 if (rules.isSubtypeOf(to, from)) { | 681 if (rules.isSubtypeOf(to, from)) { |
| 682 _recordMessage(DownCast.create(rules, expr, from, to)); | 682 _recordMessage(DownCast.create(rules, expr, from, to, _options)); |
| 683 return; | 683 return; |
| 684 } | 684 } |
| 685 | 685 |
| 686 // TODO(vsm): Once we have generic methods, we should delete this | 686 // TODO(vsm): Once we have generic methods, we should delete this |
| 687 // workaround. These sideways casts are always ones we warn about | 687 // workaround. These sideways casts are always ones we warn about |
| 688 // - i.e., we think they are likely to fail at runtime. | 688 // - i.e., we think they are likely to fail at runtime. |
| 689 // ------- | 689 // ------- |
| 690 // Downcast if toT <===> fromT | 690 // Downcast if toT <===> fromT |
| 691 // The intention here is to allow casts that are sideways in the restricted | 691 // The intention here is to allow casts that are sideways in the restricted |
| 692 // type system, but allowed in the regular dart type system, since these | 692 // type system, but allowed in the regular dart type system, since these |
| 693 // are likely to succeed. The canonical example is List<dynamic> and | 693 // are likely to succeed. The canonical example is List<dynamic> and |
| 694 // Iterable<T> for some concrete T (e.g. Object). These are unrelated | 694 // Iterable<T> for some concrete T (e.g. Object). These are unrelated |
| 695 // in the restricted system, but List<dynamic> <: Iterable<T> in dart. | 695 // in the restricted system, but List<dynamic> <: Iterable<T> in dart. |
| 696 if (from.isAssignableTo(to)) { | 696 if (from.isAssignableTo(to)) { |
| 697 _recordMessage(DownCast.create(rules, expr, from, to)); | 697 _recordMessage(DownCast.create(rules, expr, from, to, _options)); |
| 698 } | 698 } |
| 699 } | 699 } |
| 700 | 700 |
| 701 void _checkFieldAccess(AstNode node, AstNode target, SimpleIdentifier field) { | 701 void _checkFieldAccess(AstNode node, AstNode target, SimpleIdentifier field) { |
| 702 if ((_isDynamicTarget(target) || field.staticElement == null) && | 702 if ((_isDynamicTarget(target) || field.staticElement == null) && |
| 703 !_isObjectProperty(target, field)) { | 703 !_isObjectProperty(target, field)) { |
| 704 _recordDynamicInvoke(node, target); | 704 _recordDynamicInvoke(node, target); |
| 705 } | 705 } |
| 706 node.visitChildren(this); | 706 node.visitChildren(this); |
| 707 } | 707 } |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 887 bool _isObjectMethod(Expression target, SimpleIdentifier id) { | 887 bool _isObjectMethod(Expression target, SimpleIdentifier id) { |
| 888 MethodElement element = typeProvider.objectType.element.getMethod(id.name); | 888 MethodElement element = typeProvider.objectType.element.getMethod(id.name); |
| 889 return (element != null && !element.isStatic); | 889 return (element != null && !element.isStatic); |
| 890 } | 890 } |
| 891 | 891 |
| 892 bool _isObjectProperty(Expression target, SimpleIdentifier id) { | 892 bool _isObjectProperty(Expression target, SimpleIdentifier id) { |
| 893 return _isObjectGetter(target, id) || _isObjectMethod(target, id); | 893 return _isObjectGetter(target, id) || _isObjectMethod(target, id); |
| 894 } | 894 } |
| 895 | 895 |
| 896 void _recordDynamicInvoke(AstNode node, AstNode target) { | 896 void _recordDynamicInvoke(AstNode node, AstNode target) { |
| 897 if (_hints) { | 897 if (_options.strongModeHints) { |
| 898 reporter.onError(new DynamicInvoke(rules, node).toAnalysisError()); | 898 reporter.onError(new DynamicInvoke(node).toAnalysisError()); |
| 899 } | 899 } |
| 900 // TODO(jmesserly): we may eventually want to record if the whole operation | 900 // TODO(jmesserly): we may eventually want to record if the whole operation |
| 901 // (node) was dynamic, rather than the target, but this is an easier fit | 901 // (node) was dynamic, rather than the target, but this is an easier fit |
| 902 // with what we used to do. | 902 // with what we used to do. |
| 903 DynamicInvoke.set(target, true); | 903 DynamicInvoke.set(target, true); |
| 904 } | 904 } |
| 905 | 905 |
| 906 void _recordMessage(StaticInfo info) { | 906 void _recordMessage(StaticInfo info) { |
| 907 if (info == null) return; | 907 if (info == null) return; |
| 908 var error = info.toAnalysisError(); | 908 var error = info.toAnalysisError(); |
| 909 var severity = error.errorCode.errorSeverity; | 909 var severity = error.errorCode.errorSeverity; |
| 910 if (severity == ErrorSeverity.ERROR) _failure = true; | 910 if (severity == ErrorSeverity.ERROR) _failure = true; |
| 911 if (severity != ErrorSeverity.INFO || _hints) { | 911 if (severity != ErrorSeverity.INFO || _options.strongModeHints) { |
| 912 reporter.onError(error); | 912 reporter.onError(error); |
| 913 } | 913 } |
| 914 | 914 |
| 915 if (info is CoercionInfo) { | 915 if (info is CoercionInfo) { |
| 916 // TODO(jmesserly): if we're run again on the same AST, we'll produce the | 916 // TODO(jmesserly): if we're run again on the same AST, we'll produce the |
| 917 // same annotations. This should be harmless. This might go away once | 917 // same annotations. This should be harmless. This might go away once |
| 918 // CodeChecker is integrated better with analyzer, as it will know that | 918 // CodeChecker is integrated better with analyzer, as it will know that |
| 919 // checking has already been performed. | 919 // checking has already been performed. |
| 920 // assert(CoercionInfo.get(info.node) == null); | 920 // assert(CoercionInfo.get(info.node) == null); |
| 921 CoercionInfo.set(info.node, info); | 921 CoercionInfo.set(info.node, info); |
| (...skipping 344 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1266 } while (!current.isObject && !visited.contains(current)); | 1266 } while (!current.isObject && !visited.contains(current)); |
| 1267 } | 1267 } |
| 1268 | 1268 |
| 1269 void _recordMessage(StaticInfo info) { | 1269 void _recordMessage(StaticInfo info) { |
| 1270 if (info == null) return; | 1270 if (info == null) return; |
| 1271 var error = info.toAnalysisError(); | 1271 var error = info.toAnalysisError(); |
| 1272 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; | 1272 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; |
| 1273 _reporter.onError(error); | 1273 _reporter.onError(error); |
| 1274 } | 1274 } |
| 1275 } | 1275 } |
| OLD | NEW |