Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(179)

Side by Side Diff: pkg/analyzer/lib/src/task/strong/checker.dart

Issue 2199323002: Initial implementation for lazy compound assignment operators (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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/token.dart';
12 import 'package:analyzer/dart/ast/visitor.dart'; 13 import 'package:analyzer/dart/ast/visitor.dart';
13 import 'package:analyzer/dart/element/element.dart'; 14 import 'package:analyzer/dart/element/element.dart';
14 import 'package:analyzer/dart/element/type.dart'; 15 import 'package:analyzer/dart/element/type.dart';
15 import 'package:analyzer/src/dart/element/type.dart'; 16 import 'package:analyzer/src/dart/element/type.dart';
16 import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; 17 import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
17 import 'package:analyzer/src/generated/error.dart' show StrongModeCode; 18 import 'package:analyzer/src/generated/error.dart' show StrongModeCode;
18 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; 19 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
19 import 'package:analyzer/src/generated/type_system.dart'; 20 import 'package:analyzer/src/generated/type_system.dart';
20 21
21 import 'ast_properties.dart'; 22 import 'ast_properties.dart';
22 23
24 bool isKnownFunction(Expression expression) {
25 Element element = null;
26 if (expression is FunctionExpression) {
27 return true;
28 } else if (expression is PropertyAccess) {
29 element = expression.propertyName.staticElement;
30 } else if (expression is Identifier) {
31 element = expression.staticElement;
32 }
33 // First class functions and static methods, where we know the original
34 // declaration, will have an exact type, so we know a downcast will fail.
35 return element is FunctionElement ||
36 element is MethodElement && element.isStatic;
37 }
38
23 DartType _elementType(Element e) { 39 DartType _elementType(Element e) {
24 if (e == null) { 40 if (e == null) {
25 // Malformed code - just return dynamic. 41 // Malformed code - just return dynamic.
26 return DynamicTypeImpl.instance; 42 return DynamicTypeImpl.instance;
27 } 43 }
28 return (e as dynamic).type; 44 return (e as dynamic).type;
29 } 45 }
30 46
47 // Return the field on type corresponding to member, or null if none
48 // exists or the "field" is actually a getter/setter.
scheglov 2016/08/02 15:25:03 Make this a documentation comment?
Brian Wilkerson 2016/08/02 15:33:53 Or delete it. I didn't add this comment, it was mo
31 PropertyInducingElement _getMemberField( 49 PropertyInducingElement _getMemberField(
32 InterfaceType type, PropertyAccessorElement member) { 50 InterfaceType type, PropertyAccessorElement member) {
33 String memberName = member.name; 51 String memberName = member.name;
34 PropertyInducingElement field; 52 PropertyInducingElement field;
35 if (member.isGetter) { 53 if (member.isGetter) {
36 // The subclass member is an explicit getter or a field 54 // The subclass member is an explicit getter or a field
37 // - lookup the getter on the superclass. 55 // - lookup the getter on the superclass.
38 var getter = type.getGetter(memberName); 56 var getter = type.getGetter(memberName);
39 if (getter == null || getter.isStatic) return null; 57 if (getter == null || getter.isStatic) return null;
40 field = getter.variable; 58 field = getter.variable;
41 } else if (!member.isSynthetic) { 59 } else if (!member.isSynthetic) {
42 // The subclass member is an explicit setter 60 // The subclass member is an explicit setter
43 // - lookup the setter on the superclass. 61 // - lookup the setter on the superclass.
44 // Note: an implicit (synthetic) setter would have already been flagged on 62 // Note: an implicit (synthetic) setter would have already been flagged on
45 // the getter above. 63 // the getter above.
46 var setter = type.getSetter(memberName); 64 var setter = type.getSetter(memberName);
47 if (setter == null || setter.isStatic) return null; 65 if (setter == null || setter.isStatic) return null;
48 field = setter.variable; 66 field = setter.variable;
49 } else { 67 } else {
50 return null; 68 return null;
51 } 69 }
52 if (field.isSynthetic) return null; 70 if (field.isSynthetic) return null;
53 return field; 71 return field;
54 } 72 }
55 73
56 // Return the field on type corresponding to member, or null if none
57 // exists or the "field" is actually a getter/setter.
58 /// Looks up the declaration that matches [member] in [type] and returns it's 74 /// Looks up the declaration that matches [member] in [type] and returns it's
59 /// declared type. 75 /// declared type.
60 FunctionType _getMemberType(InterfaceType type, ExecutableElement member) => 76 FunctionType _getMemberType(InterfaceType type, ExecutableElement member) =>
61 _memberTypeGetter(member)(type); 77 _memberTypeGetter(member)(type);
62 78
63 _MemberTypeGetter _memberTypeGetter(ExecutableElement member) { 79 _MemberTypeGetter _memberTypeGetter(ExecutableElement member) {
64 String memberName = member.name; 80 String memberName = member.name;
65 final isGetter = member is PropertyAccessorElement && member.isGetter; 81 final isGetter = member is PropertyAccessorElement && member.isGetter;
66 final isSetter = member is PropertyAccessorElement && member.isSetter; 82 final isSetter = member is PropertyAccessorElement && member.isSetter;
67 83
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
169 185
170 DartType getType(TypeName name) { 186 DartType getType(TypeName name) {
171 return (name == null) ? DynamicTypeImpl.instance : name.type; 187 return (name == null) ? DynamicTypeImpl.instance : name.type;
172 } 188 }
173 189
174 void reset() { 190 void reset() {
175 _failure = false; 191 _failure = false;
176 } 192 }
177 193
178 @override 194 @override
179 void visitCompilationUnit(CompilationUnit node) {
180 _hasImplicitCasts = false;
181 node.visitChildren(this);
182 setHasImplicitCasts(node, _hasImplicitCasts);
183 }
184
185 @override
186 void visitAsExpression(AsExpression node) { 195 void visitAsExpression(AsExpression node) {
187 // We could do the same check as the IsExpression below, but that is 196 // We could do the same check as the IsExpression below, but that is
188 // potentially too conservative. Instead, at runtime, we must fail hard 197 // potentially too conservative. Instead, at runtime, we must fail hard
189 // if the Dart as and the DDC as would return different values. 198 // if the Dart as and the DDC as would return different values.
190 node.visitChildren(this); 199 node.visitChildren(this);
191 } 200 }
192 201
193 @override 202 @override
194 void visitAssignmentExpression(AssignmentExpression node) { 203 void visitAssignmentExpression(AssignmentExpression node) {
195 var token = node.operator; 204 Token operator = node.operator;
196 if (token.type == TokenType.EQ || 205 TokenType operatorType = operator.type;
197 token.type == TokenType.QUESTION_QUESTION_EQ) { 206 if (operatorType == TokenType.EQ ||
207 operatorType == TokenType.QUESTION_QUESTION_EQ) {
198 DartType staticType = _getStaticType(node.leftHandSide); 208 DartType staticType = _getStaticType(node.leftHandSide);
199 checkAssignment(node.rightHandSide, staticType); 209 checkAssignment(node.rightHandSide, staticType);
210 } else if (operatorType == TokenType.AMPERSAND_AMPERSAND_EQ ||
211 operatorType == TokenType.BAR_BAR_EQ) {
212 checkAssignment(node.leftHandSide, typeProvider.boolType);
213 checkAssignment(node.rightHandSide, typeProvider.boolType);
200 } else { 214 } else {
201 _checkCompoundAssignment(node); 215 _checkCompoundAssignment(node);
202 } 216 }
203 node.visitChildren(this); 217 node.visitChildren(this);
204 } 218 }
205 219
206 @override 220 @override
207 void visitBinaryExpression(BinaryExpression node) { 221 void visitBinaryExpression(BinaryExpression node) {
208 var op = node.operator; 222 var op = node.operator;
209 if (op.isUserDefinableOperator) { 223 if (op.isUserDefinableOperator) {
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
252 super.visitClassDeclaration(node); 266 super.visitClassDeclaration(node);
253 } 267 }
254 268
255 @override 269 @override
256 void visitComment(Comment node) { 270 void visitComment(Comment node) {
257 // skip, no need to do typechecking inside comments (they may contain 271 // skip, no need to do typechecking inside comments (they may contain
258 // comment references which would require resolution). 272 // comment references which would require resolution).
259 } 273 }
260 274
261 @override 275 @override
276 void visitCompilationUnit(CompilationUnit node) {
277 _hasImplicitCasts = false;
278 node.visitChildren(this);
279 setHasImplicitCasts(node, _hasImplicitCasts);
280 }
281
282 @override
262 void visitConditionalExpression(ConditionalExpression node) { 283 void visitConditionalExpression(ConditionalExpression node) {
263 checkBoolean(node.condition); 284 checkBoolean(node.condition);
264 node.visitChildren(this); 285 node.visitChildren(this);
265 } 286 }
266 287
267 /// Check constructor declaration to ensure correct super call placement. 288 /// Check constructor declaration to ensure correct super call placement.
268 @override 289 @override
269 void visitConstructorDeclaration(ConstructorDeclaration node) { 290 void visitConstructorDeclaration(ConstructorDeclaration node) {
270 node.visitChildren(this); 291 node.visitChildren(this);
271 292
(...skipping 574 matching lines...) Expand 10 before | Expand all | Expand 10 after
846 op.type == TokenType.MINUS_MINUS) { 867 op.type == TokenType.MINUS_MINUS) {
847 if (_isDynamicTarget(node.operand)) { 868 if (_isDynamicTarget(node.operand)) {
848 _recordDynamicInvoke(node, node.operand); 869 _recordDynamicInvoke(node, node.operand);
849 } 870 }
850 // For ++ and --, even if it is not dynamic, we still need to check 871 // For ++ and --, even if it is not dynamic, we still need to check
851 // that the user defined method accepts an `int` as the RHS. 872 // that the user defined method accepts an `int` as the RHS.
852 // We assume Analyzer has done this already. 873 // We assume Analyzer has done this already.
853 } 874 }
854 } 875 }
855 876
856 /// Records an implicit cast for the [expression] from [fromType] to [toType].
857 ///
858 /// This will emit the appropriate error/warning/hint message as well as mark
859 /// the AST node.
860 void _recordImplicitCast(
861 Expression expression, DartType fromType, DartType toType) {
862 // toT <:_R fromT => to <: fromT
863 // NB: classes with call methods are subtypes of function
864 // types, but the function type is not assignable to the class
865 assert(toType.isSubtypeOf(fromType) || fromType.isAssignableTo(toType));
866
867 // Inference "casts":
868 if (expression is Literal || expression is FunctionExpression) {
869 // fromT should be an exact type - this will almost certainly fail at
870 // runtime.
871 _recordMessage(expression, StrongModeCode.STATIC_TYPE_ERROR,
872 [expression, fromType, toType]);
873 return;
874 }
875
876 if (expression is InstanceCreationExpression) {
877 ConstructorElement e = expression.staticElement;
878 if (e == null || !e.isFactory) {
879 // fromT should be an exact type - this will almost certainly fail at
880 // runtime.
881
882 _recordMessage(expression, StrongModeCode.STATIC_TYPE_ERROR,
883 [expression, fromType, toType]);
884 return;
885 }
886 }
887
888 if (isKnownFunction(expression)) {
889 _recordMessage(expression, StrongModeCode.STATIC_TYPE_ERROR,
890 [expression, fromType, toType]);
891 return;
892 }
893
894 // TODO(vsm): Change this to an assert when we have generic methods and
895 // fix TypeRules._coerceTo to disallow implicit sideways casts.
896 bool downCastComposite = false;
897 if (!rules.isSubtypeOf(toType, fromType)) {
898 assert(toType.isSubtypeOf(fromType) || fromType.isAssignableTo(toType));
899 downCastComposite = true;
900 }
901
902 // Composite cast: these are more likely to fail.
903 if (!rules.isGroundType(toType)) {
904 // This cast is (probably) due to our different treatment of dynamic.
905 // It may be more likely to fail at runtime.
906 if (fromType is InterfaceType) {
907 // For class types, we'd like to allow non-generic down casts, e.g.,
908 // Iterable<T> to List<T>. The intuition here is that raw (generic)
909 // casts are problematic, and we should complain about those.
910 var typeArgs = fromType.typeArguments;
911 downCastComposite =
912 typeArgs.isEmpty || typeArgs.any((t) => t.isDynamic);
913 } else {
914 downCastComposite = true;
915 }
916 }
917
918 var parent = expression.parent;
919 ErrorCode errorCode;
920 if (downCastComposite) {
921 errorCode = StrongModeCode.DOWN_CAST_COMPOSITE;
922 } else if (fromType.isDynamic) {
923 errorCode = StrongModeCode.DYNAMIC_CAST;
924 } else if (parent is VariableDeclaration &&
925 parent.initializer == expression) {
926 errorCode = StrongModeCode.ASSIGNMENT_CAST;
927 } else {
928 errorCode = StrongModeCode.DOWN_CAST_IMPLICIT;
929 }
930
931 _recordMessage(expression, errorCode, [fromType, toType]);
932 setImplicitCast(expression, toType);
933 _hasImplicitCasts = true;
934 }
935
936 // Produce a coercion which coerces something of type fromT
937 // to something of type toT.
938 // Returns the error coercion if the types cannot be coerced
939 // according to our current criteria.
940 /// Gets the expected return type of the given function [body], either from 877 /// Gets the expected return type of the given function [body], either from
941 /// a normal return/yield, or from a yield*. 878 /// a normal return/yield, or from a yield*.
942 DartType _getExpectedReturnType(FunctionBody body, {bool yieldStar: false}) { 879 DartType _getExpectedReturnType(FunctionBody body, {bool yieldStar: false}) {
943 FunctionType functionType; 880 FunctionType functionType;
944 var parent = body.parent; 881 var parent = body.parent;
945 if (parent is Declaration) { 882 if (parent is Declaration) {
946 functionType = _elementType(parent.element); 883 functionType = _elementType(parent.element);
947 } else { 884 } else {
948 assert(parent is FunctionExpression); 885 assert(parent is FunctionExpression);
949 functionType = 886 functionType =
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
984 if (type.isDynamic) { 921 if (type.isDynamic) {
985 return type; 922 return type;
986 } else if (type is InterfaceType && type.element == expectedType.element) { 923 } else if (type is InterfaceType && type.element == expectedType.element) {
987 return type.typeArguments[0]; 924 return type.typeArguments[0];
988 } else { 925 } else {
989 // Malformed type - fallback on analyzer error. 926 // Malformed type - fallback on analyzer error.
990 return null; 927 return null;
991 } 928 }
992 } 929 }
993 930
931 // Produce a coercion which coerces something of type fromT
932 // to something of type toT.
933 // Returns the error coercion if the types cannot be coerced
934 // according to our current criteria.
scheglov 2016/08/02 15:25:03 Make this a documentation comment?
Brian Wilkerson 2016/08/02 15:33:53 I didn't write this comment either, it was also mo
994 DartType _getStaticType(Expression expr) { 935 DartType _getStaticType(Expression expr) {
995 DartType t = expr.staticType ?? DynamicTypeImpl.instance; 936 DartType t = expr.staticType ?? DynamicTypeImpl.instance;
996 937
997 // Remove fuzzy arrow if possible. 938 // Remove fuzzy arrow if possible.
998 if (t is FunctionType && isKnownFunction(expr)) { 939 if (t is FunctionType && isKnownFunction(expr)) {
999 t = rules.functionTypeToConcreteType(typeProvider, t); 940 t = rules.functionTypeToConcreteType(typeProvider, t);
1000 } 941 }
1001 942
1002 return t; 943 return t;
1003 } 944 }
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
1078 } 1019 }
1079 1020
1080 void _recordDynamicInvoke(AstNode node, Expression target) { 1021 void _recordDynamicInvoke(AstNode node, Expression target) {
1081 _recordMessage(node, StrongModeCode.DYNAMIC_INVOKE, [node]); 1022 _recordMessage(node, StrongModeCode.DYNAMIC_INVOKE, [node]);
1082 // TODO(jmesserly): we may eventually want to record if the whole operation 1023 // TODO(jmesserly): we may eventually want to record if the whole operation
1083 // (node) was dynamic, rather than the target, but this is an easier fit 1024 // (node) was dynamic, rather than the target, but this is an easier fit
1084 // with what we used to do. 1025 // with what we used to do.
1085 setIsDynamicInvoke(target, true); 1026 setIsDynamicInvoke(target, true);
1086 } 1027 }
1087 1028
1029 /// Records an implicit cast for the [expression] from [fromType] to [toType].
1030 ///
1031 /// This will emit the appropriate error/warning/hint message as well as mark
1032 /// the AST node.
1033 void _recordImplicitCast(
1034 Expression expression, DartType fromType, DartType toType) {
1035 // toT <:_R fromT => to <: fromT
1036 // NB: classes with call methods are subtypes of function
1037 // types, but the function type is not assignable to the class
1038 assert(toType.isSubtypeOf(fromType) || fromType.isAssignableTo(toType));
1039
1040 // Inference "casts":
1041 if (expression is Literal || expression is FunctionExpression) {
1042 // fromT should be an exact type - this will almost certainly fail at
1043 // runtime.
1044 _recordMessage(expression, StrongModeCode.STATIC_TYPE_ERROR,
1045 [expression, fromType, toType]);
1046 return;
1047 }
1048
1049 if (expression is InstanceCreationExpression) {
1050 ConstructorElement e = expression.staticElement;
1051 if (e == null || !e.isFactory) {
1052 // fromT should be an exact type - this will almost certainly fail at
1053 // runtime.
1054
1055 _recordMessage(expression, StrongModeCode.STATIC_TYPE_ERROR,
1056 [expression, fromType, toType]);
1057 return;
1058 }
1059 }
1060
1061 if (isKnownFunction(expression)) {
1062 _recordMessage(expression, StrongModeCode.STATIC_TYPE_ERROR,
1063 [expression, fromType, toType]);
1064 return;
1065 }
1066
1067 // TODO(vsm): Change this to an assert when we have generic methods and
1068 // fix TypeRules._coerceTo to disallow implicit sideways casts.
1069 bool downCastComposite = false;
1070 if (!rules.isSubtypeOf(toType, fromType)) {
1071 assert(toType.isSubtypeOf(fromType) || fromType.isAssignableTo(toType));
1072 downCastComposite = true;
1073 }
1074
1075 // Composite cast: these are more likely to fail.
1076 if (!rules.isGroundType(toType)) {
1077 // This cast is (probably) due to our different treatment of dynamic.
1078 // It may be more likely to fail at runtime.
1079 if (fromType is InterfaceType) {
1080 // For class types, we'd like to allow non-generic down casts, e.g.,
1081 // Iterable<T> to List<T>. The intuition here is that raw (generic)
1082 // casts are problematic, and we should complain about those.
1083 var typeArgs = fromType.typeArguments;
1084 downCastComposite =
1085 typeArgs.isEmpty || typeArgs.any((t) => t.isDynamic);
1086 } else {
1087 downCastComposite = true;
1088 }
1089 }
1090
1091 var parent = expression.parent;
1092 ErrorCode errorCode;
1093 if (downCastComposite) {
1094 errorCode = StrongModeCode.DOWN_CAST_COMPOSITE;
1095 } else if (fromType.isDynamic) {
1096 errorCode = StrongModeCode.DYNAMIC_CAST;
1097 } else if (parent is VariableDeclaration &&
1098 parent.initializer == expression) {
1099 errorCode = StrongModeCode.ASSIGNMENT_CAST;
1100 } else {
1101 errorCode = StrongModeCode.DOWN_CAST_IMPLICIT;
1102 }
1103
1104 _recordMessage(expression, errorCode, [fromType, toType]);
1105 setImplicitCast(expression, toType);
1106 _hasImplicitCasts = true;
1107 }
1108
1088 void _recordMessage(AstNode node, ErrorCode errorCode, List arguments) { 1109 void _recordMessage(AstNode node, ErrorCode errorCode, List arguments) {
1089 var severity = errorCode.errorSeverity; 1110 var severity = errorCode.errorSeverity;
1090 if (severity == ErrorSeverity.ERROR) _failure = true; 1111 if (severity == ErrorSeverity.ERROR) _failure = true;
1091 if (severity != ErrorSeverity.INFO || _options.strongModeHints) { 1112 if (severity != ErrorSeverity.INFO || _options.strongModeHints) {
1092 int begin = node is AnnotatedNode 1113 int begin = node is AnnotatedNode
1093 ? node.firstTokenAfterCommentAndMetadata.offset 1114 ? node.firstTokenAfterCommentAndMetadata.offset
1094 : node.offset; 1115 : node.offset;
1095 int length = node.end - begin; 1116 int length = node.end - begin;
1096 var source = (node.root as CompilationUnit).element.source; 1117 var source = (node.root as CompilationUnit).element.source;
1097 var error = 1118 var error =
1098 new AnalysisError(source, begin, length, errorCode, arguments); 1119 new AnalysisError(source, begin, length, errorCode, arguments);
1099 reporter.onError(error); 1120 reporter.onError(error);
1100 } 1121 }
1101 } 1122 }
1102 } 1123 }
1103 1124
1104 bool isKnownFunction(Expression expression) {
1105 Element element = null;
1106 if (expression is FunctionExpression) {
1107 return true;
1108 } else if (expression is PropertyAccess) {
1109 element = expression.propertyName.staticElement;
1110 } else if (expression is Identifier) {
1111 element = expression.staticElement;
1112 }
1113 // First class functions and static methods, where we know the original
1114 // declaration, will have an exact type, so we know a downcast will fail.
1115 return element is FunctionElement ||
1116 element is MethodElement && element.isStatic;
1117 }
1118
1119 /// Checks for overriding declarations of fields and methods. This is used to 1125 /// Checks for overriding declarations of fields and methods. This is used to
1120 /// check overrides between classes and superclasses, interfaces, and mixin 1126 /// check overrides between classes and superclasses, interfaces, and mixin
1121 /// applications. 1127 /// applications.
1122 class _OverrideChecker { 1128 class _OverrideChecker {
1123 final StrongTypeSystemImpl rules; 1129 final StrongTypeSystemImpl rules;
1124 final TypeProvider _typeProvider; 1130 final TypeProvider _typeProvider;
1125 final CodeChecker _checker; 1131 final CodeChecker _checker;
1126 1132
1127 _OverrideChecker(CodeChecker checker) 1133 _OverrideChecker(CodeChecker checker)
1128 : _checker = checker, 1134 : _checker = checker,
(...skipping 346 matching lines...) Expand 10 before | Expand all | Expand 10 after
1475 var visited = new Set<InterfaceType>(); 1481 var visited = new Set<InterfaceType>();
1476 do { 1482 do {
1477 visited.add(current); 1483 visited.add(current);
1478 current.mixins.reversed.forEach( 1484 current.mixins.reversed.forEach(
1479 (m) => _checkIndividualOverridesFromClass(node, m, seen, true)); 1485 (m) => _checkIndividualOverridesFromClass(node, m, seen, true));
1480 _checkIndividualOverridesFromClass(node, current.superclass, seen, true); 1486 _checkIndividualOverridesFromClass(node, current.superclass, seen, true);
1481 current = current.superclass; 1487 current = current.superclass;
1482 } while (!current.isObject && !visited.contains(current)); 1488 } while (!current.isObject && !visited.contains(current));
1483 } 1489 }
1484 } 1490 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698