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 |