| 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 315 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 326 void _recordMessage(StaticInfo info) { | 326 void _recordMessage(StaticInfo info) { |
| 327 if (info == null) return; | 327 if (info == null) return; |
| 328 var error = info.toAnalysisError(); | 328 var error = info.toAnalysisError(); |
| 329 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; | 329 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; |
| 330 _reporter.onError(error); | 330 _reporter.onError(error); |
| 331 } | 331 } |
| 332 } | 332 } |
| 333 | 333 |
| 334 /// Checks the body of functions and properties. | 334 /// Checks the body of functions and properties. |
| 335 class CodeChecker extends RecursiveAstVisitor { | 335 class CodeChecker extends RecursiveAstVisitor { |
| 336 final TypeRules _rules; | 336 final TypeRules rules; |
| 337 final AnalysisErrorListener _reporter; | 337 final AnalysisErrorListener reporter; |
| 338 final _OverrideChecker _overrideChecker; | 338 final _OverrideChecker _overrideChecker; |
| 339 bool _failure = false; | 339 bool _failure = false; |
| 340 bool get failure => _failure || _overrideChecker._failure; | 340 bool get failure => _failure || _overrideChecker._failure; |
| 341 | 341 |
| 342 CodeChecker(TypeRules rules, AnalysisErrorListener reporter, | 342 CodeChecker(TypeRules rules, AnalysisErrorListener reporter, |
| 343 StrongModeOptions options) | 343 StrongModeOptions options) |
| 344 : _rules = rules, | 344 : rules = rules, |
| 345 _reporter = reporter, | 345 reporter = reporter, |
| 346 _overrideChecker = new _OverrideChecker(rules, reporter, options); | 346 _overrideChecker = new _OverrideChecker(rules, reporter, options); |
| 347 | 347 |
| 348 @override | 348 @override |
| 349 void visitCompilationUnit(CompilationUnit unit) { | 349 void visitCompilationUnit(CompilationUnit unit) { |
| 350 void report(Expression expr) { | 350 void report(Expression expr) { |
| 351 _reporter.onError(new MissingTypeError(expr).toAnalysisError()); | 351 reporter.onError(new MissingTypeError(expr).toAnalysisError()); |
| 352 } | 352 } |
| 353 var callback = _rules.reportMissingType; | 353 var callback = rules.reportMissingType; |
| 354 _rules.reportMissingType = report; | 354 rules.reportMissingType = report; |
| 355 unit.visitChildren(this); | 355 unit.visitChildren(this); |
| 356 _rules.reportMissingType = callback; | 356 rules.reportMissingType = callback; |
| 357 } | 357 } |
| 358 | 358 |
| 359 @override | 359 @override |
| 360 void visitComment(Comment node) { | 360 void visitComment(Comment node) { |
| 361 // skip, no need to do typechecking inside comments (they may contain | 361 // skip, no need to do typechecking inside comments (they may contain |
| 362 // comment references which would require resolution). | 362 // comment references which would require resolution). |
| 363 } | 363 } |
| 364 | 364 |
| 365 @override | 365 @override |
| 366 void visitClassDeclaration(ClassDeclaration node) { | 366 void visitClassDeclaration(ClassDeclaration node) { |
| 367 _overrideChecker.check(node); | 367 _overrideChecker.check(node); |
| 368 super.visitClassDeclaration(node); | 368 super.visitClassDeclaration(node); |
| 369 } | 369 } |
| 370 | 370 |
| 371 @override | 371 @override |
| 372 void visitAssignmentExpression(AssignmentExpression node) { | 372 void visitAssignmentExpression(AssignmentExpression node) { |
| 373 var token = node.operator; | 373 var token = node.operator; |
| 374 if (token.type != TokenType.EQ) { | 374 if (token.type != TokenType.EQ) { |
| 375 _checkCompoundAssignment(node); | 375 _checkCompoundAssignment(node); |
| 376 } else { | 376 } else { |
| 377 DartType staticType = _rules.getStaticType(node.leftHandSide); | 377 DartType staticType = rules.getStaticType(node.leftHandSide); |
| 378 checkAssignment(node.rightHandSide, staticType); | 378 checkAssignment(node.rightHandSide, staticType); |
| 379 } | 379 } |
| 380 node.visitChildren(this); | 380 node.visitChildren(this); |
| 381 } | 381 } |
| 382 | 382 |
| 383 /// Check constructor declaration to ensure correct super call placement. | 383 /// Check constructor declaration to ensure correct super call placement. |
| 384 @override | 384 @override |
| 385 void visitConstructorDeclaration(ConstructorDeclaration node) { | 385 void visitConstructorDeclaration(ConstructorDeclaration node) { |
| 386 node.visitChildren(this); | 386 node.visitChildren(this); |
| 387 | 387 |
| 388 final init = node.initializers; | 388 final init = node.initializers; |
| 389 for (int i = 0, last = init.length - 1; i < last; i++) { | 389 for (int i = 0, last = init.length - 1; i < last; i++) { |
| 390 final node = init[i]; | 390 final node = init[i]; |
| 391 if (node is SuperConstructorInvocation) { | 391 if (node is SuperConstructorInvocation) { |
| 392 _recordMessage(new InvalidSuperInvocation(node)); | 392 _recordMessage(new InvalidSuperInvocation(node)); |
| 393 } | 393 } |
| 394 } | 394 } |
| 395 } | 395 } |
| 396 | 396 |
| 397 @override | 397 @override |
| 398 void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { | 398 void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| 399 var field = node.fieldName; | 399 var field = node.fieldName; |
| 400 DartType staticType = _rules.elementType(field.staticElement); | 400 DartType staticType = rules.elementType(field.staticElement); |
| 401 checkAssignment(node.expression, staticType); | 401 checkAssignment(node.expression, staticType); |
| 402 node.visitChildren(this); | 402 node.visitChildren(this); |
| 403 } | 403 } |
| 404 | 404 |
| 405 @override | 405 @override |
| 406 void visitForEachStatement(ForEachStatement node) { | 406 void visitForEachStatement(ForEachStatement node) { |
| 407 // Check that the expression is an Iterable. | 407 // Check that the expression is an Iterable. |
| 408 var expr = node.iterable; | 408 var expr = node.iterable; |
| 409 var iterableType = _rules.provider.iterableType; | 409 var iterableType = rules.provider.iterableType; |
| 410 var loopVariable = node.identifier != null | 410 var loopVariable = node.identifier != null |
| 411 ? node.identifier | 411 ? node.identifier |
| 412 : node.loopVariable.identifier; | 412 : node.loopVariable.identifier; |
| 413 var iteratorType = loopVariable.staticType; | 413 var iteratorType = loopVariable.staticType; |
| 414 var checkedType = iterableType.substitute4([iteratorType]); | 414 var checkedType = iterableType.substitute4([iteratorType]); |
| 415 checkAssignment(expr, checkedType); | 415 checkAssignment(expr, checkedType); |
| 416 node.visitChildren(this); | 416 node.visitChildren(this); |
| 417 } | 417 } |
| 418 | 418 |
| 419 @override | 419 @override |
| (...skipping 26 matching lines...) Expand all Loading... |
| 446 void visitSwitchStatement(SwitchStatement node) { | 446 void visitSwitchStatement(SwitchStatement node) { |
| 447 // SwitchStatement defines a boolean conversion to check the result of the | 447 // SwitchStatement defines a boolean conversion to check the result of the |
| 448 // case value == the switch value, but in dev_compiler we require a boolean | 448 // case value == the switch value, but in dev_compiler we require a boolean |
| 449 // return type from an overridden == operator (because Object.==), so | 449 // return type from an overridden == operator (because Object.==), so |
| 450 // checking in SwitchStatement shouldn't be necessary. | 450 // checking in SwitchStatement shouldn't be necessary. |
| 451 node.visitChildren(this); | 451 node.visitChildren(this); |
| 452 } | 452 } |
| 453 | 453 |
| 454 @override | 454 @override |
| 455 void visitListLiteral(ListLiteral node) { | 455 void visitListLiteral(ListLiteral node) { |
| 456 var type = _rules.provider.dynamicType; | 456 var type = rules.provider.dynamicType; |
| 457 if (node.typeArguments != null) { | 457 if (node.typeArguments != null) { |
| 458 var targs = node.typeArguments.arguments; | 458 var targs = node.typeArguments.arguments; |
| 459 if (targs.length > 0) type = targs[0].type; | 459 if (targs.length > 0) type = targs[0].type; |
| 460 } | 460 } |
| 461 var elements = node.elements; | 461 var elements = node.elements; |
| 462 for (int i = 0; i < elements.length; i++) { | 462 for (int i = 0; i < elements.length; i++) { |
| 463 checkArgument(elements[i], type); | 463 checkArgument(elements[i], type); |
| 464 } | 464 } |
| 465 super.visitListLiteral(node); | 465 super.visitListLiteral(node); |
| 466 } | 466 } |
| 467 | 467 |
| 468 @override | 468 @override |
| 469 void visitMapLiteral(MapLiteral node) { | 469 void visitMapLiteral(MapLiteral node) { |
| 470 var ktype = _rules.provider.dynamicType; | 470 var ktype = rules.provider.dynamicType; |
| 471 var vtype = _rules.provider.dynamicType; | 471 var vtype = rules.provider.dynamicType; |
| 472 if (node.typeArguments != null) { | 472 if (node.typeArguments != null) { |
| 473 var targs = node.typeArguments.arguments; | 473 var targs = node.typeArguments.arguments; |
| 474 if (targs.length > 0) ktype = targs[0].type; | 474 if (targs.length > 0) ktype = targs[0].type; |
| 475 if (targs.length > 1) vtype = targs[1].type; | 475 if (targs.length > 1) vtype = targs[1].type; |
| 476 } | 476 } |
| 477 var entries = node.entries; | 477 var entries = node.entries; |
| 478 for (int i = 0; i < entries.length; i++) { | 478 for (int i = 0; i < entries.length; i++) { |
| 479 var entry = entries[i]; | 479 var entry = entries[i]; |
| 480 checkArgument(entry.key, ktype); | 480 checkArgument(entry.key, ktype); |
| 481 checkArgument(entry.value, vtype); | 481 checkArgument(entry.value, vtype); |
| (...skipping 11 matching lines...) Expand all Loading... |
| 493 if (element == null) { | 493 if (element == null) { |
| 494 if (type.parameters.length < len) { | 494 if (type.parameters.length < len) { |
| 495 // We found an argument mismatch, the analyzer will report this too, | 495 // We found an argument mismatch, the analyzer will report this too, |
| 496 // so no need to insert an error for this here. | 496 // so no need to insert an error for this here. |
| 497 continue; | 497 continue; |
| 498 } | 498 } |
| 499 element = type.parameters[i]; | 499 element = type.parameters[i]; |
| 500 // TODO(vsm): When can this happen? | 500 // TODO(vsm): When can this happen? |
| 501 assert(element != null); | 501 assert(element != null); |
| 502 } | 502 } |
| 503 DartType expectedType = _rules.elementType(element); | 503 DartType expectedType = rules.elementType(element); |
| 504 if (expectedType == null) expectedType = _rules.provider.dynamicType; | 504 if (expectedType == null) expectedType = rules.provider.dynamicType; |
| 505 checkArgument(arg, expectedType); | 505 checkArgument(arg, expectedType); |
| 506 } | 506 } |
| 507 } | 507 } |
| 508 | 508 |
| 509 void checkArgument(Expression arg, DartType expectedType) { | 509 void checkArgument(Expression arg, DartType expectedType) { |
| 510 // Preserve named argument structure, so their immediate parent is the | 510 // Preserve named argument structure, so their immediate parent is the |
| 511 // method invocation. | 511 // method invocation. |
| 512 if (arg is NamedExpression) { | 512 if (arg is NamedExpression) { |
| 513 arg = (arg as NamedExpression).expression; | 513 arg = (arg as NamedExpression).expression; |
| 514 } | 514 } |
| 515 checkAssignment(arg, expectedType); | 515 checkAssignment(arg, expectedType); |
| 516 } | 516 } |
| 517 | 517 |
| 518 void checkFunctionApplication( | 518 void checkFunctionApplication( |
| 519 Expression node, Expression f, ArgumentList list) { | 519 Expression node, Expression f, ArgumentList list) { |
| 520 if (_rules.isDynamicCall(f)) { | 520 if (rules.isDynamicCall(f)) { |
| 521 // If f is Function and this is a method invocation, we should have | 521 // If f is Function and this is a method invocation, we should have |
| 522 // gotten an analyzer error, so no need to issue another error. | 522 // gotten an analyzer error, so no need to issue another error. |
| 523 _recordDynamicInvoke(node, f); | 523 _recordDynamicInvoke(node, f); |
| 524 } else { | 524 } else { |
| 525 checkArgumentList(list, _rules.getTypeAsCaller(f)); | 525 checkArgumentList(list, rules.getTypeAsCaller(f)); |
| 526 } | 526 } |
| 527 } | 527 } |
| 528 | 528 |
| 529 @override | 529 @override |
| 530 visitMethodInvocation(MethodInvocation node) { | 530 visitMethodInvocation(MethodInvocation node) { |
| 531 var target = node.realTarget; | 531 var target = node.realTarget; |
| 532 if (_rules.isDynamicTarget(target)) { | 532 if (rules.isDynamicTarget(target)) { |
| 533 _recordDynamicInvoke(node, target); | 533 _recordDynamicInvoke(node, target); |
| 534 | 534 |
| 535 // Mark the tear-off as being dynamic, too. This lets us distinguish | 535 // Mark the tear-off as being dynamic, too. This lets us distinguish |
| 536 // cases like: | 536 // cases like: |
| 537 // | 537 // |
| 538 // dynamic d; | 538 // dynamic d; |
| 539 // d.someMethod(...); // the whole method call must be a dynamic send. | 539 // d.someMethod(...); // the whole method call must be a dynamic send. |
| 540 // | 540 // |
| 541 // ... from case like: | 541 // ... from case like: |
| 542 // | 542 // |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 575 var type = node.staticElement.type; | 575 var type = node.staticElement.type; |
| 576 checkArgumentList(node.argumentList, type); | 576 checkArgumentList(node.argumentList, type); |
| 577 } | 577 } |
| 578 node.visitChildren(this); | 578 node.visitChildren(this); |
| 579 } | 579 } |
| 580 | 580 |
| 581 DartType _getExpectedReturnType(FunctionBody body, bool yieldStar) { | 581 DartType _getExpectedReturnType(FunctionBody body, bool yieldStar) { |
| 582 FunctionType functionType; | 582 FunctionType functionType; |
| 583 var parent = body.parent; | 583 var parent = body.parent; |
| 584 if (parent is Declaration) { | 584 if (parent is Declaration) { |
| 585 functionType = _rules.elementType(parent.element); | 585 functionType = rules.elementType(parent.element); |
| 586 } else { | 586 } else { |
| 587 assert(parent is FunctionExpression); | 587 assert(parent is FunctionExpression); |
| 588 functionType = _rules.getStaticType(parent); | 588 functionType = rules.getStaticType(parent); |
| 589 } | 589 } |
| 590 | 590 |
| 591 var type = functionType.returnType; | 591 var type = functionType.returnType; |
| 592 var provider = _rules.provider; | 592 var provider = rules.provider; |
| 593 | 593 |
| 594 InterfaceType expectedType = null; | 594 InterfaceType expectedType = null; |
| 595 if (body.isAsynchronous) { | 595 if (body.isAsynchronous) { |
| 596 if (body.isGenerator) { | 596 if (body.isGenerator) { |
| 597 // Stream<T> -> T | 597 // Stream<T> -> T |
| 598 expectedType = provider.streamType; | 598 expectedType = provider.streamType; |
| 599 } else { | 599 } else { |
| 600 // Future<T> -> T | 600 // Future<T> -> T |
| 601 // TODO(vsm): Revisit with issue #228. | 601 // TODO(vsm): Revisit with issue #228. |
| 602 expectedType = provider.futureType; | 602 expectedType = provider.futureType; |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 664 } | 664 } |
| 665 | 665 |
| 666 @override | 666 @override |
| 667 void visitYieldStatement(YieldStatement node) { | 667 void visitYieldStatement(YieldStatement node) { |
| 668 _checkReturnOrYield(node.expression, node, node.star != null); | 668 _checkReturnOrYield(node.expression, node, node.star != null); |
| 669 node.visitChildren(this); | 669 node.visitChildren(this); |
| 670 } | 670 } |
| 671 | 671 |
| 672 @override | 672 @override |
| 673 void visitAwaitExpression(AwaitExpression node) { | 673 void visitAwaitExpression(AwaitExpression node) { |
| 674 checkAssignment(node.expression, _rules.provider.futureDynamicType); | 674 checkAssignment(node.expression, rules.provider.futureDynamicType); |
| 675 node.visitChildren(this); | 675 node.visitChildren(this); |
| 676 } | 676 } |
| 677 | 677 |
| 678 @override | 678 @override |
| 679 void visitPropertyAccess(PropertyAccess node) { | 679 void visitPropertyAccess(PropertyAccess node) { |
| 680 var target = node.realTarget; | 680 var target = node.realTarget; |
| 681 if (_rules.isDynamicTarget(target)) { | 681 if (rules.isDynamicTarget(target)) { |
| 682 _recordDynamicInvoke(node, target); | 682 _recordDynamicInvoke(node, target); |
| 683 } | 683 } |
| 684 node.visitChildren(this); | 684 node.visitChildren(this); |
| 685 } | 685 } |
| 686 | 686 |
| 687 @override | 687 @override |
| 688 void visitPrefixedIdentifier(PrefixedIdentifier node) { | 688 void visitPrefixedIdentifier(PrefixedIdentifier node) { |
| 689 final target = node.prefix; | 689 final target = node.prefix; |
| 690 if (_rules.isDynamicTarget(target)) { | 690 if (rules.isDynamicTarget(target)) { |
| 691 _recordDynamicInvoke(node, target); | 691 _recordDynamicInvoke(node, target); |
| 692 } | 692 } |
| 693 node.visitChildren(this); | 693 node.visitChildren(this); |
| 694 } | 694 } |
| 695 | 695 |
| 696 @override | 696 @override |
| 697 void visitDefaultFormalParameter(DefaultFormalParameter node) { | 697 void visitDefaultFormalParameter(DefaultFormalParameter node) { |
| 698 // Check that defaults have the proper subtype. | 698 // Check that defaults have the proper subtype. |
| 699 var parameter = node.parameter; | 699 var parameter = node.parameter; |
| 700 var parameterType = _rules.elementType(parameter.element); | 700 var parameterType = rules.elementType(parameter.element); |
| 701 assert(parameterType != null); | 701 assert(parameterType != null); |
| 702 var defaultValue = node.defaultValue; | 702 var defaultValue = node.defaultValue; |
| 703 if (defaultValue == null) { | 703 if (defaultValue == null) { |
| 704 if (_rules.maybeNonNullableType(parameterType)) { | 704 if (rules.maybeNonNullableType(parameterType)) { |
| 705 var staticInfo = new InvalidVariableDeclaration( | 705 var staticInfo = new InvalidVariableDeclaration( |
| 706 _rules, node.identifier, parameterType); | 706 rules, node.identifier, parameterType); |
| 707 _recordMessage(staticInfo); | 707 _recordMessage(staticInfo); |
| 708 } | 708 } |
| 709 } else { | 709 } else { |
| 710 checkAssignment(defaultValue, parameterType); | 710 checkAssignment(defaultValue, parameterType); |
| 711 } | 711 } |
| 712 | 712 |
| 713 node.visitChildren(this); | 713 node.visitChildren(this); |
| 714 } | 714 } |
| 715 | 715 |
| 716 @override | 716 @override |
| 717 void visitFieldFormalParameter(FieldFormalParameter node) { | 717 void visitFieldFormalParameter(FieldFormalParameter node) { |
| 718 var element = node.element; | 718 var element = node.element; |
| 719 var typeName = node.type; | 719 var typeName = node.type; |
| 720 if (typeName != null) { | 720 if (typeName != null) { |
| 721 var type = _rules.elementType(element); | 721 var type = rules.elementType(element); |
| 722 var fieldElement = | 722 var fieldElement = |
| 723 node.identifier.staticElement as FieldFormalParameterElement; | 723 node.identifier.staticElement as FieldFormalParameterElement; |
| 724 var fieldType = _rules.elementType(fieldElement.field); | 724 var fieldType = rules.elementType(fieldElement.field); |
| 725 if (!_rules.isSubTypeOf(type, fieldType)) { | 725 if (!rules.isSubTypeOf(type, fieldType)) { |
| 726 var staticInfo = | 726 var staticInfo = |
| 727 new InvalidParameterDeclaration(_rules, node, fieldType); | 727 new InvalidParameterDeclaration(rules, node, fieldType); |
| 728 _recordMessage(staticInfo); | 728 _recordMessage(staticInfo); |
| 729 } | 729 } |
| 730 } | 730 } |
| 731 node.visitChildren(this); | 731 node.visitChildren(this); |
| 732 } | 732 } |
| 733 | 733 |
| 734 @override | 734 @override |
| 735 void visitInstanceCreationExpression(InstanceCreationExpression node) { | 735 void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| 736 var arguments = node.argumentList; | 736 var arguments = node.argumentList; |
| 737 var element = node.staticElement; | 737 var element = node.staticElement; |
| 738 if (element != null) { | 738 if (element != null) { |
| 739 var type = _rules.elementType(node.staticElement); | 739 var type = rules.elementType(node.staticElement); |
| 740 checkArgumentList(arguments, type); | 740 checkArgumentList(arguments, type); |
| 741 } else { | 741 } else { |
| 742 _recordMessage(new MissingTypeError(node)); | 742 _recordMessage(new MissingTypeError(node)); |
| 743 } | 743 } |
| 744 node.visitChildren(this); | 744 node.visitChildren(this); |
| 745 } | 745 } |
| 746 | 746 |
| 747 @override | 747 @override |
| 748 void visitVariableDeclarationList(VariableDeclarationList node) { | 748 void visitVariableDeclarationList(VariableDeclarationList node) { |
| 749 TypeName type = node.type; | 749 TypeName type = node.type; |
| 750 if (type == null) { | 750 if (type == null) { |
| 751 // No checks are needed when the type is var. Although internally the | 751 // No checks are needed when the type is var. Although internally the |
| 752 // typing rules may have inferred a more precise type for the variable | 752 // typing rules may have inferred a more precise type for the variable |
| 753 // based on the initializer. | 753 // based on the initializer. |
| 754 } else { | 754 } else { |
| 755 var dartType = getType(type); | 755 var dartType = getType(type); |
| 756 for (VariableDeclaration variable in node.variables) { | 756 for (VariableDeclaration variable in node.variables) { |
| 757 var initializer = variable.initializer; | 757 var initializer = variable.initializer; |
| 758 if (initializer != null) { | 758 if (initializer != null) { |
| 759 checkAssignment(initializer, dartType); | 759 checkAssignment(initializer, dartType); |
| 760 } else if (_rules.maybeNonNullableType(dartType)) { | 760 } else if (rules.maybeNonNullableType(dartType)) { |
| 761 var element = variable.element; | 761 var element = variable.element; |
| 762 if (element is FieldElement && !element.isStatic) { | 762 if (element is FieldElement && !element.isStatic) { |
| 763 // Initialized - possibly implicitly - during construction. | 763 // Initialized - possibly implicitly - during construction. |
| 764 // Handle this via a runtime check during code generation. | 764 // Handle this via a runtime check during code generation. |
| 765 | 765 |
| 766 // TODO(vsm): Detect statically whether this can fail and | 766 // TODO(vsm): Detect statically whether this can fail and |
| 767 // report a static error (must fail) or warning (can fail). | 767 // report a static error (must fail) or warning (can fail). |
| 768 } else { | 768 } else { |
| 769 var staticInfo = | 769 var staticInfo = |
| 770 new InvalidVariableDeclaration(_rules, variable, dartType); | 770 new InvalidVariableDeclaration(rules, variable, dartType); |
| 771 _recordMessage(staticInfo); | 771 _recordMessage(staticInfo); |
| 772 } | 772 } |
| 773 } | 773 } |
| 774 } | 774 } |
| 775 } | 775 } |
| 776 node.visitChildren(this); | 776 node.visitChildren(this); |
| 777 } | 777 } |
| 778 | 778 |
| 779 void _checkRuntimeTypeCheck(AstNode node, TypeName typeName) { | 779 void _checkRuntimeTypeCheck(AstNode node, TypeName typeName) { |
| 780 var type = getType(typeName); | 780 var type = getType(typeName); |
| 781 if (!_rules.isGroundType(type)) { | 781 if (!rules.isGroundType(type)) { |
| 782 _recordMessage(new InvalidRuntimeCheckError(node, type)); | 782 _recordMessage(new InvalidRuntimeCheckError(node, type)); |
| 783 } | 783 } |
| 784 } | 784 } |
| 785 | 785 |
| 786 @override | 786 @override |
| 787 void visitAsExpression(AsExpression node) { | 787 void visitAsExpression(AsExpression node) { |
| 788 // We could do the same check as the IsExpression below, but that is | 788 // We could do the same check as the IsExpression below, but that is |
| 789 // potentially too conservative. Instead, at runtime, we must fail hard | 789 // potentially too conservative. Instead, at runtime, we must fail hard |
| 790 // if the Dart as and the DDC as would return different values. | 790 // if the Dart as and the DDC as would return different values. |
| 791 node.visitChildren(this); | 791 node.visitChildren(this); |
| (...skipping 19 matching lines...) Expand all Loading... |
| 811 void visitPostfixExpression(PostfixExpression node) { | 811 void visitPostfixExpression(PostfixExpression node) { |
| 812 _checkUnary(node); | 812 _checkUnary(node); |
| 813 node.visitChildren(this); | 813 node.visitChildren(this); |
| 814 } | 814 } |
| 815 | 815 |
| 816 void _checkUnary(/*PrefixExpression|PostfixExpression*/ node) { | 816 void _checkUnary(/*PrefixExpression|PostfixExpression*/ node) { |
| 817 var op = node.operator; | 817 var op = node.operator; |
| 818 if (op.isUserDefinableOperator || | 818 if (op.isUserDefinableOperator || |
| 819 op.type == TokenType.PLUS_PLUS || | 819 op.type == TokenType.PLUS_PLUS || |
| 820 op.type == TokenType.MINUS_MINUS) { | 820 op.type == TokenType.MINUS_MINUS) { |
| 821 if (_rules.isDynamicTarget(node.operand)) { | 821 if (rules.isDynamicTarget(node.operand)) { |
| 822 _recordDynamicInvoke(node, node.operand); | 822 _recordDynamicInvoke(node, node.operand); |
| 823 } | 823 } |
| 824 // For ++ and --, even if it is not dynamic, we still need to check | 824 // For ++ and --, even if it is not dynamic, we still need to check |
| 825 // that the user defined method accepts an `int` as the RHS. | 825 // that the user defined method accepts an `int` as the RHS. |
| 826 // We assume Analyzer has done this already. | 826 // We assume Analyzer has done this already. |
| 827 } | 827 } |
| 828 } | 828 } |
| 829 | 829 |
| 830 @override | 830 @override |
| 831 void visitBinaryExpression(BinaryExpression node) { | 831 void visitBinaryExpression(BinaryExpression node) { |
| 832 var op = node.operator; | 832 var op = node.operator; |
| 833 if (op.isUserDefinableOperator) { | 833 if (op.isUserDefinableOperator) { |
| 834 if (_rules.isDynamicTarget(node.leftOperand)) { | 834 if (rules.isDynamicTarget(node.leftOperand)) { |
| 835 // Dynamic invocation | 835 // Dynamic invocation |
| 836 // TODO(vsm): Move this logic to the resolver? | 836 // TODO(vsm): Move this logic to the resolver? |
| 837 if (op.type != TokenType.EQ_EQ && op.type != TokenType.BANG_EQ) { | 837 if (op.type != TokenType.EQ_EQ && op.type != TokenType.BANG_EQ) { |
| 838 _recordDynamicInvoke(node, node.leftOperand); | 838 _recordDynamicInvoke(node, node.leftOperand); |
| 839 } | 839 } |
| 840 } else { | 840 } else { |
| 841 var element = node.staticElement; | 841 var element = node.staticElement; |
| 842 // Method invocation. | 842 // Method invocation. |
| 843 if (element is MethodElement) { | 843 if (element is MethodElement) { |
| 844 var type = element.type as FunctionType; | 844 var type = element.type as FunctionType; |
| (...skipping 25 matching lines...) Expand all Loading... |
| 870 | 870 |
| 871 @override | 871 @override |
| 872 void visitConditionalExpression(ConditionalExpression node) { | 872 void visitConditionalExpression(ConditionalExpression node) { |
| 873 checkBoolean(node.condition); | 873 checkBoolean(node.condition); |
| 874 node.visitChildren(this); | 874 node.visitChildren(this); |
| 875 } | 875 } |
| 876 | 876 |
| 877 @override | 877 @override |
| 878 void visitIndexExpression(IndexExpression node) { | 878 void visitIndexExpression(IndexExpression node) { |
| 879 var target = node.realTarget; | 879 var target = node.realTarget; |
| 880 if (_rules.isDynamicTarget(target)) { | 880 if (rules.isDynamicTarget(target)) { |
| 881 _recordDynamicInvoke(node, target); | 881 _recordDynamicInvoke(node, target); |
| 882 } else { | 882 } else { |
| 883 var element = node.staticElement; | 883 var element = node.staticElement; |
| 884 if (element is MethodElement) { | 884 if (element is MethodElement) { |
| 885 var type = element.type as FunctionType; | 885 var type = element.type as FunctionType; |
| 886 // Analyzer should enforce number of parameter types, but check in | 886 // Analyzer should enforce number of parameter types, but check in |
| 887 // case we have erroneous input. | 887 // case we have erroneous input. |
| 888 if (type.normalParameterTypes.isNotEmpty) { | 888 if (type.normalParameterTypes.isNotEmpty) { |
| 889 checkArgument(node.index, type.normalParameterTypes[0]); | 889 checkArgument(node.index, type.normalParameterTypes[0]); |
| 890 } | 890 } |
| 891 } else { | 891 } else { |
| 892 // TODO(vsm): Assert that the analyzer found an error here? | 892 // TODO(vsm): Assert that the analyzer found an error here? |
| 893 } | 893 } |
| 894 } | 894 } |
| 895 node.visitChildren(this); | 895 node.visitChildren(this); |
| 896 } | 896 } |
| 897 | 897 |
| 898 DartType getType(TypeName name) { | 898 DartType getType(TypeName name) { |
| 899 return (name == null) ? _rules.provider.dynamicType : name.type; | 899 return (name == null) ? rules.provider.dynamicType : name.type; |
| 900 } | 900 } |
| 901 | 901 |
| 902 /// Analyzer checks boolean conversions, but we need to check too, because | 902 /// Analyzer checks boolean conversions, but we need to check too, because |
| 903 /// it uses the default assignability rules that allow `dynamic` and `Object` | 903 /// it uses the default assignability rules that allow `dynamic` and `Object` |
| 904 /// to be assigned to bool with no message. | 904 /// to be assigned to bool with no message. |
| 905 void checkBoolean(Expression expr) => | 905 void checkBoolean(Expression expr) => |
| 906 checkAssignment(expr, _rules.provider.boolType); | 906 checkAssignment(expr, rules.provider.boolType); |
| 907 | 907 |
| 908 void checkAssignment(Expression expr, DartType type) { | 908 void checkAssignment(Expression expr, DartType type) { |
| 909 if (expr is ParenthesizedExpression) { | 909 if (expr is ParenthesizedExpression) { |
| 910 checkAssignment(expr.expression, type); | 910 checkAssignment(expr.expression, type); |
| 911 } else { | 911 } else { |
| 912 _recordMessage(_rules.checkAssignment(expr, type)); | 912 _recordMessage(rules.checkAssignment(expr, type)); |
| 913 } | 913 } |
| 914 } | 914 } |
| 915 | 915 |
| 916 DartType _specializedBinaryReturnType( | 916 DartType _specializedBinaryReturnType( |
| 917 TokenType op, DartType t1, DartType t2, DartType normalReturnType) { | 917 TokenType op, DartType t1, DartType t2, DartType normalReturnType) { |
| 918 // This special cases binary return types as per 16.26 and 16.27 of the | 918 // This special cases binary return types as per 16.26 and 16.27 of the |
| 919 // Dart language spec. | 919 // Dart language spec. |
| 920 switch (op) { | 920 switch (op) { |
| 921 case TokenType.PLUS: | 921 case TokenType.PLUS: |
| 922 case TokenType.MINUS: | 922 case TokenType.MINUS: |
| 923 case TokenType.STAR: | 923 case TokenType.STAR: |
| 924 case TokenType.TILDE_SLASH: | 924 case TokenType.TILDE_SLASH: |
| 925 case TokenType.PERCENT: | 925 case TokenType.PERCENT: |
| 926 case TokenType.PLUS_EQ: | 926 case TokenType.PLUS_EQ: |
| 927 case TokenType.MINUS_EQ: | 927 case TokenType.MINUS_EQ: |
| 928 case TokenType.STAR_EQ: | 928 case TokenType.STAR_EQ: |
| 929 case TokenType.TILDE_SLASH_EQ: | 929 case TokenType.TILDE_SLASH_EQ: |
| 930 case TokenType.PERCENT_EQ: | 930 case TokenType.PERCENT_EQ: |
| 931 if (_rules.isIntType(t1) && _rules.isIntType(t2)) return t1; | 931 if (rules.isIntType(t1) && rules.isIntType(t2)) return t1; |
| 932 if (_rules.isDoubleType(t1) && _rules.isDoubleType(t2)) return t1; | 932 if (rules.isDoubleType(t1) && rules.isDoubleType(t2)) return t1; |
| 933 // This particular combo is not spelled out in the spec, but all | 933 // This particular combo is not spelled out in the spec, but all |
| 934 // implementations and analyzer seem to follow this. | 934 // implementations and analyzer seem to follow this. |
| 935 if (_rules.isDoubleType(t1) && _rules.isIntType(t2)) return t1; | 935 if (rules.isDoubleType(t1) && rules.isIntType(t2)) return t1; |
| 936 } | 936 } |
| 937 return normalReturnType; | 937 return normalReturnType; |
| 938 } | 938 } |
| 939 | 939 |
| 940 void _checkCompoundAssignment(AssignmentExpression expr) { | 940 void _checkCompoundAssignment(AssignmentExpression expr) { |
| 941 var op = expr.operator.type; | 941 var op = expr.operator.type; |
| 942 assert(op.isAssignmentOperator && op != TokenType.EQ); | 942 assert(op.isAssignmentOperator && op != TokenType.EQ); |
| 943 var methodElement = expr.staticElement; | 943 var methodElement = expr.staticElement; |
| 944 if (methodElement == null) { | 944 if (methodElement == null) { |
| 945 // Dynamic invocation | 945 // Dynamic invocation |
| 946 _recordDynamicInvoke(expr, expr.leftHandSide); | 946 _recordDynamicInvoke(expr, expr.leftHandSide); |
| 947 } else { | 947 } else { |
| 948 // Sanity check the operator | 948 // Sanity check the operator |
| 949 assert(methodElement.isOperator); | 949 assert(methodElement.isOperator); |
| 950 var functionType = methodElement.type; | 950 var functionType = methodElement.type; |
| 951 var paramTypes = functionType.normalParameterTypes; | 951 var paramTypes = functionType.normalParameterTypes; |
| 952 assert(paramTypes.length == 1); | 952 assert(paramTypes.length == 1); |
| 953 assert(functionType.namedParameterTypes.isEmpty); | 953 assert(functionType.namedParameterTypes.isEmpty); |
| 954 assert(functionType.optionalParameterTypes.isEmpty); | 954 assert(functionType.optionalParameterTypes.isEmpty); |
| 955 | 955 |
| 956 // Check the lhs type | 956 // Check the lhs type |
| 957 var staticInfo; | 957 var staticInfo; |
| 958 var rhsType = _rules.getStaticType(expr.rightHandSide); | 958 var rhsType = rules.getStaticType(expr.rightHandSide); |
| 959 var lhsType = _rules.getStaticType(expr.leftHandSide); | 959 var lhsType = rules.getStaticType(expr.leftHandSide); |
| 960 var returnType = _specializedBinaryReturnType( | 960 var returnType = _specializedBinaryReturnType( |
| 961 op, lhsType, rhsType, functionType.returnType); | 961 op, lhsType, rhsType, functionType.returnType); |
| 962 | 962 |
| 963 if (!_rules.isSubTypeOf(returnType, lhsType)) { | 963 if (!rules.isSubTypeOf(returnType, lhsType)) { |
| 964 final numType = _rules.provider.numType; | 964 final numType = rules.provider.numType; |
| 965 // Try to fix up the numerical case if possible. | 965 // Try to fix up the numerical case if possible. |
| 966 if (_rules.isSubTypeOf(lhsType, numType) && | 966 if (rules.isSubTypeOf(lhsType, numType) && |
| 967 _rules.isSubTypeOf(lhsType, rhsType)) { | 967 rules.isSubTypeOf(lhsType, rhsType)) { |
| 968 // This is also slightly different from spec, but allows us to keep | 968 // This is also slightly different from spec, but allows us to keep |
| 969 // compound operators in the int += num and num += dynamic cases. | 969 // compound operators in the int += num and num += dynamic cases. |
| 970 staticInfo = DownCast.create( | 970 staticInfo = DownCast.create( |
| 971 _rules, expr.rightHandSide, Coercion.cast(rhsType, lhsType)); | 971 rules, expr.rightHandSide, Coercion.cast(rhsType, lhsType)); |
| 972 rhsType = lhsType; | 972 rhsType = lhsType; |
| 973 } else { | 973 } else { |
| 974 // Static type error | 974 // Static type error |
| 975 staticInfo = new StaticTypeError(_rules, expr, lhsType); | 975 staticInfo = new StaticTypeError(rules, expr, lhsType); |
| 976 } | 976 } |
| 977 _recordMessage(staticInfo); | 977 _recordMessage(staticInfo); |
| 978 } | 978 } |
| 979 | 979 |
| 980 // Check the rhs type | 980 // Check the rhs type |
| 981 if (staticInfo is! CoercionInfo) { | 981 if (staticInfo is! CoercionInfo) { |
| 982 var paramType = paramTypes.first; | 982 var paramType = paramTypes.first; |
| 983 staticInfo = _rules.checkAssignment(expr.rightHandSide, paramType); | 983 staticInfo = rules.checkAssignment(expr.rightHandSide, paramType); |
| 984 _recordMessage(staticInfo); | 984 _recordMessage(staticInfo); |
| 985 } | 985 } |
| 986 } | 986 } |
| 987 } | 987 } |
| 988 | 988 |
| 989 void _recordDynamicInvoke(AstNode node, AstNode target) { | 989 void _recordDynamicInvoke(AstNode node, AstNode target) { |
| 990 _reporter.onError(new DynamicInvoke(_rules, node).toAnalysisError()); | 990 reporter.onError(new DynamicInvoke(rules, node).toAnalysisError()); |
| 991 // TODO(jmesserly): we may eventually want to record if the whole operation | 991 // TODO(jmesserly): we may eventually want to record if the whole operation |
| 992 // (node) was dynamic, rather than the target, but this is an easier fit | 992 // (node) was dynamic, rather than the target, but this is an easier fit |
| 993 // with what we used to do. | 993 // with what we used to do. |
| 994 DynamicInvoke.set(target, true); | 994 DynamicInvoke.set(target, true); |
| 995 } | 995 } |
| 996 | 996 |
| 997 void _recordMessage(StaticInfo info) { | 997 void _recordMessage(StaticInfo info) { |
| 998 if (info == null) return; | 998 if (info == null) return; |
| 999 var error = info.toAnalysisError(); | 999 var error = info.toAnalysisError(); |
| 1000 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; | 1000 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; |
| 1001 _reporter.onError(error); | 1001 reporter.onError(error); |
| 1002 | 1002 |
| 1003 if (info is CoercionInfo) { | 1003 if (info is CoercionInfo) { |
| 1004 assert(CoercionInfo.get(info.node) == null); | 1004 assert(CoercionInfo.get(info.node) == null); |
| 1005 CoercionInfo.set(info.node, info); | 1005 CoercionInfo.set(info.node, info); |
| 1006 } | 1006 } |
| 1007 } | 1007 } |
| 1008 } | 1008 } |
| OLD | NEW |