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 library dev_compiler.src.checker.checker; | 5 library dev_compiler.src.checker.checker; |
6 | 6 |
7 import 'package:analyzer/analyzer.dart'; | 7 import 'package:analyzer/analyzer.dart'; |
8 import 'package:analyzer/src/generated/ast.dart'; | 8 import 'package:analyzer/src/generated/ast.dart'; |
9 import 'package:analyzer/src/generated/element.dart'; | 9 import 'package:analyzer/src/generated/element.dart'; |
10 import 'package:analyzer/src/generated/scanner.dart' show Token, TokenType; | 10 import 'package:analyzer/src/generated/scanner.dart' show Token, TokenType; |
(...skipping 319 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
330 if (info.level >= logger.Level.SEVERE) _failure = true; | 330 if (info.level >= logger.Level.SEVERE) _failure = true; |
331 _reporter.log(info); | 331 _reporter.log(info); |
332 } | 332 } |
333 } | 333 } |
334 | 334 |
335 /// Checks the body of functions and properties. | 335 /// Checks the body of functions and properties. |
336 class CodeChecker extends RecursiveAstVisitor { | 336 class CodeChecker extends RecursiveAstVisitor { |
337 final TypeRules _rules; | 337 final TypeRules _rules; |
338 final CheckerReporter _reporter; | 338 final CheckerReporter _reporter; |
339 final _OverrideChecker _overrideChecker; | 339 final _OverrideChecker _overrideChecker; |
340 bool _constantContext = false; | |
341 bool _failure = false; | 340 bool _failure = false; |
342 bool get failure => _failure || _overrideChecker._failure; | 341 bool get failure => _failure || _overrideChecker._failure; |
343 | 342 |
344 CodeChecker( | 343 CodeChecker( |
345 TypeRules rules, CheckerReporter reporter, StrongModeOptions options) | 344 TypeRules rules, CheckerReporter reporter, StrongModeOptions options) |
346 : _rules = rules, | 345 : _rules = rules, |
347 _reporter = reporter, | 346 _reporter = reporter, |
348 _overrideChecker = new _OverrideChecker(rules, reporter, options); | 347 _overrideChecker = new _OverrideChecker(rules, reporter, options); |
349 | 348 |
350 @override | 349 @override |
351 void visitCompilationUnit(CompilationUnit unit) { | 350 void visitCompilationUnit(CompilationUnit unit) { |
352 void report(Expression expr) { | 351 void report(Expression expr) { |
353 _reporter.log(new MissingTypeError(expr)); | 352 _reporter.log(new MissingTypeError(expr)); |
354 } | 353 } |
355 var callback = _rules.reportMissingType; | 354 var callback = _rules.reportMissingType; |
356 _rules.reportMissingType = report; | 355 _rules.reportMissingType = report; |
357 unit.visitChildren(this); | 356 unit.visitChildren(this); |
358 _rules.reportMissingType = callback; | 357 _rules.reportMissingType = callback; |
359 } | 358 } |
360 | 359 |
361 _visitMaybeConst(AstNode n, visitNode(AstNode n)) { | |
362 var o = _constantContext; | |
363 if (!o) { | |
364 if (n is VariableDeclarationList) { | |
365 _constantContext = o || n.isConst; | |
366 } else if (n is VariableDeclaration) { | |
367 _constantContext = o || n.isConst; | |
368 } else if (n is DefaultFormalParameter) { | |
369 _constantContext = true; | |
370 } else if (n is FormalParameter) { | |
371 _constantContext = o || n.isConst; | |
372 } else if (n is InstanceCreationExpression) { | |
373 _constantContext = o || n.isConst; | |
374 } else if (n is ConstructorDeclaration) { | |
375 _constantContext = o || n.element.isConst; | |
376 } | |
377 } | |
378 visitNode(n); | |
379 _constantContext = o; | |
380 } | |
381 | |
382 @override | 360 @override |
383 void visitComment(Comment node) { | 361 void visitComment(Comment node) { |
384 // skip, no need to do typechecking inside comments (they may contain | 362 // skip, no need to do typechecking inside comments (they may contain |
385 // comment references which would require resolution). | 363 // comment references which would require resolution). |
386 } | 364 } |
387 | 365 |
388 @override | 366 @override |
389 void visitClassDeclaration(ClassDeclaration node) { | 367 void visitClassDeclaration(ClassDeclaration node) { |
390 _overrideChecker.check(node); | 368 _overrideChecker.check(node); |
391 super.visitClassDeclaration(node); | 369 super.visitClassDeclaration(node); |
392 } | 370 } |
393 | 371 |
394 @override | 372 @override |
395 void visitAssignmentExpression(AssignmentExpression node) { | 373 void visitAssignmentExpression(AssignmentExpression node) { |
396 var token = node.operator; | 374 var token = node.operator; |
397 if (token.type != TokenType.EQ) { | 375 if (token.type != TokenType.EQ) { |
398 _checkCompoundAssignment(node); | 376 _checkCompoundAssignment(node); |
399 } else { | 377 } else { |
400 DartType staticType = _rules.getStaticType(node.leftHandSide); | 378 DartType staticType = _rules.getStaticType(node.leftHandSide); |
401 checkAssignment(node.rightHandSide, staticType); | 379 checkAssignment(node.rightHandSide, staticType); |
402 } | 380 } |
403 node.visitChildren(this); | 381 node.visitChildren(this); |
404 } | 382 } |
405 | 383 |
406 /// Check constructor declaration to ensure correct super call placement. | 384 /// Check constructor declaration to ensure correct super call placement. |
407 @override | 385 @override |
408 void visitConstructorDeclaration(ConstructorDeclaration node) { | 386 void visitConstructorDeclaration(ConstructorDeclaration node) { |
409 _visitMaybeConst(node, (node) { | 387 node.visitChildren(this); |
410 node.visitChildren(this); | |
411 | 388 |
412 final init = node.initializers; | 389 final init = node.initializers; |
413 for (int i = 0, last = init.length - 1; i < last; i++) { | 390 for (int i = 0, last = init.length - 1; i < last; i++) { |
414 final node = init[i]; | 391 final node = init[i]; |
415 if (node is SuperConstructorInvocation) { | 392 if (node is SuperConstructorInvocation) { |
416 _recordMessage(new InvalidSuperInvocation(node)); | 393 _recordMessage(new InvalidSuperInvocation(node)); |
417 } | |
418 } | 394 } |
419 }); | 395 } |
420 } | 396 } |
421 | 397 |
422 @override | 398 @override |
423 void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { | 399 void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
424 var field = node.fieldName; | 400 var field = node.fieldName; |
425 DartType staticType = _rules.elementType(field.staticElement); | 401 DartType staticType = _rules.elementType(field.staticElement); |
426 checkAssignment(node.expression, staticType); | 402 checkAssignment(node.expression, staticType); |
427 node.visitChildren(this); | 403 node.visitChildren(this); |
428 } | 404 } |
429 | 405 |
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
596 var element = node.staticElement; | 572 var element = node.staticElement; |
597 if (element == null) { | 573 if (element == null) { |
598 _recordMessage(new MissingTypeError(node)); | 574 _recordMessage(new MissingTypeError(node)); |
599 } else { | 575 } else { |
600 var type = node.staticElement.type; | 576 var type = node.staticElement.type; |
601 checkArgumentList(node.argumentList, type); | 577 checkArgumentList(node.argumentList, type); |
602 } | 578 } |
603 node.visitChildren(this); | 579 node.visitChildren(this); |
604 } | 580 } |
605 | 581 |
606 AstNode _getOwnerFunction(AstNode node) { | 582 DartType _getExpectedReturnType(FunctionBody body) { |
607 var parent = node.parent; | 583 FunctionType functionType; |
608 while (parent is! FunctionExpression && | 584 var parent = body.parent; |
609 parent is! MethodDeclaration && | 585 if (parent is Declaration) { |
610 parent is! ConstructorDeclaration) { | 586 functionType = _rules.elementType(parent.element); |
611 parent = parent.parent; | 587 } else { |
588 assert(parent is FunctionExpression); | |
589 functionType = _rules.getStaticType(parent); | |
612 } | 590 } |
613 return parent; | |
614 } | |
615 | 591 |
616 FunctionType _getFunctionType(AstNode node) { | 592 var type = functionType.returnType; |
617 if (node is Declaration) { | 593 var provider = _rules.provider; |
618 return _rules.elementType(node.element); | 594 if (type.isDynamic) return provider.dynamicType; |
595 | |
596 InterfaceType expectedType = null; | |
597 if (body.isAsynchronous) { | |
598 if (body.isGenerator) { | |
599 // Stream<T> -> T | |
600 expectedType = provider.streamType; | |
601 } else { | |
602 // Future<T> -> T | |
603 // TODO(vsm): Revisit with issue #228. | |
604 expectedType = provider.futureType; | |
605 } | |
619 } else { | 606 } else { |
620 assert(node is FunctionExpression); | 607 if (body.isGenerator) { |
621 return _rules.getStaticType(node); | 608 // Iterable<T> -> T |
609 expectedType = provider.iterableType; | |
610 } else { | |
611 // T -> T | |
612 return type; | |
613 } | |
614 } | |
615 if (type is InterfaceType && type.element == expectedType.element) { | |
616 return type.typeArguments[0]; | |
617 } else { | |
618 return null; | |
622 } | 619 } |
623 } | 620 } |
624 | 621 |
625 void _checkReturn(Expression expression, AstNode node) { | 622 FunctionBody _getFunctionBody(AstNode node) { |
626 var type = _getFunctionType(_getOwnerFunction(node)).returnType; | 623 while (node is! FunctionBody) { |
624 node = node.parent; | |
625 } | |
626 return node as FunctionBody; | |
627 } | |
628 | |
629 void _checkReturnOrYield(Expression expression, AstNode node) { | |
630 var body = _getFunctionBody(node); | |
631 var type = _getExpectedReturnType(body); | |
632 if (type == null) { | |
633 // We have a type mismatch: the async/async*/sync* modifier does | |
634 // not match the return or yield type. We should have already gotten an | |
635 // analyzer error in this case. | |
636 return; | |
637 } | |
627 // TODO(vsm): Enforce void or dynamic (to void?) when expression is null. | 638 // TODO(vsm): Enforce void or dynamic (to void?) when expression is null. |
628 if (expression != null) checkAssignment(expression, type); | 639 if (expression != null) checkAssignment(expression, type); |
629 } | 640 } |
630 | 641 |
631 @override | 642 @override |
632 void visitExpressionFunctionBody(ExpressionFunctionBody node) { | 643 void visitExpressionFunctionBody(ExpressionFunctionBody node) { |
633 _checkReturn(node.expression, node); | 644 _checkReturnOrYield(node.expression, node); |
634 node.visitChildren(this); | 645 node.visitChildren(this); |
635 } | 646 } |
636 | 647 |
637 @override | 648 @override |
638 void visitReturnStatement(ReturnStatement node) { | 649 void visitReturnStatement(ReturnStatement node) { |
639 _checkReturn(node.expression, node); | 650 _checkReturnOrYield(node.expression, node); |
640 node.visitChildren(this); | 651 node.visitChildren(this); |
641 } | 652 } |
642 | 653 |
654 @override | |
655 void visitYieldStatement(YieldStatement node) { | |
656 _checkReturnOrYield(node.expression, node); | |
Leaf
2015/06/19 22:15:27
I think this will do the wrong thing if this yield
| |
657 node.visitChildren(this); | |
658 } | |
659 | |
643 @override | 660 @override |
644 void visitPropertyAccess(PropertyAccess node) { | 661 void visitPropertyAccess(PropertyAccess node) { |
645 var target = node.realTarget; | 662 var target = node.realTarget; |
646 if (_rules.isDynamicTarget(target)) { | 663 if (_rules.isDynamicTarget(target)) { |
647 _recordDynamicInvoke(node, target); | 664 _recordDynamicInvoke(node, target); |
648 } | 665 } |
649 node.visitChildren(this); | 666 node.visitChildren(this); |
650 } | 667 } |
651 | 668 |
652 @override | 669 @override |
653 void visitPrefixedIdentifier(PrefixedIdentifier node) { | 670 void visitPrefixedIdentifier(PrefixedIdentifier node) { |
654 final target = node.prefix; | 671 final target = node.prefix; |
655 if (_rules.isDynamicTarget(target)) { | 672 if (_rules.isDynamicTarget(target)) { |
656 _recordDynamicInvoke(node, target); | 673 _recordDynamicInvoke(node, target); |
657 } | 674 } |
658 node.visitChildren(this); | 675 node.visitChildren(this); |
659 } | 676 } |
660 | 677 |
661 @override | 678 @override |
662 void visitDefaultFormalParameter(DefaultFormalParameter node) { | 679 void visitDefaultFormalParameter(DefaultFormalParameter node) { |
663 _visitMaybeConst(node, (node) { | 680 // Check that defaults have the proper subtype. |
664 // Check that defaults have the proper subtype. | 681 var parameter = node.parameter; |
665 var parameter = node.parameter; | 682 var parameterType = _rules.elementType(parameter.element); |
666 var parameterType = _rules.elementType(parameter.element); | 683 assert(parameterType != null); |
667 assert(parameterType != null); | 684 var defaultValue = node.defaultValue; |
668 var defaultValue = node.defaultValue; | 685 if (defaultValue == null) { |
669 if (defaultValue == null) { | 686 if (_rules.maybeNonNullableType(parameterType)) { |
670 if (_rules.maybeNonNullableType(parameterType)) { | 687 var staticInfo = new InvalidVariableDeclaration( |
671 var staticInfo = new InvalidVariableDeclaration( | 688 _rules, node.identifier, parameterType); |
672 _rules, node.identifier, parameterType); | 689 _recordMessage(staticInfo); |
673 _recordMessage(staticInfo); | |
674 } | |
675 } else { | |
676 checkAssignment(defaultValue, parameterType); | |
677 } | 690 } |
691 } else { | |
692 checkAssignment(defaultValue, parameterType); | |
693 } | |
678 | 694 |
679 node.visitChildren(this); | 695 node.visitChildren(this); |
680 }); | |
681 } | 696 } |
682 | 697 |
683 @override | 698 @override |
684 void visitFieldFormalParameter(FieldFormalParameter node) { | 699 void visitFieldFormalParameter(FieldFormalParameter node) { |
685 var element = node.element; | 700 var element = node.element; |
686 var typeName = node.type; | 701 var typeName = node.type; |
687 if (typeName != null) { | 702 if (typeName != null) { |
688 var type = _rules.elementType(element); | 703 var type = _rules.elementType(element); |
689 var fieldElement = | 704 var fieldElement = |
690 node.identifier.staticElement as FieldFormalParameterElement; | 705 node.identifier.staticElement as FieldFormalParameterElement; |
691 var fieldType = _rules.elementType(fieldElement.field); | 706 var fieldType = _rules.elementType(fieldElement.field); |
692 if (!_rules.isSubTypeOf(type, fieldType)) { | 707 if (!_rules.isSubTypeOf(type, fieldType)) { |
693 var staticInfo = | 708 var staticInfo = |
694 new InvalidParameterDeclaration(_rules, node, fieldType); | 709 new InvalidParameterDeclaration(_rules, node, fieldType); |
695 _recordMessage(staticInfo); | 710 _recordMessage(staticInfo); |
696 } | 711 } |
697 } | 712 } |
698 node.visitChildren(this); | 713 node.visitChildren(this); |
699 } | 714 } |
700 | 715 |
701 @override | 716 @override |
702 void visitInstanceCreationExpression(InstanceCreationExpression node) { | 717 void visitInstanceCreationExpression(InstanceCreationExpression node) { |
703 _visitMaybeConst(node, (node) { | 718 var arguments = node.argumentList; |
704 var arguments = node.argumentList; | 719 var element = node.staticElement; |
705 var element = node.staticElement; | 720 if (element != null) { |
706 if (element != null) { | 721 var type = _rules.elementType(node.staticElement); |
707 var type = _rules.elementType(node.staticElement); | 722 checkArgumentList(arguments, type); |
708 checkArgumentList(arguments, type); | 723 } else { |
709 } else { | 724 _recordMessage(new MissingTypeError(node)); |
710 _recordMessage(new MissingTypeError(node)); | 725 } |
711 } | 726 node.visitChildren(this); |
712 node.visitChildren(this); | |
713 }); | |
714 } | 727 } |
715 | 728 |
716 @override | 729 @override |
717 void visitVariableDeclarationList(VariableDeclarationList node) { | 730 void visitVariableDeclarationList(VariableDeclarationList node) { |
718 _visitMaybeConst(node, (node) { | 731 TypeName type = node.type; |
719 TypeName type = node.type; | 732 if (type == null) { |
720 if (type == null) { | 733 // No checks are needed when the type is var. Although internally the |
721 // No checks are needed when the type is var. Although internally the | 734 // typing rules may have inferred a more precise type for the variable |
722 // typing rules may have inferred a more precise type for the variable | 735 // based on the initializer. |
723 // based on the initializer. | 736 } else { |
724 } else { | 737 var dartType = getType(type); |
725 var dartType = getType(type); | 738 for (VariableDeclaration variable in node.variables) { |
726 for (VariableDeclaration variable in node.variables) { | 739 var initializer = variable.initializer; |
727 var initializer = variable.initializer; | 740 if (initializer != null) { |
728 if (initializer != null) { | 741 checkAssignment(initializer, dartType); |
729 checkAssignment(initializer, dartType); | 742 } else if (_rules.maybeNonNullableType(dartType)) { |
730 } else if (_rules.maybeNonNullableType(dartType)) { | 743 var element = variable.element; |
731 var element = variable.element; | 744 if (element is FieldElement && !element.isStatic) { |
732 if (element is FieldElement && !element.isStatic) { | 745 // Initialized - possibly implicitly - during construction. |
733 // Initialized - possibly implicitly - during construction. | 746 // Handle this via a runtime check during code generation. |
734 // Handle this via a runtime check during code generation. | |
735 | 747 |
736 // TODO(vsm): Detect statically whether this can fail and | 748 // TODO(vsm): Detect statically whether this can fail and |
737 // report a static error (must fail) or warning (can fail). | 749 // report a static error (must fail) or warning (can fail). |
738 } else { | 750 } else { |
739 var staticInfo = | 751 var staticInfo = |
740 new InvalidVariableDeclaration(_rules, variable, dartType); | 752 new InvalidVariableDeclaration(_rules, variable, dartType); |
741 _recordMessage(staticInfo); | 753 _recordMessage(staticInfo); |
742 } | |
743 } | 754 } |
744 } | 755 } |
745 } | 756 } |
746 node.visitChildren(this); | 757 } |
747 }); | 758 node.visitChildren(this); |
748 } | |
749 | |
750 @override | |
751 void visitVariableDeclaration(VariableDeclaration node) { | |
752 _visitMaybeConst(node, super.visitVariableDeclaration); | |
753 } | 759 } |
754 | 760 |
755 void _checkRuntimeTypeCheck(AstNode node, TypeName typeName) { | 761 void _checkRuntimeTypeCheck(AstNode node, TypeName typeName) { |
756 var type = getType(typeName); | 762 var type = getType(typeName); |
757 if (!_rules.isGroundType(type)) { | 763 if (!_rules.isGroundType(type)) { |
758 _recordMessage(new InvalidRuntimeCheckError(node, type)); | 764 _recordMessage(new InvalidRuntimeCheckError(node, type)); |
759 } | 765 } |
760 } | 766 } |
761 | 767 |
762 @override | 768 @override |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
878 /// Analyzer checks boolean conversions, but we need to check too, because | 884 /// Analyzer checks boolean conversions, but we need to check too, because |
879 /// it uses the default assignability rules that allow `dynamic` and `Object` | 885 /// it uses the default assignability rules that allow `dynamic` and `Object` |
880 /// to be assigned to bool with no message. | 886 /// to be assigned to bool with no message. |
881 void checkBoolean(Expression expr) => | 887 void checkBoolean(Expression expr) => |
882 checkAssignment(expr, _rules.provider.boolType); | 888 checkAssignment(expr, _rules.provider.boolType); |
883 | 889 |
884 void checkAssignment(Expression expr, DartType type) { | 890 void checkAssignment(Expression expr, DartType type) { |
885 if (expr is ParenthesizedExpression) { | 891 if (expr is ParenthesizedExpression) { |
886 checkAssignment(expr.expression, type); | 892 checkAssignment(expr.expression, type); |
887 } else { | 893 } else { |
888 _recordMessage(_rules.checkAssignment(expr, type, _constantContext)); | 894 _recordMessage(_rules.checkAssignment(expr, type)); |
889 } | 895 } |
890 } | 896 } |
891 | 897 |
892 DartType _specializedBinaryReturnType( | 898 DartType _specializedBinaryReturnType( |
893 TokenType op, DartType t1, DartType t2, DartType normalReturnType) { | 899 TokenType op, DartType t1, DartType t2, DartType normalReturnType) { |
894 // This special cases binary return types as per 16.26 and 16.27 of the | 900 // This special cases binary return types as per 16.26 and 16.27 of the |
895 // Dart language spec. | 901 // Dart language spec. |
896 switch (op) { | 902 switch (op) { |
897 case TokenType.PLUS: | 903 case TokenType.PLUS: |
898 case TokenType.MINUS: | 904 case TokenType.MINUS: |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
949 } else { | 955 } else { |
950 // Static type error | 956 // Static type error |
951 staticInfo = new StaticTypeError(_rules, expr, lhsType); | 957 staticInfo = new StaticTypeError(_rules, expr, lhsType); |
952 } | 958 } |
953 _recordMessage(staticInfo); | 959 _recordMessage(staticInfo); |
954 } | 960 } |
955 | 961 |
956 // Check the rhs type | 962 // Check the rhs type |
957 if (staticInfo is! CoercionInfo) { | 963 if (staticInfo is! CoercionInfo) { |
958 var paramType = paramTypes.first; | 964 var paramType = paramTypes.first; |
959 staticInfo = _rules.checkAssignment( | 965 staticInfo = _rules.checkAssignment(expr.rightHandSide, paramType); |
960 expr.rightHandSide, paramType, _constantContext); | |
961 _recordMessage(staticInfo); | 966 _recordMessage(staticInfo); |
962 } | 967 } |
963 } | 968 } |
964 } | 969 } |
965 | 970 |
966 void _recordDynamicInvoke(AstNode node, AstNode target) { | 971 void _recordDynamicInvoke(AstNode node, AstNode target) { |
967 var dinvoke = new DynamicInvoke(_rules, node); | 972 var dinvoke = new DynamicInvoke(_rules, node); |
968 _reporter.log(dinvoke); | 973 _reporter.log(dinvoke); |
969 // TODO(jmesserly): we may eventually want to record if the whole operation | 974 // TODO(jmesserly): we may eventually want to record if the whole operation |
970 // (node) was dynamic, rather than the target, but this is an easier fit | 975 // (node) was dynamic, rather than the target, but this is an easier fit |
971 // with what we used to do. | 976 // with what we used to do. |
972 DynamicInvoke.set(target, true); | 977 DynamicInvoke.set(target, true); |
973 } | 978 } |
974 | 979 |
975 void _recordMessage(StaticInfo info) { | 980 void _recordMessage(StaticInfo info) { |
976 if (info == null) return; | 981 if (info == null) return; |
977 if (info.level >= logger.Level.SEVERE) _failure = true; | 982 if (info.level >= logger.Level.SEVERE) _failure = true; |
978 _reporter.log(info); | 983 _reporter.log(info); |
979 if (info is CoercionInfo) { | 984 if (info is CoercionInfo) { |
980 assert(CoercionInfo.get(info.node) == null); | 985 assert(CoercionInfo.get(info.node) == null); |
981 CoercionInfo.set(info.node, info); | 986 CoercionInfo.set(info.node, info); |
982 } | 987 } |
983 } | 988 } |
984 } | 989 } |
OLD | NEW |