| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // TODO(jmesserly): this was ported from package:dev_compiler, and needs to be | 5 // TODO(jmesserly): this was ported from package:dev_compiler, and needs to be |
| 6 // refactored to fit into analyzer. | 6 // refactored to fit into analyzer. |
| 7 library analyzer.src.task.strong.checker; | 7 library analyzer.src.task.strong.checker; |
| 8 | 8 |
| 9 import 'package:analyzer/analyzer.dart'; | 9 import 'package:analyzer/analyzer.dart'; |
| 10 import 'package:analyzer/dart/ast/ast.dart'; | 10 import 'package:analyzer/dart/ast/ast.dart'; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 22 import 'ast_properties.dart'; | 22 import 'ast_properties.dart'; |
| 23 | 23 |
| 24 bool isKnownFunction(Expression expression) { | 24 bool isKnownFunction(Expression expression) { |
| 25 var element = _getKnownElement(expression); | 25 var element = _getKnownElement(expression); |
| 26 // First class functions and static methods, where we know the original | 26 // First class functions and static methods, where we know the original |
| 27 // declaration, will have an exact type, so we know a downcast will fail. | 27 // declaration, will have an exact type, so we know a downcast will fail. |
| 28 return element is FunctionElement || | 28 return element is FunctionElement || |
| 29 element is MethodElement && element.isStatic; | 29 element is MethodElement && element.isStatic; |
| 30 } | 30 } |
| 31 | 31 |
| 32 /// Given an [expression] and a corresponding [typeSystem] and [typeProvider], |
| 33 /// gets the known static type of the expression. |
| 34 /// |
| 35 /// Normally when we ask for an expression's type, we get the type of the |
| 36 /// storage slot that would contain it. For function types, this is necessarily |
| 37 /// a "fuzzy arrow" that treats `dynamic` as bottom. However, if we're |
| 38 /// interested in the expression's own type, it can often be a "strict arrow" |
| 39 /// because we know it evaluates to a specific, concrete function, and we can |
| 40 /// treat "dynamic" as top for that case, which is more permissive. |
| 41 DartType getDefiniteType( |
| 42 Expression expression, TypeSystem typeSystem, TypeProvider typeProvider) { |
| 43 DartType type = expression.staticType ?? DynamicTypeImpl.instance; |
| 44 if (typeSystem is StrongTypeSystemImpl && |
| 45 type is FunctionType && |
| 46 _hasStrictArrow(expression)) { |
| 47 // Remove fuzzy arrow if possible. |
| 48 return typeSystem.functionTypeToConcreteType(typeProvider, type); |
| 49 } |
| 50 return type; |
| 51 } |
| 52 |
| 32 bool _hasStrictArrow(Expression expression) { | 53 bool _hasStrictArrow(Expression expression) { |
| 33 var element = _getKnownElement(expression); | 54 var element = _getKnownElement(expression); |
| 34 return element is FunctionElement || element is MethodElement; | 55 return element is FunctionElement || element is MethodElement; |
| 35 } | 56 } |
| 36 | 57 |
| 37 Element _getKnownElement(Expression expression) { | 58 Element _getKnownElement(Expression expression) { |
| 38 if (expression is ParenthesizedExpression) { | 59 if (expression is ParenthesizedExpression) { |
| 39 expression = (expression as ParenthesizedExpression).expression; | 60 expression = (expression as ParenthesizedExpression).expression; |
| 40 } | 61 } |
| 41 if (expression is FunctionExpression) { | 62 if (expression is FunctionExpression) { |
| (...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 180 } | 201 } |
| 181 } | 202 } |
| 182 } | 203 } |
| 183 | 204 |
| 184 /// Analyzer checks boolean conversions, but we need to check too, because | 205 /// Analyzer checks boolean conversions, but we need to check too, because |
| 185 /// it uses the default assignability rules that allow `dynamic` and `Object` | 206 /// it uses the default assignability rules that allow `dynamic` and `Object` |
| 186 /// to be assigned to bool with no message. | 207 /// to be assigned to bool with no message. |
| 187 void checkBoolean(Expression expr) => | 208 void checkBoolean(Expression expr) => |
| 188 checkAssignment(expr, typeProvider.boolType); | 209 checkAssignment(expr, typeProvider.boolType); |
| 189 | 210 |
| 190 void checkFunctionApplication( | 211 void checkFunctionApplication(InvocationExpression node) { |
| 191 Expression node, Expression f, ArgumentList list) { | 212 var ft = _getTypeAsCaller(node); |
| 192 if (_isDynamicCall(f)) { | 213 |
| 214 if (_isDynamicCall(node, ft)) { |
| 193 // If f is Function and this is a method invocation, we should have | 215 // If f is Function and this is a method invocation, we should have |
| 194 // gotten an analyzer error, so no need to issue another error. | 216 // gotten an analyzer error, so no need to issue another error. |
| 195 _recordDynamicInvoke(node, f); | 217 _recordDynamicInvoke(node, node.function); |
| 196 } else { | 218 } else { |
| 197 checkArgumentList(list, _getTypeAsCaller(f)); | 219 checkArgumentList(node.argumentList, ft); |
| 198 } | 220 } |
| 199 } | 221 } |
| 200 | 222 |
| 201 DartType getType(TypeName name) { | 223 DartType getType(TypeName name) { |
| 202 return (name == null) ? DynamicTypeImpl.instance : name.type; | 224 return (name == null) ? DynamicTypeImpl.instance : name.type; |
| 203 } | 225 } |
| 204 | 226 |
| 205 void reset() { | 227 void reset() { |
| 206 _failure = false; | 228 _failure = false; |
| 207 } | 229 } |
| 208 | 230 |
| 209 @override | 231 @override |
| 210 void visitAsExpression(AsExpression node) { | 232 void visitAsExpression(AsExpression node) { |
| 211 // We could do the same check as the IsExpression below, but that is | 233 // We could do the same check as the IsExpression below, but that is |
| 212 // potentially too conservative. Instead, at runtime, we must fail hard | 234 // potentially too conservative. Instead, at runtime, we must fail hard |
| 213 // if the Dart as and the DDC as would return different values. | 235 // if the Dart as and the DDC as would return different values. |
| 214 node.visitChildren(this); | 236 node.visitChildren(this); |
| 215 } | 237 } |
| 216 | 238 |
| 217 @override | 239 @override |
| 218 void visitAssignmentExpression(AssignmentExpression node) { | 240 void visitAssignmentExpression(AssignmentExpression node) { |
| 219 Token operator = node.operator; | 241 Token operator = node.operator; |
| 220 TokenType operatorType = operator.type; | 242 TokenType operatorType = operator.type; |
| 221 if (operatorType == TokenType.EQ || | 243 if (operatorType == TokenType.EQ || |
| 222 operatorType == TokenType.QUESTION_QUESTION_EQ) { | 244 operatorType == TokenType.QUESTION_QUESTION_EQ) { |
| 223 DartType staticType = _getStaticType(node.leftHandSide); | 245 DartType staticType = _getDefiniteType(node.leftHandSide); |
| 224 checkAssignment(node.rightHandSide, staticType); | 246 checkAssignment(node.rightHandSide, staticType); |
| 225 } else if (operatorType == TokenType.AMPERSAND_AMPERSAND_EQ || | 247 } else if (operatorType == TokenType.AMPERSAND_AMPERSAND_EQ || |
| 226 operatorType == TokenType.BAR_BAR_EQ) { | 248 operatorType == TokenType.BAR_BAR_EQ) { |
| 227 checkAssignment(node.leftHandSide, typeProvider.boolType); | 249 checkAssignment(node.leftHandSide, typeProvider.boolType); |
| 228 checkAssignment(node.rightHandSide, typeProvider.boolType); | 250 checkAssignment(node.rightHandSide, typeProvider.boolType); |
| 229 } else { | 251 } else { |
| 230 _checkCompoundAssignment(node); | 252 _checkCompoundAssignment(node); |
| 231 } | 253 } |
| 232 node.visitChildren(this); | 254 node.visitChildren(this); |
| 233 } | 255 } |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 370 @override | 392 @override |
| 371 void visitForEachStatement(ForEachStatement node) { | 393 void visitForEachStatement(ForEachStatement node) { |
| 372 var loopVariable = node.identifier ?? node.loopVariable?.identifier; | 394 var loopVariable = node.identifier ?? node.loopVariable?.identifier; |
| 373 | 395 |
| 374 // Safely handle malformed statements. | 396 // Safely handle malformed statements. |
| 375 if (loopVariable != null) { | 397 if (loopVariable != null) { |
| 376 // Find the element type of the sequence. | 398 // Find the element type of the sequence. |
| 377 var sequenceInterface = node.awaitKeyword != null | 399 var sequenceInterface = node.awaitKeyword != null |
| 378 ? typeProvider.streamType | 400 ? typeProvider.streamType |
| 379 : typeProvider.iterableType; | 401 : typeProvider.iterableType; |
| 380 var iterableType = _getStaticType(node.iterable); | 402 var iterableType = _getDefiniteType(node.iterable); |
| 381 var elementType = | 403 var elementType = |
| 382 rules.mostSpecificTypeArgument(iterableType, sequenceInterface); | 404 rules.mostSpecificTypeArgument(iterableType, sequenceInterface); |
| 383 | 405 |
| 384 // If the sequence is not an Iterable (or Stream for await for) but is a | 406 // If the sequence is not an Iterable (or Stream for await for) but is a |
| 385 // supertype of it, do an implicit downcast to Iterable<dynamic>. Then | 407 // supertype of it, do an implicit downcast to Iterable<dynamic>. Then |
| 386 // we'll do a separate cast of the dynamic element to the variable's type. | 408 // we'll do a separate cast of the dynamic element to the variable's type. |
| 387 if (elementType == null) { | 409 if (elementType == null) { |
| 388 var sequenceType = | 410 var sequenceType = |
| 389 sequenceInterface.instantiate([DynamicTypeImpl.instance]); | 411 sequenceInterface.instantiate([DynamicTypeImpl.instance]); |
| 390 | 412 |
| 391 if (rules.isSubtypeOf(sequenceType, iterableType)) { | 413 if (rules.isSubtypeOf(sequenceType, iterableType)) { |
| 392 _recordImplicitCast(node.iterable, iterableType, sequenceType); | 414 _recordImplicitCast(node.iterable, iterableType, sequenceType); |
| 393 elementType = DynamicTypeImpl.instance; | 415 elementType = DynamicTypeImpl.instance; |
| 394 } | 416 } |
| 395 } | 417 } |
| 396 | 418 |
| 397 // If the sequence doesn't implement the interface at all, [ErrorVerifier] | 419 // If the sequence doesn't implement the interface at all, [ErrorVerifier] |
| 398 // will report the error, so ignore it here. | 420 // will report the error, so ignore it here. |
| 399 if (elementType != null) { | 421 if (elementType != null) { |
| 400 // Insert a cast from the sequence's element type to the loop variable's | 422 // Insert a cast from the sequence's element type to the loop variable's |
| 401 // if needed. | 423 // if needed. |
| 402 _checkDowncast(loopVariable, _getStaticType(loopVariable), | 424 _checkDowncast(loopVariable, _getDefiniteType(loopVariable), |
| 403 from: elementType); | 425 from: elementType); |
| 404 } | 426 } |
| 405 } | 427 } |
| 406 | 428 |
| 407 node.visitChildren(this); | 429 node.visitChildren(this); |
| 408 } | 430 } |
| 409 | 431 |
| 410 @override | 432 @override |
| 411 void visitForStatement(ForStatement node) { | 433 void visitForStatement(ForStatement node) { |
| 412 if (node.condition != null) { | 434 if (node.condition != null) { |
| 413 checkBoolean(node.condition); | 435 checkBoolean(node.condition); |
| 414 } | 436 } |
| 415 node.visitChildren(this); | 437 node.visitChildren(this); |
| 416 } | 438 } |
| 417 | 439 |
| 418 @override | 440 @override |
| 419 void visitFunctionExpression(FunctionExpression node) { | 441 void visitFunctionExpression(FunctionExpression node) { |
| 420 _checkForUnsafeBlockClosureInference(node); | 442 _checkForUnsafeBlockClosureInference(node); |
| 421 super.visitFunctionExpression(node); | 443 super.visitFunctionExpression(node); |
| 422 } | 444 } |
| 423 | 445 |
| 424 @override | 446 @override |
| 425 void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { | 447 void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
| 426 checkFunctionApplication(node, node.function, node.argumentList); | 448 checkFunctionApplication(node); |
| 427 node.visitChildren(this); | 449 node.visitChildren(this); |
| 428 } | 450 } |
| 429 | 451 |
| 430 @override | 452 @override |
| 431 void visitIfStatement(IfStatement node) { | 453 void visitIfStatement(IfStatement node) { |
| 432 checkBoolean(node.condition); | 454 checkBoolean(node.condition); |
| 433 node.visitChildren(this); | 455 node.visitChildren(this); |
| 434 } | 456 } |
| 435 | 457 |
| 436 @override | 458 @override |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 543 // | 565 // |
| 544 // ... from case like: | 566 // ... from case like: |
| 545 // | 567 // |
| 546 // SomeType s; | 568 // SomeType s; |
| 547 // s.someDynamicField(...); // static get, followed by dynamic call. | 569 // s.someDynamicField(...); // static get, followed by dynamic call. |
| 548 // | 570 // |
| 549 // The first case is handled here, the second case is handled below when | 571 // The first case is handled here, the second case is handled below when |
| 550 // we call [checkFunctionApplication]. | 572 // we call [checkFunctionApplication]. |
| 551 setIsDynamicInvoke(node.methodName, true); | 573 setIsDynamicInvoke(node.methodName, true); |
| 552 } else { | 574 } else { |
| 553 checkFunctionApplication(node, node.methodName, node.argumentList); | 575 checkFunctionApplication(node); |
| 554 } | 576 } |
| 555 node.visitChildren(this); | 577 node.visitChildren(this); |
| 556 } | 578 } |
| 557 | 579 |
| 558 @override | 580 @override |
| 559 void visitPostfixExpression(PostfixExpression node) { | 581 void visitPostfixExpression(PostfixExpression node) { |
| 560 _checkUnary(node, node.staticElement); | 582 _checkUnary(node, node.staticElement); |
| 561 node.visitChildren(this); | 583 node.visitChildren(this); |
| 562 } | 584 } |
| 563 | 585 |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 673 } else { | 695 } else { |
| 674 // Sanity check the operator. | 696 // Sanity check the operator. |
| 675 assert(methodElement.isOperator); | 697 assert(methodElement.isOperator); |
| 676 var functionType = methodElement.type; | 698 var functionType = methodElement.type; |
| 677 var paramTypes = functionType.normalParameterTypes; | 699 var paramTypes = functionType.normalParameterTypes; |
| 678 assert(paramTypes.length == 1); | 700 assert(paramTypes.length == 1); |
| 679 assert(functionType.namedParameterTypes.isEmpty); | 701 assert(functionType.namedParameterTypes.isEmpty); |
| 680 assert(functionType.optionalParameterTypes.isEmpty); | 702 assert(functionType.optionalParameterTypes.isEmpty); |
| 681 | 703 |
| 682 // Check the LHS type. | 704 // Check the LHS type. |
| 683 var rhsType = _getStaticType(expr.rightHandSide); | 705 var rhsType = _getDefiniteType(expr.rightHandSide); |
| 684 var lhsType = _getStaticType(expr.leftHandSide); | 706 var lhsType = _getDefiniteType(expr.leftHandSide); |
| 685 var returnType = rules.refineBinaryExpressionType( | 707 var returnType = rules.refineBinaryExpressionType( |
| 686 typeProvider, lhsType, op, rhsType, functionType.returnType); | 708 typeProvider, lhsType, op, rhsType, functionType.returnType); |
| 687 | 709 |
| 688 if (!rules.isSubtypeOf(returnType, lhsType)) { | 710 if (!rules.isSubtypeOf(returnType, lhsType)) { |
| 689 final numType = typeProvider.numType; | 711 final numType = typeProvider.numType; |
| 690 // TODO(jmesserly): this seems to duplicate logic in StaticTypeAnalyzer. | 712 // TODO(jmesserly): this seems to duplicate logic in StaticTypeAnalyzer. |
| 691 // Try to fix up the numerical case if possible. | 713 // Try to fix up the numerical case if possible. |
| 692 if (rules.isSubtypeOf(lhsType, numType) && | 714 if (rules.isSubtypeOf(lhsType, numType) && |
| 693 rules.isSubtypeOf(lhsType, rhsType)) { | 715 rules.isSubtypeOf(lhsType, rhsType)) { |
| 694 // This is also slightly different from spec, but allows us to keep | 716 // This is also slightly different from spec, but allows us to keep |
| (...skipping 16 matching lines...) Expand all Loading... |
| 711 } | 733 } |
| 712 | 734 |
| 713 /// Records a [DownCast] of [expr] from [from] to [to], if there is one. | 735 /// Records a [DownCast] of [expr] from [from] to [to], if there is one. |
| 714 /// | 736 /// |
| 715 /// If [from] is omitted, uses the static type of [expr]. | 737 /// If [from] is omitted, uses the static type of [expr]. |
| 716 /// | 738 /// |
| 717 /// If [expr] does not require a downcast because it is not related to [to] | 739 /// If [expr] does not require a downcast because it is not related to [to] |
| 718 /// or is already a subtype of it, does nothing. | 740 /// or is already a subtype of it, does nothing. |
| 719 void _checkDowncast(Expression expr, DartType to, {DartType from}) { | 741 void _checkDowncast(Expression expr, DartType to, {DartType from}) { |
| 720 if (from == null) { | 742 if (from == null) { |
| 721 from = _getStaticType(expr); | 743 from = _getDefiniteType(expr); |
| 722 } | 744 } |
| 723 | 745 |
| 724 // We can use anything as void. | 746 // We can use anything as void. |
| 725 if (to.isVoid) return; | 747 if (to.isVoid) return; |
| 726 | 748 |
| 727 // fromT <: toT, no coercion needed. | 749 // fromT <: toT, no coercion needed. |
| 728 if (rules.isSubtypeOf(from, to)) return; | 750 if (rules.isSubtypeOf(from, to)) return; |
| 729 | 751 |
| 730 // TODO(vsm): We can get rid of the second clause if we disallow | 752 // TODO(vsm): We can get rid of the second clause if we disallow |
| 731 // all sideways casts - see TODO below. | 753 // all sideways casts - see TODO below. |
| (...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 961 if (type.isDynamic) { | 983 if (type.isDynamic) { |
| 962 return type; | 984 return type; |
| 963 } else if (type is InterfaceType && type.element == expectedType.element) { | 985 } else if (type is InterfaceType && type.element == expectedType.element) { |
| 964 return type.typeArguments[0]; | 986 return type.typeArguments[0]; |
| 965 } else { | 987 } else { |
| 966 // Malformed type - fallback on analyzer error. | 988 // Malformed type - fallback on analyzer error. |
| 967 return null; | 989 return null; |
| 968 } | 990 } |
| 969 } | 991 } |
| 970 | 992 |
| 971 DartType _getStaticType(Expression expr) { | 993 DartType _getDefiniteType(Expression expr) => |
| 972 DartType t = expr.staticType ?? DynamicTypeImpl.instance; | 994 getDefiniteType(expr, rules, typeProvider); |
| 973 | |
| 974 // Remove fuzzy arrow if possible. | |
| 975 if (t is FunctionType && _hasStrictArrow(expr)) { | |
| 976 t = rules.functionTypeToConcreteType(typeProvider, t); | |
| 977 } | |
| 978 | |
| 979 return t; | |
| 980 } | |
| 981 | 995 |
| 982 /// Given an expression, return its type assuming it is | 996 /// Given an expression, return its type assuming it is |
| 983 /// in the caller position of a call (that is, accounting | 997 /// in the caller position of a call (that is, accounting |
| 984 /// for the possibility of a call method). Returns null | 998 /// for the possibility of a call method). Returns null |
| 985 /// if expression is not statically callable. | 999 /// if expression is not statically callable. |
| 986 FunctionType _getTypeAsCaller(Expression node) { | 1000 FunctionType _getTypeAsCaller(InvocationExpression node) { |
| 987 DartType t = _getStaticType(node); | 1001 DartType type = node.staticInvokeType; |
| 988 if (node is SimpleIdentifier) { | 1002 if (type is FunctionType) { |
| 989 Expression parent = node.parent; | 1003 return type; |
| 990 if (parent is MethodInvocation) { | 1004 } else if (type is InterfaceType) { |
| 991 t = parent.staticInvokeType; | 1005 return rules.getCallMethodType(type); |
| 992 } | |
| 993 } | |
| 994 if (t is InterfaceType) { | |
| 995 return rules.getCallMethodType(t); | |
| 996 } | |
| 997 if (t is FunctionType) { | |
| 998 return t; | |
| 999 } | 1006 } |
| 1000 return null; | 1007 return null; |
| 1001 } | 1008 } |
| 1002 | 1009 |
| 1003 /// Returns `true` if the expression is a dynamic function call or method | 1010 /// Returns `true` if the expression is a dynamic function call or method |
| 1004 /// invocation. | 1011 /// invocation. |
| 1005 bool _isDynamicCall(Expression call) { | 1012 bool _isDynamicCall(InvocationExpression call, FunctionType ft) { |
| 1006 var ft = _getTypeAsCaller(call); | |
| 1007 // TODO(leafp): This will currently return true if t is Function | 1013 // TODO(leafp): This will currently return true if t is Function |
| 1008 // This is probably the most correct thing to do for now, since | 1014 // This is probably the most correct thing to do for now, since |
| 1009 // this code is also used by the back end. Maybe revisit at some | 1015 // this code is also used by the back end. Maybe revisit at some |
| 1010 // point? | 1016 // point? |
| 1011 if (ft == null) return true; | 1017 if (ft == null) return true; |
| 1012 // Dynamic as the parameter type is treated as bottom. A function with | 1018 // Dynamic as the parameter type is treated as bottom. A function with |
| 1013 // a dynamic parameter type requires a dynamic call in general. | 1019 // a dynamic parameter type requires a dynamic call in general. |
| 1014 // However, as an optimization, if we have an original definition, we know | 1020 // However, as an optimization, if we have an original definition, we know |
| 1015 // dynamic is reified as Object - in this case a regular call is fine. | 1021 // dynamic is reified as Object - in this case a regular call is fine. |
| 1016 if (_hasStrictArrow(call)) { | 1022 if (_hasStrictArrow(call.function)) { |
| 1017 return false; | 1023 return false; |
| 1018 } | 1024 } |
| 1019 return rules.anyParameterType(ft, (pt) => pt.isDynamic); | 1025 return rules.anyParameterType(ft, (pt) => pt.isDynamic); |
| 1020 } | 1026 } |
| 1021 | 1027 |
| 1022 bool _isObjectGetter(Expression target, SimpleIdentifier id) { | 1028 bool _isObjectGetter(Expression target, SimpleIdentifier id) { |
| 1023 PropertyAccessorElement element = | 1029 PropertyAccessorElement element = |
| 1024 typeProvider.objectType.element.getGetter(id.name); | 1030 typeProvider.objectType.element.getGetter(id.name); |
| 1025 return (element != null && !element.isStatic); | 1031 return (element != null && !element.isStatic); |
| 1026 } | 1032 } |
| (...skipping 471 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1498 var visited = new Set<InterfaceType>(); | 1504 var visited = new Set<InterfaceType>(); |
| 1499 do { | 1505 do { |
| 1500 visited.add(current); | 1506 visited.add(current); |
| 1501 current.mixins.reversed.forEach( | 1507 current.mixins.reversed.forEach( |
| 1502 (m) => _checkIndividualOverridesFromClass(node, m, seen, true)); | 1508 (m) => _checkIndividualOverridesFromClass(node, m, seen, true)); |
| 1503 _checkIndividualOverridesFromClass(node, current.superclass, seen, true); | 1509 _checkIndividualOverridesFromClass(node, current.superclass, seen, true); |
| 1504 current = current.superclass; | 1510 current = current.superclass; |
| 1505 } while (!current.isObject && !visited.contains(current)); | 1511 } while (!current.isObject && !visited.contains(current)); |
| 1506 } | 1512 } |
| 1507 } | 1513 } |
| OLD | NEW |