| 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/src/generated/ast.dart'; | 10 import 'package:analyzer/dart/ast/ast.dart'; |
| 11 import 'package:analyzer/src/generated/element.dart'; | 11 import 'package:analyzer/dart/ast/token.dart' show TokenType; |
| 12 import 'package:analyzer/src/generated/scanner.dart' show Token, TokenType; | 12 import 'package:analyzer/dart/ast/token.dart'; |
| 13 | 13 import 'package:analyzer/dart/ast/visitor.dart'; |
| 14 import 'info.dart'; | 14 import 'package:analyzer/dart/element/element.dart'; |
| 15 import 'rules.dart'; | 15 import 'package:analyzer/dart/element/type.dart'; |
| 16 | 16 import 'package:analyzer/src/dart/element/type.dart'; |
| 17 /// Checks for overriding declarations of fields and methods. This is used to | 17 import 'package:analyzer/src/error/codes.dart' show StrongModeCode; |
| 18 /// check overrides between classes and superclasses, interfaces, and mixin | 18 import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; |
| 19 /// applications. | 19 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
| 20 class _OverrideChecker { | 20 import 'package:analyzer/src/generated/type_system.dart'; |
| 21 |
| 22 import 'ast_properties.dart'; |
| 23 |
| 24 /// Given an [expression] and a corresponding [typeSystem] and [typeProvider], |
| 25 /// gets the known static type of the expression. |
| 26 /// |
| 27 /// Normally when we ask for an expression's type, we get the type of the |
| 28 /// storage slot that would contain it. For function types, this is necessarily |
| 29 /// a "fuzzy arrow" that treats `dynamic` as bottom. However, if we're |
| 30 /// interested in the expression's own type, it can often be a "strict arrow" |
| 31 /// because we know it evaluates to a specific, concrete function, and we can |
| 32 /// treat "dynamic" as top for that case, which is more permissive. |
| 33 DartType getDefiniteType( |
| 34 Expression expression, TypeSystem typeSystem, TypeProvider typeProvider) { |
| 35 DartType type = expression.staticType ?? DynamicTypeImpl.instance; |
| 36 if (typeSystem is StrongTypeSystemImpl && |
| 37 type is FunctionType && |
| 38 _hasStrictArrow(expression)) { |
| 39 // Remove fuzzy arrow if possible. |
| 40 return typeSystem.functionTypeToConcreteType(typeProvider, type); |
| 41 } |
| 42 return type; |
| 43 } |
| 44 |
| 45 bool isKnownFunction(Expression expression) { |
| 46 var element = _getKnownElement(expression); |
| 47 // First class functions and static methods, where we know the original |
| 48 // declaration, will have an exact type, so we know a downcast will fail. |
| 49 return element is FunctionElement || |
| 50 element is MethodElement && element.isStatic; |
| 51 } |
| 52 |
| 53 DartType _elementType(Element e) { |
| 54 if (e == null) { |
| 55 // Malformed code - just return dynamic. |
| 56 return DynamicTypeImpl.instance; |
| 57 } |
| 58 return (e as dynamic).type; |
| 59 } |
| 60 |
| 61 Element _getKnownElement(Expression expression) { |
| 62 if (expression is ParenthesizedExpression) { |
| 63 expression = (expression as ParenthesizedExpression).expression; |
| 64 } |
| 65 if (expression is FunctionExpression) { |
| 66 return expression.element; |
| 67 } else if (expression is PropertyAccess) { |
| 68 return expression.propertyName.staticElement; |
| 69 } else if (expression is Identifier) { |
| 70 return expression.staticElement; |
| 71 } |
| 72 return null; |
| 73 } |
| 74 |
| 75 /// Return the field on type corresponding to member, or null if none |
| 76 /// exists or the "field" is actually a getter/setter. |
| 77 FieldElement _getMemberField( |
| 78 InterfaceType type, PropertyAccessorElement member) { |
| 79 String memberName = member.name; |
| 80 FieldElement field; |
| 81 if (member.isGetter) { |
| 82 // The subclass member is an explicit getter or a field |
| 83 // - lookup the getter on the superclass. |
| 84 var getter = type.getGetter(memberName); |
| 85 if (getter == null || getter.isStatic) return null; |
| 86 field = getter.variable; |
| 87 } else if (!member.isSynthetic) { |
| 88 // The subclass member is an explicit setter |
| 89 // - lookup the setter on the superclass. |
| 90 // Note: an implicit (synthetic) setter would have already been flagged on |
| 91 // the getter above. |
| 92 var setter = type.getSetter(memberName); |
| 93 if (setter == null || setter.isStatic) return null; |
| 94 field = setter.variable; |
| 95 } else { |
| 96 return null; |
| 97 } |
| 98 if (field.isSynthetic) return null; |
| 99 return field; |
| 100 } |
| 101 |
| 102 /// Looks up the declaration that matches [member] in [type] and returns it's |
| 103 /// declared type. |
| 104 FunctionType _getMemberType(InterfaceType type, ExecutableElement member) => |
| 105 _memberTypeGetter(member)(type); |
| 106 |
| 107 bool _hasStrictArrow(Expression expression) { |
| 108 var element = _getKnownElement(expression); |
| 109 return element is FunctionElement || element is MethodElement; |
| 110 } |
| 111 |
| 112 _MemberTypeGetter _memberTypeGetter(ExecutableElement member) { |
| 113 String memberName = member.name; |
| 114 final isGetter = member is PropertyAccessorElement && member.isGetter; |
| 115 final isSetter = member is PropertyAccessorElement && member.isSetter; |
| 116 |
| 117 FunctionType f(InterfaceType type) { |
| 118 ExecutableElement baseMethod; |
| 119 |
| 120 if (member.isPrivate) { |
| 121 var subtypeLibrary = member.library; |
| 122 var baseLibrary = type.element.library; |
| 123 if (baseLibrary != subtypeLibrary) { |
| 124 return null; |
| 125 } |
| 126 } |
| 127 |
| 128 try { |
| 129 if (isGetter) { |
| 130 assert(!isSetter); |
| 131 // Look for getter or field. |
| 132 baseMethod = type.getGetter(memberName); |
| 133 } else if (isSetter) { |
| 134 baseMethod = type.getSetter(memberName); |
| 135 } else { |
| 136 baseMethod = type.getMethod(memberName); |
| 137 } |
| 138 } catch (e) { |
| 139 // TODO(sigmund): remove this try-catch block (see issue #48). |
| 140 } |
| 141 if (baseMethod == null || baseMethod.isStatic) return null; |
| 142 return baseMethod.type; |
| 143 } |
| 144 |
| 145 return f; |
| 146 } |
| 147 |
| 148 typedef FunctionType _MemberTypeGetter(InterfaceType type); |
| 149 |
| 150 /// Checks the body of functions and properties. |
| 151 class CodeChecker extends RecursiveAstVisitor { |
| 152 final StrongTypeSystemImpl rules; |
| 153 final TypeProvider typeProvider; |
| 154 final AnalysisErrorListener reporter; |
| 155 final AnalysisOptionsImpl _options; |
| 156 _OverrideChecker _overrideChecker; |
| 157 |
| 21 bool _failure = false; | 158 bool _failure = false; |
| 22 final TypeRules _rules; | 159 bool _hasImplicitCasts; |
| 23 final AnalysisErrorListener _reporter; | 160 |
| 24 | 161 CodeChecker(TypeProvider typeProvider, StrongTypeSystemImpl rules, |
| 25 _OverrideChecker(this._rules, this._reporter); | 162 AnalysisErrorListener reporter, this._options) |
| 26 | 163 : typeProvider = typeProvider, |
| 27 void check(ClassDeclaration node) { | 164 rules = rules, |
| 28 if (node.element.type.isObject) return; | 165 reporter = reporter { |
| 29 _checkSuperOverrides(node); | 166 _overrideChecker = new _OverrideChecker(this); |
| 30 _checkMixinApplicationOverrides(node); | 167 } |
| 31 _checkAllInterfaceOverrides(node); | 168 |
| 32 } | 169 bool get failure => _failure; |
| 33 | 170 |
| 34 /// Check overrides from mixin applications themselves. For example, in: | 171 void checkArgument(Expression arg, DartType expectedType) { |
| 35 /// | 172 // Preserve named argument structure, so their immediate parent is the |
| 36 /// A extends B with E, F | 173 // method invocation. |
| 37 /// | 174 Expression baseExpression = arg is NamedExpression ? arg.expression : arg; |
| 38 /// we check: | 175 checkAssignment(baseExpression, expectedType); |
| 39 /// | 176 } |
| 40 /// B & E against B (equivalently how E overrides B) | 177 |
| 41 /// B & E & F against B & E (equivalently how F overrides both B and E) | 178 void checkArgumentList(ArgumentList node, FunctionType type) { |
| 42 void _checkMixinApplicationOverrides(ClassDeclaration node) { | 179 NodeList<Expression> list = node.arguments; |
| 43 var type = node.element.type; | 180 int len = list.length; |
| 44 var parent = type.superclass; | 181 for (int i = 0; i < len; ++i) { |
| 45 var mixins = type.mixins; | 182 Expression arg = list[i]; |
| 46 | 183 ParameterElement element = arg.staticParameterElement; |
| 47 // Check overrides from applying mixins | 184 if (element == null) { |
| 48 for (int i = 0; i < mixins.length; i++) { | 185 // We found an argument mismatch, the analyzer will report this too, |
| 49 var seen = new Set<String>(); | 186 // so no need to insert an error for this here. |
| 50 var current = mixins[i]; | 187 continue; |
| 51 var errorLocation = node.withClause.mixinTypes[i]; | 188 } |
| 52 for (int j = i - 1; j >= 0; j--) { | 189 checkArgument(arg, _elementType(element)); |
| 53 _checkIndividualOverridesFromType( | 190 } |
| 54 current, mixins[j], errorLocation, seen); | 191 } |
| 55 } | 192 |
| 56 _checkIndividualOverridesFromType(current, parent, errorLocation, seen); | 193 void checkAssignment(Expression expr, DartType type) { |
| 57 } | 194 if (expr is ParenthesizedExpression) { |
| 58 } | 195 checkAssignment(expr.expression, type); |
| 59 | 196 } else { |
| 60 /// Check overrides between a class and its superclasses and mixins. For | 197 _checkImplicitCast(expr, type); |
| 61 /// example, in: | 198 } |
| 62 /// | 199 } |
| 63 /// A extends B with E, F | 200 |
| 64 /// | 201 /// Analyzer checks boolean conversions, but we need to check too, because |
| 65 /// we check A against B, B super classes, E, and F. | 202 /// it uses the default assignability rules that allow `dynamic` and `Object` |
| 66 /// | 203 /// to be assigned to bool with no message. |
| 67 /// Internally we avoid reporting errors twice and we visit classes bottom up | 204 void checkBoolean(Expression expr) => |
| 68 /// to ensure we report the most immediate invalid override first. For | 205 checkAssignment(expr, typeProvider.boolType); |
| 69 /// example, in the following code we'll report that `Test` has an invalid | 206 |
| 70 /// override with respect to `Parent` (as opposed to an invalid override with | 207 void checkFunctionApplication(InvocationExpression node) { |
| 71 /// respect to `Grandparent`): | 208 var ft = _getTypeAsCaller(node); |
| 72 /// | 209 |
| 73 /// class Grandparent { | 210 if (_isDynamicCall(node, ft)) { |
| 74 /// m(A a) {} | 211 // If f is Function and this is a method invocation, we should have |
| 75 /// } | 212 // gotten an analyzer error, so no need to issue another error. |
| 76 /// class Parent extends Grandparent { | 213 _recordDynamicInvoke(node, node.function); |
| 77 /// m(A a) {} | 214 } else { |
| 78 /// } | 215 checkArgumentList(node.argumentList, ft); |
| 79 /// class Test extends Parent { | 216 } |
| 80 /// m(B a) {} // invalid override | 217 } |
| 81 /// } | 218 |
| 82 void _checkSuperOverrides(ClassDeclaration node) { | 219 DartType getType(TypeName name) { |
| 83 var seen = new Set<String>(); | 220 return (name == null) ? DynamicTypeImpl.instance : name.type; |
| 84 var current = node.element.type; | 221 } |
| 85 var visited = new Set<InterfaceType>(); | 222 |
| 86 do { | 223 void reset() { |
| 87 visited.add(current); | 224 _failure = false; |
| 88 current.mixins.reversed | 225 } |
| 89 .forEach((m) => _checkIndividualOverridesFromClass(node, m, seen)); | 226 |
| 90 _checkIndividualOverridesFromClass(node, current.superclass, seen); | 227 @override |
| 91 current = current.superclass; | 228 void visitAsExpression(AsExpression node) { |
| 92 } while (!current.isObject && !visited.contains(current)); | 229 // We could do the same check as the IsExpression below, but that is |
| 93 } | 230 // potentially too conservative. Instead, at runtime, we must fail hard |
| 94 | 231 // if the Dart as and the DDC as would return different values. |
| 95 /// Checks that implementations correctly override all reachable interfaces. | 232 node.visitChildren(this); |
| 96 /// In particular, we need to check these overrides for the definitions in | 233 } |
| 97 /// the class itself and each its superclasses. If a superclass is not | 234 |
| 98 /// abstract, then we can skip its transitive interfaces. For example, in: | 235 @override |
| 99 /// | 236 void visitAssignmentExpression(AssignmentExpression node) { |
| 100 /// B extends C implements G | 237 Token operator = node.operator; |
| 101 /// A extends B with E, F implements H, I | 238 TokenType operatorType = operator.type; |
| 102 /// | 239 if (operatorType == TokenType.EQ || |
| 103 /// we check: | 240 operatorType == TokenType.QUESTION_QUESTION_EQ) { |
| 104 /// | 241 DartType staticType = _getDefiniteType(node.leftHandSide); |
| 105 /// C against G, H, and I | 242 checkAssignment(node.rightHandSide, staticType); |
| 106 /// B against G, H, and I | 243 } else if (operatorType == TokenType.AMPERSAND_AMPERSAND_EQ || |
| 107 /// E against H and I // no check against G because B is a concrete class | 244 operatorType == TokenType.BAR_BAR_EQ) { |
| 108 /// F against H and I | 245 checkAssignment(node.leftHandSide, typeProvider.boolType); |
| 109 /// A against H and I | 246 checkAssignment(node.rightHandSide, typeProvider.boolType); |
| 110 void _checkAllInterfaceOverrides(ClassDeclaration node) { | 247 } else { |
| 111 var seen = new Set<String>(); | 248 _checkCompoundAssignment(node); |
| 112 // Helper function to collect all reachable interfaces. | 249 } |
| 113 find(InterfaceType interfaceType, Set result) { | 250 node.visitChildren(this); |
| 114 if (interfaceType == null || interfaceType.isObject) return; | 251 } |
| 115 if (result.contains(interfaceType)) return; | 252 |
| 116 result.add(interfaceType); | 253 @override |
| 117 find(interfaceType.superclass, result); | 254 void visitBinaryExpression(BinaryExpression node) { |
| 118 interfaceType.mixins.forEach((i) => find(i, result)); | 255 var op = node.operator; |
| 119 interfaceType.interfaces.forEach((i) => find(i, result)); | 256 if (op.isUserDefinableOperator) { |
| 120 } | 257 var element = node.staticElement; |
| 121 | 258 if (element == null) { |
| 122 // Check all interfaces reachable from the `implements` clause in the | 259 // Dynamic invocation |
| 123 // current class against definitions here and in superclasses. | 260 // TODO(vsm): Move this logic to the resolver? |
| 124 var localInterfaces = new Set<InterfaceType>(); | 261 if (op.type != TokenType.EQ_EQ && op.type != TokenType.BANG_EQ) { |
| 125 var type = node.element.type; | 262 _recordDynamicInvoke(node, node.leftOperand); |
| 126 type.interfaces.forEach((i) => find(i, localInterfaces)); | |
| 127 _checkInterfacesOverrides(node, localInterfaces, seen, | |
| 128 includeParents: true); | |
| 129 | |
| 130 // Check also how we override locally the interfaces from parent classes if | |
| 131 // the parent class is abstract. Otherwise, these will be checked as | |
| 132 // overrides on the concrete superclass. | |
| 133 var superInterfaces = new Set<InterfaceType>(); | |
| 134 var parent = type.superclass; | |
| 135 // TODO(sigmund): we don't seem to be reporting the analyzer error that a | |
| 136 // non-abstract class is not implementing an interface. See | |
| 137 // https://github.com/dart-lang/dart-dev-compiler/issues/25 | |
| 138 while (parent != null && parent.element.isAbstract) { | |
| 139 parent.interfaces.forEach((i) => find(i, superInterfaces)); | |
| 140 parent = parent.superclass; | |
| 141 } | |
| 142 _checkInterfacesOverrides(node, superInterfaces, seen, | |
| 143 includeParents: false); | |
| 144 } | |
| 145 | |
| 146 /// Checks that [cls] and its super classes (including mixins) correctly | |
| 147 /// overrides each interface in [interfaces]. If [includeParents] is false, | |
| 148 /// then mixins are still checked, but the base type and it's transitive | |
| 149 /// supertypes are not. | |
| 150 /// | |
| 151 /// [cls] can be either a [ClassDeclaration] or a [InterfaceType]. For | |
| 152 /// [ClassDeclaration]s errors are reported on the member that contains the | |
| 153 /// invalid override, for [InterfaceType]s we use [errorLocation] instead. | |
| 154 void _checkInterfacesOverrides( | |
| 155 cls, Iterable<InterfaceType> interfaces, Set<String> seen, | |
| 156 {Set<InterfaceType> visited, | |
| 157 bool includeParents: true, | |
| 158 AstNode errorLocation}) { | |
| 159 var node = cls is ClassDeclaration ? cls : null; | |
| 160 var type = cls is InterfaceType ? cls : node.element.type; | |
| 161 | |
| 162 if (visited == null) { | |
| 163 visited = new Set<InterfaceType>(); | |
| 164 } else if (visited.contains(type)) { | |
| 165 // Malformed type. | |
| 166 return; | |
| 167 } else { | |
| 168 visited.add(type); | |
| 169 } | |
| 170 | |
| 171 // Check direct overrides on [type] | |
| 172 for (var interfaceType in interfaces) { | |
| 173 if (node != null) { | |
| 174 _checkIndividualOverridesFromClass(node, interfaceType, seen); | |
| 175 } else { | |
| 176 _checkIndividualOverridesFromType( | |
| 177 type, interfaceType, errorLocation, seen); | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 // Check overrides from its mixins | |
| 182 for (int i = 0; i < type.mixins.length; i++) { | |
| 183 var loc = | |
| 184 errorLocation != null ? errorLocation : node.withClause.mixinTypes[i]; | |
| 185 for (var interfaceType in interfaces) { | |
| 186 // We copy [seen] so we can report separately if more than one mixin or | |
| 187 // the base class have an invalid override. | |
| 188 _checkIndividualOverridesFromType( | |
| 189 type.mixins[i], interfaceType, loc, new Set.from(seen)); | |
| 190 } | |
| 191 } | |
| 192 | |
| 193 // Check overrides from its superclasses | |
| 194 if (includeParents) { | |
| 195 var parent = type.superclass; | |
| 196 if (parent.isObject) return; | |
| 197 var loc = errorLocation != null ? errorLocation : node.extendsClause; | |
| 198 // No need to copy [seen] here because we made copies above when reporting | |
| 199 // errors on mixins. | |
| 200 _checkInterfacesOverrides(parent, interfaces, seen, | |
| 201 visited: visited, includeParents: true, errorLocation: loc); | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 /// Check that individual methods and fields in [subType] correctly override | |
| 206 /// the declarations in [baseType]. | |
| 207 /// | |
| 208 /// The [errorLocation] node indicates where errors are reported, see | |
| 209 /// [_checkSingleOverride] for more details. | |
| 210 /// | |
| 211 /// The set [seen] is used to avoid reporting overrides more than once. It | |
| 212 /// is used when invoking this function multiple times when checking several | |
| 213 /// types in a class hierarchy. Errors are reported only the first time an | |
| 214 /// invalid override involving a specific member is encountered. | |
| 215 _checkIndividualOverridesFromType(InterfaceType subType, | |
| 216 InterfaceType baseType, AstNode errorLocation, Set<String> seen) { | |
| 217 void checkHelper(ExecutableElement e) { | |
| 218 if (e.isStatic) return; | |
| 219 if (seen.contains(e.name)) return; | |
| 220 if (_checkSingleOverride(e, baseType, null, errorLocation)) { | |
| 221 seen.add(e.name); | |
| 222 } | |
| 223 } | |
| 224 subType.methods.forEach(checkHelper); | |
| 225 subType.accessors.forEach(checkHelper); | |
| 226 } | |
| 227 | |
| 228 /// Check that individual methods and fields in [subType] correctly override | |
| 229 /// the declarations in [baseType]. | |
| 230 /// | |
| 231 /// The [errorLocation] node indicates where errors are reported, see | |
| 232 /// [_checkSingleOverride] for more details. | |
| 233 _checkIndividualOverridesFromClass( | |
| 234 ClassDeclaration node, InterfaceType baseType, Set<String> seen) { | |
| 235 for (var member in node.members) { | |
| 236 if (member is ConstructorDeclaration) continue; | |
| 237 if (member is FieldDeclaration) { | |
| 238 if (member.isStatic) continue; | |
| 239 for (var variable in member.fields.variables) { | |
| 240 var element = variable.element as PropertyInducingElement; | |
| 241 var name = element.name; | |
| 242 if (seen.contains(name)) continue; | |
| 243 var getter = element.getter; | |
| 244 var setter = element.setter; | |
| 245 bool found = _checkSingleOverride(getter, baseType, variable, member); | |
| 246 if (!variable.isFinal && | |
| 247 !variable.isConst && | |
| 248 _checkSingleOverride(setter, baseType, variable, member)) { | |
| 249 found = true; | |
| 250 } | |
| 251 if (found) seen.add(name); | |
| 252 } | 263 } |
| 253 } else { | 264 } else { |
| 254 if ((member as MethodDeclaration).isStatic) continue; | 265 // Method invocation. |
| 255 var method = (member as MethodDeclaration).element; | 266 if (element is MethodElement) { |
| 256 if (seen.contains(method.name)) continue; | 267 var type = element.type; |
| 257 if (_checkSingleOverride(method, baseType, member, member)) { | 268 // Analyzer should enforce number of parameter types, but check in |
| 258 seen.add(method.name); | 269 // case we have erroneous input. |
| 270 if (type.normalParameterTypes.isNotEmpty) { |
| 271 checkArgument(node.rightOperand, type.normalParameterTypes[0]); |
| 272 } |
| 273 } else { |
| 274 // TODO(vsm): Assert that the analyzer found an error here? |
| 259 } | 275 } |
| 260 } | 276 } |
| 261 } | 277 } else { |
| 262 } | 278 // Non-method operator. |
| 263 | 279 switch (op.type) { |
| 264 /// Checks that [element] correctly overrides its corresponding member in | 280 case TokenType.AMPERSAND_AMPERSAND: |
| 265 /// [type]. Returns `true` if an override was found, that is, if [element] has | 281 case TokenType.BAR_BAR: |
| 266 /// a corresponding member in [type] that it overrides. | 282 checkBoolean(node.leftOperand); |
| 267 /// | 283 checkBoolean(node.rightOperand); |
| 268 /// The [errorLocation] is a node where the error is reported. For example, a | 284 break; |
| 269 /// bad override of a method in a class with respect to its superclass is | 285 case TokenType.BANG_EQ: |
| 270 /// reported directly at the method declaration. However, invalid overrides | 286 break; |
| 271 /// from base classes to interfaces, mixins to the base they are applied to, | 287 case TokenType.QUESTION_QUESTION: |
| 272 /// or mixins to interfaces are reported at the class declaration, since the | 288 break; |
| 273 /// base class or members on their own were not incorrect, only combining them | 289 default: |
| 274 /// with the interface was problematic. For example, these are example error | 290 assert(false); |
| 275 /// locations in these cases: | 291 } |
| 276 /// | 292 } |
| 277 /// error: base class introduces an invalid override. The type of B.foo is | 293 node.visitChildren(this); |
| 278 /// not a subtype of E.foo: | 294 } |
| 279 /// class A extends B implements E { ... } | 295 |
| 280 /// ^^^^^^^^^ | 296 @override |
| 281 /// | 297 void visitClassDeclaration(ClassDeclaration node) { |
| 282 /// error: mixin introduces an invalid override. The type of C.foo is not | 298 _overrideChecker.check(node); |
| 283 /// a subtype of E.foo: | 299 super.visitClassDeclaration(node); |
| 284 /// class A extends B with C implements E { ... } | 300 } |
| 285 /// ^ | |
| 286 /// | |
| 287 /// When checking for overrides from a type and it's super types, [node] is | |
| 288 /// the AST node that defines [element]. This is used to determine whether the | |
| 289 /// type of the element could be inferred from the types in the super classes. | |
| 290 bool _checkSingleOverride(ExecutableElement element, InterfaceType type, | |
| 291 AstNode node, AstNode errorLocation) { | |
| 292 assert(!element.isStatic); | |
| 293 | |
| 294 FunctionType subType = _rules.elementType(element); | |
| 295 // TODO(vsm): Test for generic | |
| 296 FunctionType baseType = _getMemberType(type, element); | |
| 297 | |
| 298 if (baseType == null) return false; | |
| 299 if (!_rules.isAssignable(subType, baseType)) { | |
| 300 // See whether non-assignable cases fit one of our common patterns: | |
| 301 // | |
| 302 // Common pattern 1: Inferable return type (on getters and methods) | |
| 303 // class A { | |
| 304 // int get foo => ...; | |
| 305 // String toString() { ... } | |
| 306 // } | |
| 307 // class B extends A { | |
| 308 // get foo => e; // no type specified. | |
| 309 // toString() { ... } // no return type specified. | |
| 310 // } | |
| 311 _recordMessage(new InvalidMethodOverride( | |
| 312 errorLocation, element, type, subType, baseType)); | |
| 313 } | |
| 314 return true; | |
| 315 } | |
| 316 | |
| 317 void _recordMessage(StaticInfo info) { | |
| 318 if (info == null) return; | |
| 319 var error = info.toAnalysisError(); | |
| 320 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; | |
| 321 _reporter.onError(error); | |
| 322 } | |
| 323 } | |
| 324 | |
| 325 /// Checks the body of functions and properties. | |
| 326 class CodeChecker extends RecursiveAstVisitor { | |
| 327 final TypeRules rules; | |
| 328 final AnalysisErrorListener reporter; | |
| 329 final _OverrideChecker _overrideChecker; | |
| 330 final bool _hints; | |
| 331 | |
| 332 bool _failure = false; | |
| 333 bool get failure => _failure || _overrideChecker._failure; | |
| 334 | |
| 335 void reset() { | |
| 336 _failure = false; | |
| 337 _overrideChecker._failure = false; | |
| 338 } | |
| 339 | |
| 340 CodeChecker(TypeRules rules, AnalysisErrorListener reporter, | |
| 341 {bool hints: false}) | |
| 342 : rules = rules, | |
| 343 reporter = reporter, | |
| 344 _hints = hints, | |
| 345 _overrideChecker = new _OverrideChecker(rules, reporter); | |
| 346 | 301 |
| 347 @override | 302 @override |
| 348 void visitComment(Comment node) { | 303 void visitComment(Comment node) { |
| 349 // skip, no need to do typechecking inside comments (they may contain | 304 // skip, no need to do typechecking inside comments (they may contain |
| 350 // comment references which would require resolution). | 305 // comment references which would require resolution). |
| 351 } | 306 } |
| 352 | 307 |
| 353 @override | 308 @override |
| 354 void visitClassDeclaration(ClassDeclaration node) { | 309 void visitCompilationUnit(CompilationUnit node) { |
| 355 _overrideChecker.check(node); | 310 _hasImplicitCasts = false; |
| 356 super.visitClassDeclaration(node); | 311 node.visitChildren(this); |
| 312 setHasImplicitCasts(node, _hasImplicitCasts); |
| 357 } | 313 } |
| 358 | 314 |
| 359 @override | 315 @override |
| 360 void visitAssignmentExpression(AssignmentExpression node) { | 316 void visitConditionalExpression(ConditionalExpression node) { |
| 361 var token = node.operator; | 317 checkBoolean(node.condition); |
| 362 if (token.type != TokenType.EQ) { | |
| 363 _checkCompoundAssignment(node); | |
| 364 } else { | |
| 365 DartType staticType = _getStaticType(node.leftHandSide); | |
| 366 checkAssignment(node.rightHandSide, staticType); | |
| 367 } | |
| 368 node.visitChildren(this); | 318 node.visitChildren(this); |
| 369 } | 319 } |
| 370 | 320 |
| 371 /// Check constructor declaration to ensure correct super call placement. | 321 /// Check constructor declaration to ensure correct super call placement. |
| 372 @override | 322 @override |
| 373 void visitConstructorDeclaration(ConstructorDeclaration node) { | 323 void visitConstructorDeclaration(ConstructorDeclaration node) { |
| 374 node.visitChildren(this); | 324 node.visitChildren(this); |
| 375 | 325 |
| 376 final init = node.initializers; | 326 final init = node.initializers; |
| 377 for (int i = 0, last = init.length - 1; i < last; i++) { | 327 for (int i = 0, last = init.length - 1; i < last; i++) { |
| 378 final node = init[i]; | 328 final node = init[i]; |
| 379 if (node is SuperConstructorInvocation) { | 329 if (node is SuperConstructorInvocation) { |
| 380 _recordMessage(new InvalidSuperInvocation(node)); | 330 _recordMessage(node, StrongModeCode.INVALID_SUPER_INVOCATION, [node]); |
| 381 } | 331 } |
| 382 } | 332 } |
| 383 } | 333 } |
| 384 | 334 |
| 335 // Check invocations |
| 385 @override | 336 @override |
| 386 void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { | 337 void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| 387 var field = node.fieldName; | 338 var field = node.fieldName; |
| 388 var element = field.staticElement; | 339 var element = field.staticElement; |
| 389 DartType staticType = rules.elementType(element); | 340 DartType staticType = _elementType(element); |
| 390 checkAssignment(node.expression, staticType); | 341 checkAssignment(node.expression, staticType); |
| 391 node.visitChildren(this); | 342 node.visitChildren(this); |
| 392 } | 343 } |
| 393 | 344 |
| 394 @override | 345 @override |
| 395 void visitForEachStatement(ForEachStatement node) { | 346 void visitDefaultFormalParameter(DefaultFormalParameter node) { |
| 396 // Check that the expression is an Iterable. | 347 // Check that defaults have the proper subtype. |
| 397 var expr = node.iterable; | 348 var parameter = node.parameter; |
| 398 var iterableType = node.awaitKeyword != null | 349 var parameterType = _elementType(parameter.element); |
| 399 ? rules.provider.streamType | 350 assert(parameterType != null); |
| 400 : rules.provider.iterableType; | 351 var defaultValue = node.defaultValue; |
| 401 var loopVariable = node.identifier != null | 352 if (defaultValue != null) { |
| 402 ? node.identifier | 353 checkAssignment(defaultValue, parameterType); |
| 403 : node.loopVariable?.identifier; | 354 } |
| 404 if (loopVariable != null) { | 355 |
| 405 var iteratorType = loopVariable.staticType; | 356 node.visitChildren(this); |
| 406 var checkedType = iterableType.substitute4([iteratorType]); | 357 } |
| 407 checkAssignment(expr, checkedType); | 358 |
| 359 @override |
| 360 void visitDoStatement(DoStatement node) { |
| 361 checkBoolean(node.condition); |
| 362 node.visitChildren(this); |
| 363 } |
| 364 |
| 365 @override |
| 366 void visitExpressionFunctionBody(ExpressionFunctionBody node) { |
| 367 _checkReturnOrYield(node.expression, node); |
| 368 node.visitChildren(this); |
| 369 } |
| 370 |
| 371 @override |
| 372 void visitFieldFormalParameter(FieldFormalParameter node) { |
| 373 var element = node.element; |
| 374 var typeName = node.type; |
| 375 if (typeName != null) { |
| 376 var type = _elementType(element); |
| 377 var fieldElement = |
| 378 node.identifier.staticElement as FieldFormalParameterElement; |
| 379 var fieldType = _elementType(fieldElement.field); |
| 380 if (!rules.isSubtypeOf(type, fieldType)) { |
| 381 _recordMessage(node, StrongModeCode.INVALID_PARAMETER_DECLARATION, |
| 382 [node, fieldType]); |
| 383 } |
| 408 } | 384 } |
| 409 node.visitChildren(this); | 385 node.visitChildren(this); |
| 410 } | 386 } |
| 411 | 387 |
| 388 @override |
| 389 void visitForEachStatement(ForEachStatement node) { |
| 390 var loopVariable = node.identifier ?? node.loopVariable?.identifier; |
| 391 |
| 392 // Safely handle malformed statements. |
| 393 if (loopVariable != null) { |
| 394 // Find the element type of the sequence. |
| 395 var sequenceInterface = node.awaitKeyword != null |
| 396 ? typeProvider.streamType |
| 397 : typeProvider.iterableType; |
| 398 var iterableType = _getDefiniteType(node.iterable); |
| 399 var elementType = |
| 400 rules.mostSpecificTypeArgument(iterableType, sequenceInterface); |
| 401 |
| 402 // If the sequence is not an Iterable (or Stream for await for) but is a |
| 403 // supertype of it, do an implicit downcast to Iterable<dynamic>. Then |
| 404 // we'll do a separate cast of the dynamic element to the variable's type. |
| 405 if (elementType == null) { |
| 406 var sequenceType = |
| 407 sequenceInterface.instantiate([DynamicTypeImpl.instance]); |
| 408 |
| 409 if (rules.isSubtypeOf(sequenceType, iterableType)) { |
| 410 _recordImplicitCast(node.iterable, sequenceType, from: iterableType); |
| 411 elementType = DynamicTypeImpl.instance; |
| 412 } |
| 413 } |
| 414 |
| 415 // If the sequence doesn't implement the interface at all, [ErrorVerifier] |
| 416 // will report the error, so ignore it here. |
| 417 if (elementType != null) { |
| 418 // Insert a cast from the sequence's element type to the loop variable's |
| 419 // if needed. |
| 420 _checkImplicitCast(loopVariable, _getDefiniteType(loopVariable), |
| 421 from: elementType); |
| 422 } |
| 423 } |
| 424 |
| 425 node.visitChildren(this); |
| 426 } |
| 427 |
| 412 @override | 428 @override |
| 413 void visitForStatement(ForStatement node) { | 429 void visitForStatement(ForStatement node) { |
| 414 if (node.condition != null) { | 430 if (node.condition != null) { |
| 415 checkBoolean(node.condition); | 431 checkBoolean(node.condition); |
| 416 } | 432 } |
| 417 node.visitChildren(this); | 433 node.visitChildren(this); |
| 418 } | 434 } |
| 419 | 435 |
| 420 @override | 436 @override |
| 437 void visitFunctionExpression(FunctionExpression node) { |
| 438 _checkForUnsafeBlockClosureInference(node); |
| 439 super.visitFunctionExpression(node); |
| 440 } |
| 441 |
| 442 @override |
| 443 void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
| 444 checkFunctionApplication(node); |
| 445 node.visitChildren(this); |
| 446 } |
| 447 |
| 448 @override |
| 421 void visitIfStatement(IfStatement node) { | 449 void visitIfStatement(IfStatement node) { |
| 422 checkBoolean(node.condition); | 450 checkBoolean(node.condition); |
| 423 node.visitChildren(this); | 451 node.visitChildren(this); |
| 424 } | 452 } |
| 425 | 453 |
| 426 @override | 454 @override |
| 427 void visitDoStatement(DoStatement node) { | 455 void visitIndexExpression(IndexExpression node) { |
| 428 checkBoolean(node.condition); | 456 var target = node.realTarget; |
| 457 var element = node.staticElement; |
| 458 if (element == null) { |
| 459 _recordDynamicInvoke(node, target); |
| 460 } else if (element is MethodElement) { |
| 461 var type = element.type; |
| 462 // Analyzer should enforce number of parameter types, but check in |
| 463 // case we have erroneous input. |
| 464 if (type.normalParameterTypes.isNotEmpty) { |
| 465 checkArgument(node.index, type.normalParameterTypes[0]); |
| 466 } |
| 467 } else { |
| 468 // TODO(vsm): Assert that the analyzer found an error here? |
| 469 } |
| 429 node.visitChildren(this); | 470 node.visitChildren(this); |
| 430 } | 471 } |
| 431 | 472 |
| 432 @override | 473 @override |
| 433 void visitWhileStatement(WhileStatement node) { | 474 void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| 434 checkBoolean(node.condition); | 475 var arguments = node.argumentList; |
| 476 var element = node.staticElement; |
| 477 if (element != null) { |
| 478 var type = _elementType(node.staticElement); |
| 479 checkArgumentList(arguments, type); |
| 480 } |
| 435 node.visitChildren(this); | 481 node.visitChildren(this); |
| 436 } | 482 } |
| 437 | 483 |
| 438 @override | 484 @override |
| 439 void visitSwitchStatement(SwitchStatement node) { | 485 void visitIsExpression(IsExpression node) { |
| 440 // SwitchStatement defines a boolean conversion to check the result of the | 486 _checkRuntimeTypeCheck(node, node.type); |
| 441 // case value == the switch value, but in dev_compiler we require a boolean | |
| 442 // return type from an overridden == operator (because Object.==), so | |
| 443 // checking in SwitchStatement shouldn't be necessary. | |
| 444 node.visitChildren(this); | 487 node.visitChildren(this); |
| 445 } | 488 } |
| 446 | 489 |
| 447 @override | 490 @override |
| 448 void visitListLiteral(ListLiteral node) { | 491 void visitListLiteral(ListLiteral node) { |
| 449 var type = rules.provider.dynamicType; | 492 DartType type = DynamicTypeImpl.instance; |
| 450 if (node.typeArguments != null) { | 493 if (node.typeArguments != null) { |
| 451 var targs = node.typeArguments.arguments; | 494 NodeList<TypeName> targs = node.typeArguments.arguments; |
| 452 if (targs.length > 0) type = targs[0].type; | 495 if (targs.length > 0) { |
| 496 type = targs[0].type; |
| 497 } |
| 498 } else { |
| 499 DartType staticType = node.staticType; |
| 500 if (staticType is InterfaceType) { |
| 501 List<DartType> targs = staticType.typeArguments; |
| 502 if (targs != null && targs.length > 0) { |
| 503 type = targs[0]; |
| 504 } |
| 505 } |
| 453 } | 506 } |
| 454 var elements = node.elements; | 507 NodeList<Expression> elements = node.elements; |
| 455 for (int i = 0; i < elements.length; i++) { | 508 for (int i = 0; i < elements.length; i++) { |
| 456 checkArgument(elements[i], type); | 509 checkArgument(elements[i], type); |
| 457 } | 510 } |
| 458 super.visitListLiteral(node); | 511 super.visitListLiteral(node); |
| 459 } | 512 } |
| 460 | 513 |
| 461 @override | 514 @override |
| 462 void visitMapLiteral(MapLiteral node) { | 515 void visitMapLiteral(MapLiteral node) { |
| 463 var ktype = rules.provider.dynamicType; | 516 DartType ktype = DynamicTypeImpl.instance; |
| 464 var vtype = rules.provider.dynamicType; | 517 DartType vtype = DynamicTypeImpl.instance; |
| 465 if (node.typeArguments != null) { | 518 if (node.typeArguments != null) { |
| 466 var targs = node.typeArguments.arguments; | 519 NodeList<TypeName> targs = node.typeArguments.arguments; |
| 467 if (targs.length > 0) ktype = targs[0].type; | 520 if (targs.length > 0) { |
| 468 if (targs.length > 1) vtype = targs[1].type; | 521 ktype = targs[0].type; |
| 522 } |
| 523 if (targs.length > 1) { |
| 524 vtype = targs[1].type; |
| 525 } |
| 526 } else { |
| 527 DartType staticType = node.staticType; |
| 528 if (staticType is InterfaceType) { |
| 529 List<DartType> targs = staticType.typeArguments; |
| 530 if (targs != null) { |
| 531 if (targs.length > 0) { |
| 532 ktype = targs[0]; |
| 533 } |
| 534 if (targs.length > 1) { |
| 535 vtype = targs[1]; |
| 536 } |
| 537 } |
| 538 } |
| 469 } | 539 } |
| 470 var entries = node.entries; | 540 NodeList<MapLiteralEntry> entries = node.entries; |
| 471 for (int i = 0; i < entries.length; i++) { | 541 for (int i = 0; i < entries.length; i++) { |
| 472 var entry = entries[i]; | 542 MapLiteralEntry entry = entries[i]; |
| 473 checkArgument(entry.key, ktype); | 543 checkArgument(entry.key, ktype); |
| 474 checkArgument(entry.value, vtype); | 544 checkArgument(entry.value, vtype); |
| 475 } | 545 } |
| 476 super.visitMapLiteral(node); | 546 super.visitMapLiteral(node); |
| 477 } | 547 } |
| 478 | 548 |
| 479 // Check invocations | |
| 480 void checkArgumentList(ArgumentList node, FunctionType type) { | |
| 481 NodeList<Expression> list = node.arguments; | |
| 482 int len = list.length; | |
| 483 for (int i = 0; i < len; ++i) { | |
| 484 Expression arg = list[i]; | |
| 485 ParameterElement element = arg.staticParameterElement; | |
| 486 if (element == null) { | |
| 487 if (type.parameters.length < len) { | |
| 488 // We found an argument mismatch, the analyzer will report this too, | |
| 489 // so no need to insert an error for this here. | |
| 490 continue; | |
| 491 } | |
| 492 element = type.parameters[i]; | |
| 493 // TODO(vsm): When can this happen? | |
| 494 assert(element != null); | |
| 495 } | |
| 496 DartType expectedType = rules.elementType(element); | |
| 497 if (expectedType == null) expectedType = rules.provider.dynamicType; | |
| 498 checkArgument(arg, expectedType); | |
| 499 } | |
| 500 } | |
| 501 | |
| 502 void checkArgument(Expression arg, DartType expectedType) { | |
| 503 // Preserve named argument structure, so their immediate parent is the | |
| 504 // method invocation. | |
| 505 if (arg is NamedExpression) { | |
| 506 arg = (arg as NamedExpression).expression; | |
| 507 } | |
| 508 checkAssignment(arg, expectedType); | |
| 509 } | |
| 510 | |
| 511 void checkFunctionApplication( | |
| 512 Expression node, Expression f, ArgumentList list) { | |
| 513 if (rules.isDynamicCall(f)) { | |
| 514 // If f is Function and this is a method invocation, we should have | |
| 515 // gotten an analyzer error, so no need to issue another error. | |
| 516 _recordDynamicInvoke(node, f); | |
| 517 } else { | |
| 518 checkArgumentList(list, rules.getTypeAsCaller(f)); | |
| 519 } | |
| 520 } | |
| 521 | |
| 522 @override | 549 @override |
| 523 visitMethodInvocation(MethodInvocation node) { | 550 visitMethodInvocation(MethodInvocation node) { |
| 524 var target = node.realTarget; | 551 var target = node.realTarget; |
| 525 if (rules.isDynamicTarget(target) && | 552 var element = node.methodName.staticElement; |
| 526 !_isObjectMethod(node, node.methodName)) { | 553 if (element == null && !typeProvider.isObjectMethod(node.methodName.name)) { |
| 527 _recordDynamicInvoke(node, target); | 554 _recordDynamicInvoke(node, target); |
| 528 | 555 |
| 529 // Mark the tear-off as being dynamic, too. This lets us distinguish | 556 // Mark the tear-off as being dynamic, too. This lets us distinguish |
| 530 // cases like: | 557 // cases like: |
| 531 // | 558 // |
| 532 // dynamic d; | 559 // dynamic d; |
| 533 // d.someMethod(...); // the whole method call must be a dynamic send. | 560 // d.someMethod(...); // the whole method call must be a dynamic send. |
| 534 // | 561 // |
| 535 // ... from case like: | 562 // ... from case like: |
| 536 // | 563 // |
| 537 // SomeType s; | 564 // SomeType s; |
| 538 // s.someDynamicField(...); // static get, followed by dynamic call. | 565 // s.someDynamicField(...); // static get, followed by dynamic call. |
| 539 // | 566 // |
| 540 // The first case is handled here, the second case is handled below when | 567 // The first case is handled here, the second case is handled below when |
| 541 // we call [checkFunctionApplication]. | 568 // we call [checkFunctionApplication]. |
| 542 DynamicInvoke.set(node.methodName, true); | 569 setIsDynamicInvoke(node.methodName, true); |
| 543 } else { | 570 } else { |
| 544 checkFunctionApplication(node, node.methodName, node.argumentList); | 571 checkFunctionApplication(node); |
| 545 } | 572 } |
| 546 node.visitChildren(this); | 573 node.visitChildren(this); |
| 547 } | 574 } |
| 548 | 575 |
| 549 @override | 576 @override |
| 550 void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { | 577 void visitPostfixExpression(PostfixExpression node) { |
| 551 checkFunctionApplication(node, node.function, node.argumentList); | 578 _checkUnary(node.operand, node.operator, node.staticElement); |
| 552 node.visitChildren(this); | 579 node.visitChildren(this); |
| 580 } |
| 581 |
| 582 @override |
| 583 void visitPrefixedIdentifier(PrefixedIdentifier node) { |
| 584 _checkFieldAccess(node, node.prefix, node.identifier); |
| 585 } |
| 586 |
| 587 @override |
| 588 void visitPrefixExpression(PrefixExpression node) { |
| 589 if (node.operator.type == TokenType.BANG) { |
| 590 checkBoolean(node.operand); |
| 591 } else { |
| 592 _checkUnary(node.operand, node.operator, node.staticElement); |
| 593 } |
| 594 node.visitChildren(this); |
| 595 } |
| 596 |
| 597 @override |
| 598 void visitPropertyAccess(PropertyAccess node) { |
| 599 _checkFieldAccess(node, node.realTarget, node.propertyName); |
| 553 } | 600 } |
| 554 | 601 |
| 555 @override | 602 @override |
| 556 void visitRedirectingConstructorInvocation( | 603 void visitRedirectingConstructorInvocation( |
| 557 RedirectingConstructorInvocation node) { | 604 RedirectingConstructorInvocation node) { |
| 558 var type = node.staticElement.type; | 605 var type = node.staticElement?.type; |
| 559 checkArgumentList(node.argumentList, type); | 606 // TODO(leafp): There's a TODO in visitRedirectingConstructorInvocation |
| 607 // in the element_resolver to handle the case that the element is null |
| 608 // and emit an error. In the meantime, just be defensive here. |
| 609 if (type != null) { |
| 610 checkArgumentList(node.argumentList, type); |
| 611 } |
| 612 node.visitChildren(this); |
| 613 } |
| 614 |
| 615 @override |
| 616 void visitReturnStatement(ReturnStatement node) { |
| 617 _checkReturnOrYield(node.expression, node); |
| 560 node.visitChildren(this); | 618 node.visitChildren(this); |
| 561 } | 619 } |
| 562 | 620 |
| 563 @override | 621 @override |
| 564 void visitSuperConstructorInvocation(SuperConstructorInvocation node) { | 622 void visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| 565 var element = node.staticElement; | 623 var element = node.staticElement; |
| 566 if (element != null) { | 624 if (element != null) { |
| 567 var type = node.staticElement.type; | 625 var type = node.staticElement.type; |
| 568 checkArgumentList(node.argumentList, type); | 626 checkArgumentList(node.argumentList, type); |
| 569 } | 627 } |
| 570 node.visitChildren(this); | 628 node.visitChildren(this); |
| 571 } | 629 } |
| 572 | 630 |
| 631 @override |
| 632 void visitSwitchStatement(SwitchStatement node) { |
| 633 // SwitchStatement defines a boolean conversion to check the result of the |
| 634 // case value == the switch value, but in dev_compiler we require a boolean |
| 635 // return type from an overridden == operator (because Object.==), so |
| 636 // checking in SwitchStatement shouldn't be necessary. |
| 637 node.visitChildren(this); |
| 638 } |
| 639 |
| 640 @override |
| 641 Object visitVariableDeclaration(VariableDeclaration node) { |
| 642 if (!node.isConst && |
| 643 !node.isFinal && |
| 644 node.initializer == null && |
| 645 rules.isNonNullableType(node?.element?.type)) { |
| 646 _recordMessage( |
| 647 node, |
| 648 StaticTypeWarningCode.NON_NULLABLE_FIELD_NOT_INITIALIZED, |
| 649 [node.name, node?.element?.type]); |
| 650 } |
| 651 return super.visitVariableDeclaration(node); |
| 652 } |
| 653 |
| 654 @override |
| 655 void visitVariableDeclarationList(VariableDeclarationList node) { |
| 656 TypeName type = node.type; |
| 657 if (type == null) { |
| 658 // No checks are needed when the type is var. Although internally the |
| 659 // typing rules may have inferred a more precise type for the variable |
| 660 // based on the initializer. |
| 661 } else { |
| 662 for (VariableDeclaration variable in node.variables) { |
| 663 var initializer = variable.initializer; |
| 664 if (initializer != null) { |
| 665 checkAssignment(initializer, type.type); |
| 666 } |
| 667 } |
| 668 } |
| 669 node.visitChildren(this); |
| 670 } |
| 671 |
| 672 @override |
| 673 void visitWhileStatement(WhileStatement node) { |
| 674 checkBoolean(node.condition); |
| 675 node.visitChildren(this); |
| 676 } |
| 677 |
| 678 @override |
| 679 void visitYieldStatement(YieldStatement node) { |
| 680 _checkReturnOrYield(node.expression, node, yieldStar: node.star != null); |
| 681 node.visitChildren(this); |
| 682 } |
| 683 |
| 684 void _checkCompoundAssignment(AssignmentExpression expr) { |
| 685 var op = expr.operator.type; |
| 686 assert(op.isAssignmentOperator && op != TokenType.EQ); |
| 687 var methodElement = expr.staticElement; |
| 688 if (methodElement == null) { |
| 689 // Dynamic invocation. |
| 690 _recordDynamicInvoke(expr, expr.leftHandSide); |
| 691 } else { |
| 692 // Sanity check the operator. |
| 693 assert(methodElement.isOperator); |
| 694 var functionType = methodElement.type; |
| 695 var paramTypes = functionType.normalParameterTypes; |
| 696 assert(paramTypes.length == 1); |
| 697 assert(functionType.namedParameterTypes.isEmpty); |
| 698 assert(functionType.optionalParameterTypes.isEmpty); |
| 699 |
| 700 // Refine the return type. |
| 701 var rhsType = _getDefiniteType(expr.rightHandSide); |
| 702 var lhsType = _getDefiniteType(expr.leftHandSide); |
| 703 var returnType = rules.refineBinaryExpressionType( |
| 704 typeProvider, lhsType, op, rhsType, functionType.returnType); |
| 705 |
| 706 // Check the argument for an implicit cast. |
| 707 _checkImplicitCast(expr.rightHandSide, paramTypes[0], from: rhsType); |
| 708 |
| 709 // Check the return type for an implicit cast. |
| 710 // |
| 711 // If needed, mark the assignment to indicate a down cast when we assign |
| 712 // back to it. So these two implicit casts are equivalent: |
| 713 // |
| 714 // y = /*implicit cast*/(y + 42); |
| 715 // /*implicit assignment cast*/y += 42; |
| 716 // |
| 717 _checkImplicitCast(expr.leftHandSide, lhsType, |
| 718 from: returnType, opAssign: true); |
| 719 } |
| 720 } |
| 721 |
| 722 /// Returns true if we need an implicit cast of [expr] from [from] type to |
| 723 /// [to] type, otherwise returns false. |
| 724 /// |
| 725 /// If [from] is omitted, uses the static type of [expr]. |
| 726 bool _needsImplicitCast(Expression expr, DartType to, {DartType from}) { |
| 727 from ??= _getDefiniteType(expr); |
| 728 |
| 729 if (!_checkNonNullAssignment(expr, to, from)) return false; |
| 730 |
| 731 // We can use anything as void. |
| 732 if (to.isVoid) return false; |
| 733 |
| 734 // fromT <: toT, no coercion needed. |
| 735 if (rules.isSubtypeOf(from, to)) return false; |
| 736 |
| 737 // Note: a function type is never assignable to a class per the Dart |
| 738 // spec - even if it has a compatible call method. We disallow as |
| 739 // well for consistency. |
| 740 if (from is FunctionType && rules.getCallMethodType(to) != null) { |
| 741 return false; |
| 742 } |
| 743 |
| 744 // Downcast if toT <: fromT |
| 745 if (rules.isSubtypeOf(to, from)) { |
| 746 return true; |
| 747 } |
| 748 |
| 749 // Anything else is an illegal sideways cast. |
| 750 // However, these will have been reported already in error_verifier, so we |
| 751 // don't need to report them again. |
| 752 return false; |
| 753 } |
| 754 |
| 755 /// Checks if an implicit cast of [expr] from [from] type to [to] type is |
| 756 /// needed, and if so records it. |
| 757 /// |
| 758 /// If [from] is omitted, uses the static type of [expr]. |
| 759 /// |
| 760 /// If [expr] does not require an implicit cast because it is not related to |
| 761 /// [to] or is already a subtype of it, does nothing. |
| 762 void _checkImplicitCast(Expression expr, DartType to, |
| 763 {DartType from, bool opAssign: false}) { |
| 764 from ??= _getDefiniteType(expr); |
| 765 |
| 766 if (_needsImplicitCast(expr, to, from: from)) { |
| 767 _recordImplicitCast(expr, to, from: from, opAssign: opAssign); |
| 768 } |
| 769 } |
| 770 |
| 771 void _checkFieldAccess(AstNode node, AstNode target, SimpleIdentifier field) { |
| 772 if (field.staticElement == null && |
| 773 !typeProvider.isObjectMember(field.name)) { |
| 774 _recordDynamicInvoke(node, target); |
| 775 } |
| 776 node.visitChildren(this); |
| 777 } |
| 778 |
| 779 /** |
| 780 * Check if the closure [node] is unsafe due to dartbug.com/26947. If so, |
| 781 * issue a warning. |
| 782 * |
| 783 * TODO(paulberry): eliminate this once dartbug.com/26947 is fixed. |
| 784 */ |
| 785 void _checkForUnsafeBlockClosureInference(FunctionExpression node) { |
| 786 if (node.body is! BlockFunctionBody) { |
| 787 return; |
| 788 } |
| 789 if (node.element.returnType.isDynamic) { |
| 790 return; |
| 791 } |
| 792 // Find the enclosing variable declaration whose inferred type might depend |
| 793 // on the inferred return type of the block closure (if any). |
| 794 AstNode prevAncestor = node; |
| 795 AstNode ancestor = node.parent; |
| 796 while (ancestor != null && ancestor is! VariableDeclaration) { |
| 797 if (ancestor is BlockFunctionBody) { |
| 798 // node is inside another block function body; if that block |
| 799 // function body is unsafe, we've already warned about it. |
| 800 return; |
| 801 } |
| 802 if (ancestor is InstanceCreationExpression) { |
| 803 // node appears inside an instance creation expression; we may be safe |
| 804 // if the type of the instance creation expression requires no |
| 805 // inference. |
| 806 TypeName typeName = ancestor.constructorName.type; |
| 807 if (typeName.typeArguments != null) { |
| 808 // Type arguments were explicitly specified. We are safe. |
| 809 return; |
| 810 } |
| 811 DartType type = typeName.type; |
| 812 if (!(type is ParameterizedType && type.typeParameters.isNotEmpty)) { |
| 813 // Type is not generic. We are safe. |
| 814 return; |
| 815 } |
| 816 } |
| 817 if (ancestor is MethodInvocation) { |
| 818 // node appears inside a method or function invocation; we may be safe |
| 819 // if the type of the method or function requires no inference. |
| 820 if (ancestor.typeArguments != null) { |
| 821 // Type arguments were explicitly specified. We are safe. |
| 822 return; |
| 823 } |
| 824 Element methodElement = ancestor.methodName.staticElement; |
| 825 if (!(methodElement is ExecutableElement && |
| 826 methodElement.typeParameters.isNotEmpty)) { |
| 827 // Method is not generic. We are safe. |
| 828 return; |
| 829 } |
| 830 } |
| 831 if (ancestor is FunctionExpressionInvocation && |
| 832 !identical(prevAncestor, ancestor.function)) { |
| 833 // node appears inside an argument to a function expression invocation; |
| 834 // we may be safe if the type of the function expression requires no |
| 835 // inference. |
| 836 if (ancestor.typeArguments != null) { |
| 837 // Type arguments were explicitly specified. We are safe. |
| 838 return; |
| 839 } |
| 840 DartType type = ancestor.function.staticType; |
| 841 if (!(type is FunctionTypeImpl && type.typeFormals.isNotEmpty)) { |
| 842 // Type is not generic or has had its type parameters instantiated. |
| 843 // We are safe. |
| 844 return; |
| 845 } |
| 846 } |
| 847 if ((ancestor is ListLiteral && ancestor.typeArguments != null) || |
| 848 (ancestor is MapLiteral && ancestor.typeArguments != null)) { |
| 849 // node appears inside a list or map literal with an explicit type. We |
| 850 // are safe because no type inference is required. |
| 851 return; |
| 852 } |
| 853 prevAncestor = ancestor; |
| 854 ancestor = ancestor.parent; |
| 855 } |
| 856 if (ancestor == null) { |
| 857 // node is not inside a variable declaration, so it is safe. |
| 858 return; |
| 859 } |
| 860 VariableDeclaration decl = ancestor; |
| 861 VariableElement declElement = decl.element; |
| 862 if (!declElement.hasImplicitType) { |
| 863 // Variable declaration has an explicit type, so it's safe. |
| 864 return; |
| 865 } |
| 866 if (declElement.type.isDynamic) { |
| 867 // No type was successfully inferred for this variable, so it's safe. |
| 868 return; |
| 869 } |
| 870 if (declElement.enclosingElement is ExecutableElement) { |
| 871 // Variable declaration is inside a function or method, so it's safe. |
| 872 return; |
| 873 } |
| 874 _recordMessage(node, StrongModeCode.UNSAFE_BLOCK_CLOSURE_INFERENCE, |
| 875 [declElement.name]); |
| 876 } |
| 877 |
| 878 /// Checks if the assignment is valid with respect to non-nullable types. |
| 879 /// Returns `false` if a nullable expression is assigned to a variable of |
| 880 /// non-nullable type and `true` otherwise. |
| 881 bool _checkNonNullAssignment( |
| 882 Expression expression, DartType to, DartType from) { |
| 883 if (rules.isNonNullableType(to) && rules.isNullableType(from)) { |
| 884 _recordMessage( |
| 885 expression, StaticTypeWarningCode.INVALID_ASSIGNMENT, [from, to]); |
| 886 return false; |
| 887 } |
| 888 return true; |
| 889 } |
| 890 |
| 573 void _checkReturnOrYield(Expression expression, AstNode node, | 891 void _checkReturnOrYield(Expression expression, AstNode node, |
| 574 {bool yieldStar: false}) { | 892 {bool yieldStar: false}) { |
| 575 var body = node.getAncestor((n) => n is FunctionBody); | 893 FunctionBody body = node.getAncestor((n) => n is FunctionBody); |
| 576 var type = rules.getExpectedReturnType(body, yieldStar: yieldStar); | 894 var type = _getExpectedReturnType(body, yieldStar: yieldStar); |
| 577 if (type == null) { | 895 if (type == null) { |
| 578 // We have a type mismatch: the async/async*/sync* modifier does | 896 // We have a type mismatch: the async/async*/sync* modifier does |
| 579 // not match the return or yield type. We should have already gotten an | 897 // not match the return or yield type. We should have already gotten an |
| 580 // analyzer error in this case. | 898 // analyzer error in this case. |
| 581 return; | 899 return; |
| 582 } | 900 } |
| 583 // TODO(vsm): Enforce void or dynamic (to void?) when expression is null. | 901 // TODO(vsm): Enforce void or dynamic (to void?) when expression is null. |
| 584 if (expression != null) checkAssignment(expression, type); | 902 if (expression != null) checkAssignment(expression, type); |
| 585 } | 903 } |
| 586 | 904 |
| 587 @override | |
| 588 void visitExpressionFunctionBody(ExpressionFunctionBody node) { | |
| 589 _checkReturnOrYield(node.expression, node); | |
| 590 node.visitChildren(this); | |
| 591 } | |
| 592 | |
| 593 @override | |
| 594 void visitReturnStatement(ReturnStatement node) { | |
| 595 _checkReturnOrYield(node.expression, node); | |
| 596 node.visitChildren(this); | |
| 597 } | |
| 598 | |
| 599 @override | |
| 600 void visitYieldStatement(YieldStatement node) { | |
| 601 _checkReturnOrYield(node.expression, node, yieldStar: node.star != null); | |
| 602 node.visitChildren(this); | |
| 603 } | |
| 604 | |
| 605 @override | |
| 606 void visitPropertyAccess(PropertyAccess node) { | |
| 607 var target = node.realTarget; | |
| 608 if (rules.isDynamicTarget(target) && | |
| 609 !_isObjectProperty(target, node.propertyName)) { | |
| 610 _recordDynamicInvoke(node, target); | |
| 611 } | |
| 612 node.visitChildren(this); | |
| 613 } | |
| 614 | |
| 615 @override | |
| 616 void visitPrefixedIdentifier(PrefixedIdentifier node) { | |
| 617 final target = node.prefix; | |
| 618 if (rules.isDynamicTarget(target) && | |
| 619 !_isObjectProperty(target, node.identifier)) { | |
| 620 _recordDynamicInvoke(node, target); | |
| 621 } | |
| 622 node.visitChildren(this); | |
| 623 } | |
| 624 | |
| 625 @override | |
| 626 void visitDefaultFormalParameter(DefaultFormalParameter node) { | |
| 627 // Check that defaults have the proper subtype. | |
| 628 var parameter = node.parameter; | |
| 629 var parameterType = rules.elementType(parameter.element); | |
| 630 assert(parameterType != null); | |
| 631 var defaultValue = node.defaultValue; | |
| 632 if (defaultValue != null) { | |
| 633 checkAssignment(defaultValue, parameterType); | |
| 634 } | |
| 635 | |
| 636 node.visitChildren(this); | |
| 637 } | |
| 638 | |
| 639 @override | |
| 640 void visitFieldFormalParameter(FieldFormalParameter node) { | |
| 641 var element = node.element; | |
| 642 var typeName = node.type; | |
| 643 if (typeName != null) { | |
| 644 var type = rules.elementType(element); | |
| 645 var fieldElement = | |
| 646 node.identifier.staticElement as FieldFormalParameterElement; | |
| 647 var fieldType = rules.elementType(fieldElement.field); | |
| 648 if (!rules.isSubTypeOf(type, fieldType)) { | |
| 649 var staticInfo = | |
| 650 new InvalidParameterDeclaration(rules, node, fieldType); | |
| 651 _recordMessage(staticInfo); | |
| 652 } | |
| 653 } | |
| 654 node.visitChildren(this); | |
| 655 } | |
| 656 | |
| 657 @override | |
| 658 void visitInstanceCreationExpression(InstanceCreationExpression node) { | |
| 659 var arguments = node.argumentList; | |
| 660 var element = node.staticElement; | |
| 661 if (element != null) { | |
| 662 var type = rules.elementType(node.staticElement); | |
| 663 checkArgumentList(arguments, type); | |
| 664 } | |
| 665 node.visitChildren(this); | |
| 666 } | |
| 667 | |
| 668 @override | |
| 669 void visitVariableDeclarationList(VariableDeclarationList node) { | |
| 670 TypeName type = node.type; | |
| 671 if (type == null) { | |
| 672 // No checks are needed when the type is var. Although internally the | |
| 673 // typing rules may have inferred a more precise type for the variable | |
| 674 // based on the initializer. | |
| 675 } else { | |
| 676 var dartType = getType(type); | |
| 677 for (VariableDeclaration variable in node.variables) { | |
| 678 var initializer = variable.initializer; | |
| 679 if (initializer != null) { | |
| 680 checkAssignment(initializer, dartType); | |
| 681 } | |
| 682 } | |
| 683 } | |
| 684 node.visitChildren(this); | |
| 685 } | |
| 686 | |
| 687 void _checkRuntimeTypeCheck(AstNode node, TypeName typeName) { | 905 void _checkRuntimeTypeCheck(AstNode node, TypeName typeName) { |
| 688 var type = getType(typeName); | 906 var type = getType(typeName); |
| 689 if (!rules.isGroundType(type)) { | 907 if (!rules.isGroundType(type)) { |
| 690 _recordMessage(new NonGroundTypeCheckInfo(node, type)); | 908 _recordMessage(node, StrongModeCode.NON_GROUND_TYPE_CHECK_INFO, [type]); |
| 691 } | 909 } |
| 692 } | 910 } |
| 693 | 911 |
| 694 @override | 912 void _checkUnary(Expression operand, Token op, MethodElement element) { |
| 695 void visitAsExpression(AsExpression node) { | 913 bool isIncrementAssign = |
| 696 // We could do the same check as the IsExpression below, but that is | 914 op.type == TokenType.PLUS_PLUS || op.type == TokenType.MINUS_MINUS; |
| 697 // potentially too conservative. Instead, at runtime, we must fail hard | 915 if (op.isUserDefinableOperator || isIncrementAssign) { |
| 698 // if the Dart as and the DDC as would return different values. | 916 if (element == null) { |
| 699 node.visitChildren(this); | 917 _recordDynamicInvoke(operand.parent, operand); |
| 700 } | 918 } else if (isIncrementAssign) { |
| 701 | 919 // For ++ and --, even if it is not dynamic, we still need to check |
| 702 @override | 920 // that the user defined method accepts an `int` as the RHS. |
| 703 void visitIsExpression(IsExpression node) { | 921 // |
| 704 _checkRuntimeTypeCheck(node, node.type); | 922 // We assume Analyzer has done this already (in ErrorVerifier). |
| 705 node.visitChildren(this); | 923 // |
| 706 } | 924 // However, we also need to check the return type. |
| 707 | 925 |
| 708 @override | 926 // Refine the return type. |
| 709 void visitPrefixExpression(PrefixExpression node) { | 927 var functionType = element.type; |
| 710 if (node.operator.type == TokenType.BANG) { | 928 var rhsType = typeProvider.intType; |
| 711 checkBoolean(node.operand); | 929 var lhsType = _getDefiniteType(operand); |
| 930 var returnType = rules.refineBinaryExpressionType(typeProvider, lhsType, |
| 931 TokenType.PLUS, rhsType, functionType.returnType); |
| 932 |
| 933 // Skip the argument check - `int` cannot be downcast. |
| 934 // |
| 935 // Check the return type for an implicit cast. |
| 936 // |
| 937 // If needed, mark the assignment to indicate a down cast when we assign |
| 938 // back to it. So these two implicit casts are equivalent: |
| 939 // |
| 940 // y = /*implicit cast*/(y + 1); |
| 941 // /*implicit assignment cast*/y++; |
| 942 // |
| 943 _checkImplicitCast(operand, lhsType, from: returnType, opAssign: true); |
| 944 } |
| 945 } |
| 946 } |
| 947 |
| 948 DartType _getDefiniteType(Expression expr) => |
| 949 getDefiniteType(expr, rules, typeProvider); |
| 950 |
| 951 /// Gets the expected return type of the given function [body], either from |
| 952 /// a normal return/yield, or from a yield*. |
| 953 DartType _getExpectedReturnType(FunctionBody body, {bool yieldStar: false}) { |
| 954 FunctionType functionType; |
| 955 var parent = body.parent; |
| 956 if (parent is Declaration) { |
| 957 functionType = _elementType(parent.element); |
| 712 } else { | 958 } else { |
| 713 _checkUnary(node); | 959 assert(parent is FunctionExpression); |
| 714 } | 960 functionType = |
| 715 node.visitChildren(this); | 961 (parent as FunctionExpression).staticType ?? DynamicTypeImpl.instance; |
| 716 } | 962 } |
| 717 | 963 |
| 718 @override | 964 var type = functionType.returnType; |
| 719 void visitPostfixExpression(PostfixExpression node) { | 965 |
| 720 _checkUnary(node); | 966 InterfaceType expectedType = null; |
| 721 node.visitChildren(this); | 967 if (body.isAsynchronous) { |
| 722 } | 968 if (body.isGenerator) { |
| 723 | 969 // Stream<T> -> T |
| 724 void _checkUnary(/*PrefixExpression|PostfixExpression*/ node) { | 970 expectedType = typeProvider.streamType; |
| 725 var op = node.operator; | 971 } else { |
| 726 if (op.isUserDefinableOperator || | 972 // Don't validate return type of async methods. |
| 727 op.type == TokenType.PLUS_PLUS || | 973 // They're handled by the runtime implementation. |
| 728 op.type == TokenType.MINUS_MINUS) { | 974 return null; |
| 729 if (rules.isDynamicTarget(node.operand)) { | |
| 730 _recordDynamicInvoke(node, node.operand); | |
| 731 } | |
| 732 // For ++ and --, even if it is not dynamic, we still need to check | |
| 733 // that the user defined method accepts an `int` as the RHS. | |
| 734 // We assume Analyzer has done this already. | |
| 735 } | |
| 736 } | |
| 737 | |
| 738 @override | |
| 739 void visitBinaryExpression(BinaryExpression node) { | |
| 740 var op = node.operator; | |
| 741 if (op.isUserDefinableOperator) { | |
| 742 if (rules.isDynamicTarget(node.leftOperand)) { | |
| 743 // Dynamic invocation | |
| 744 // TODO(vsm): Move this logic to the resolver? | |
| 745 if (op.type != TokenType.EQ_EQ && op.type != TokenType.BANG_EQ) { | |
| 746 _recordDynamicInvoke(node, node.leftOperand); | |
| 747 } | |
| 748 } else { | |
| 749 var element = node.staticElement; | |
| 750 // Method invocation. | |
| 751 if (element is MethodElement) { | |
| 752 var type = element.type; | |
| 753 // Analyzer should enforce number of parameter types, but check in | |
| 754 // case we have erroneous input. | |
| 755 if (type.normalParameterTypes.isNotEmpty) { | |
| 756 checkArgument(node.rightOperand, type.normalParameterTypes[0]); | |
| 757 } | |
| 758 } else { | |
| 759 // TODO(vsm): Assert that the analyzer found an error here? | |
| 760 } | |
| 761 } | 975 } |
| 762 } else { | 976 } else { |
| 763 // Non-method operator. | 977 if (body.isGenerator) { |
| 764 switch (op.type) { | 978 // Iterable<T> -> T |
| 765 case TokenType.AMPERSAND_AMPERSAND: | 979 expectedType = typeProvider.iterableType; |
| 766 case TokenType.BAR_BAR: | 980 } else { |
| 767 checkBoolean(node.leftOperand); | 981 // T -> T |
| 768 checkBoolean(node.rightOperand); | 982 return type; |
| 769 break; | 983 } |
| 770 case TokenType.BANG_EQ: | 984 } |
| 771 break; | 985 if (yieldStar) { |
| 772 case TokenType.QUESTION_QUESTION: | 986 if (type.isDynamic) { |
| 773 break; | 987 // Ensure it's at least a Stream / Iterable. |
| 774 default: | 988 return expectedType.instantiate([typeProvider.dynamicType]); |
| 775 assert(false); | 989 } else { |
| 776 } | 990 // Analyzer will provide a separate error if expected type |
| 777 } | 991 // is not compatible with type. |
| 778 node.visitChildren(this); | 992 return type; |
| 779 } | 993 } |
| 780 | 994 } |
| 781 @override | 995 if (type.isDynamic) { |
| 782 void visitConditionalExpression(ConditionalExpression node) { | 996 return type; |
| 783 checkBoolean(node.condition); | 997 } else if (type is InterfaceType && type.element == expectedType.element) { |
| 784 node.visitChildren(this); | 998 return type.typeArguments[0]; |
| 785 } | |
| 786 | |
| 787 @override | |
| 788 void visitIndexExpression(IndexExpression node) { | |
| 789 var target = node.realTarget; | |
| 790 if (rules.isDynamicTarget(target)) { | |
| 791 _recordDynamicInvoke(node, target); | |
| 792 } else { | 999 } else { |
| 793 var element = node.staticElement; | 1000 // Malformed type - fallback on analyzer error. |
| 794 if (element is MethodElement) { | 1001 return null; |
| 795 var type = element.type; | 1002 } |
| 796 // Analyzer should enforce number of parameter types, but check in | 1003 } |
| 797 // case we have erroneous input. | 1004 |
| 798 if (type.normalParameterTypes.isNotEmpty) { | 1005 /// Given an expression, return its type assuming it is |
| 799 checkArgument(node.index, type.normalParameterTypes[0]); | 1006 /// in the caller position of a call (that is, accounting |
| 800 } | 1007 /// for the possibility of a call method). Returns null |
| 801 } else { | 1008 /// if expression is not statically callable. |
| 802 // TODO(vsm): Assert that the analyzer found an error here? | 1009 FunctionType _getTypeAsCaller(InvocationExpression node) { |
| 803 } | 1010 DartType type = node.staticInvokeType; |
| 804 } | 1011 if (type is FunctionType) { |
| 805 node.visitChildren(this); | 1012 return type; |
| 806 } | 1013 } else if (type is InterfaceType) { |
| 807 | 1014 return rules.getCallMethodType(type); |
| 808 DartType getType(TypeName name) { | 1015 } |
| 809 return (name == null) ? rules.provider.dynamicType : name.type; | 1016 return null; |
| 810 } | 1017 } |
| 811 | 1018 |
| 812 /// Analyzer checks boolean conversions, but we need to check too, because | 1019 /// Returns `true` if the expression is a dynamic function call or method |
| 813 /// it uses the default assignability rules that allow `dynamic` and `Object` | 1020 /// invocation. |
| 814 /// to be assigned to bool with no message. | 1021 bool _isDynamicCall(InvocationExpression call, FunctionType ft) { |
| 815 void checkBoolean(Expression expr) => | 1022 // TODO(leafp): This will currently return true if t is Function |
| 816 checkAssignment(expr, rules.provider.boolType); | 1023 // This is probably the most correct thing to do for now, since |
| 817 | 1024 // this code is also used by the back end. Maybe revisit at some |
| 818 void checkAssignment(Expression expr, DartType type) { | 1025 // point? |
| 819 if (expr is ParenthesizedExpression) { | 1026 if (ft == null) return true; |
| 820 checkAssignment(expr.expression, type); | 1027 // Dynamic as the parameter type is treated as bottom. A function with |
| 821 } else { | 1028 // a dynamic parameter type requires a dynamic call in general. |
| 822 _recordMessage(rules.checkAssignment(expr, type)); | 1029 // However, as an optimization, if we have an original definition, we know |
| 823 } | 1030 // dynamic is reified as Object - in this case a regular call is fine. |
| 824 } | 1031 if (_hasStrictArrow(call.function)) { |
| 825 | 1032 return false; |
| 826 DartType _specializedBinaryReturnType( | 1033 } |
| 827 TokenType op, DartType t1, DartType t2, DartType normalReturnType) { | 1034 return rules.anyParameterType(ft, (pt) => pt.isDynamic); |
| 828 // This special cases binary return types as per 16.26 and 16.27 of the | 1035 } |
| 829 // Dart language spec. | 1036 |
| 830 switch (op) { | 1037 void _recordDynamicInvoke(AstNode node, Expression target) { |
| 831 case TokenType.PLUS: | 1038 _recordMessage(node, StrongModeCode.DYNAMIC_INVOKE, [node]); |
| 832 case TokenType.MINUS: | |
| 833 case TokenType.STAR: | |
| 834 case TokenType.TILDE_SLASH: | |
| 835 case TokenType.PERCENT: | |
| 836 case TokenType.PLUS_EQ: | |
| 837 case TokenType.MINUS_EQ: | |
| 838 case TokenType.STAR_EQ: | |
| 839 case TokenType.TILDE_SLASH_EQ: | |
| 840 case TokenType.PERCENT_EQ: | |
| 841 if (t1 == rules.provider.intType && | |
| 842 t2 == rules.provider.intType) return t1; | |
| 843 if (t1 == rules.provider.doubleType && | |
| 844 t2 == rules.provider.doubleType) return t1; | |
| 845 // This particular combo is not spelled out in the spec, but all | |
| 846 // implementations and analyzer seem to follow this. | |
| 847 if (t1 == rules.provider.doubleType && | |
| 848 t2 == rules.provider.intType) return t1; | |
| 849 } | |
| 850 return normalReturnType; | |
| 851 } | |
| 852 | |
| 853 void _checkCompoundAssignment(AssignmentExpression expr) { | |
| 854 var op = expr.operator.type; | |
| 855 assert(op.isAssignmentOperator && op != TokenType.EQ); | |
| 856 var methodElement = expr.staticElement; | |
| 857 if (methodElement == null) { | |
| 858 // Dynamic invocation | |
| 859 _recordDynamicInvoke(expr, expr.leftHandSide); | |
| 860 } else { | |
| 861 // Sanity check the operator | |
| 862 assert(methodElement.isOperator); | |
| 863 var functionType = methodElement.type; | |
| 864 var paramTypes = functionType.normalParameterTypes; | |
| 865 assert(paramTypes.length == 1); | |
| 866 assert(functionType.namedParameterTypes.isEmpty); | |
| 867 assert(functionType.optionalParameterTypes.isEmpty); | |
| 868 | |
| 869 // Check the lhs type | |
| 870 var staticInfo; | |
| 871 var rhsType = _getStaticType(expr.rightHandSide); | |
| 872 var lhsType = _getStaticType(expr.leftHandSide); | |
| 873 var returnType = _specializedBinaryReturnType( | |
| 874 op, lhsType, rhsType, functionType.returnType); | |
| 875 | |
| 876 if (!rules.isSubTypeOf(returnType, lhsType)) { | |
| 877 final numType = rules.provider.numType; | |
| 878 // Try to fix up the numerical case if possible. | |
| 879 if (rules.isSubTypeOf(lhsType, numType) && | |
| 880 rules.isSubTypeOf(lhsType, rhsType)) { | |
| 881 // This is also slightly different from spec, but allows us to keep | |
| 882 // compound operators in the int += num and num += dynamic cases. | |
| 883 staticInfo = DownCast.create( | |
| 884 rules, expr.rightHandSide, Coercion.cast(rhsType, lhsType)); | |
| 885 rhsType = lhsType; | |
| 886 } else { | |
| 887 // Static type error | |
| 888 staticInfo = new StaticTypeError(rules, expr, lhsType); | |
| 889 } | |
| 890 _recordMessage(staticInfo); | |
| 891 } | |
| 892 | |
| 893 // Check the rhs type | |
| 894 if (staticInfo is! CoercionInfo) { | |
| 895 var paramType = paramTypes.first; | |
| 896 staticInfo = rules.checkAssignment(expr.rightHandSide, paramType); | |
| 897 _recordMessage(staticInfo); | |
| 898 } | |
| 899 } | |
| 900 } | |
| 901 | |
| 902 bool _isObjectGetter(Expression target, SimpleIdentifier id) { | |
| 903 PropertyAccessorElement element = | |
| 904 rules.provider.objectType.element.getGetter(id.name); | |
| 905 return (element != null && !element.isStatic); | |
| 906 } | |
| 907 | |
| 908 bool _isObjectMethod(Expression target, SimpleIdentifier id) { | |
| 909 MethodElement element = | |
| 910 rules.provider.objectType.element.getMethod(id.name); | |
| 911 return (element != null && !element.isStatic); | |
| 912 } | |
| 913 | |
| 914 bool _isObjectProperty(Expression target, SimpleIdentifier id) { | |
| 915 return _isObjectGetter(target, id) || _isObjectMethod(target, id); | |
| 916 } | |
| 917 | |
| 918 DartType _getStaticType(Expression expr) { | |
| 919 return expr.staticType ?? rules.provider.dynamicType; | |
| 920 } | |
| 921 | |
| 922 void _recordDynamicInvoke(AstNode node, AstNode target) { | |
| 923 if (_hints) { | |
| 924 reporter.onError(new DynamicInvoke(rules, node).toAnalysisError()); | |
| 925 } | |
| 926 // TODO(jmesserly): we may eventually want to record if the whole operation | 1039 // TODO(jmesserly): we may eventually want to record if the whole operation |
| 927 // (node) was dynamic, rather than the target, but this is an easier fit | 1040 // (node) was dynamic, rather than the target, but this is an easier fit |
| 928 // with what we used to do. | 1041 // with what we used to do. |
| 929 DynamicInvoke.set(target, true); | 1042 if (target != null) setIsDynamicInvoke(target, true); |
| 930 } | 1043 } |
| 931 | 1044 |
| 932 void _recordMessage(StaticInfo info) { | 1045 /// Records an implicit cast for the [expr] from [from] to [to]. |
| 933 if (info == null) return; | 1046 /// |
| 934 var error = info.toAnalysisError(); | 1047 /// This will emit the appropriate error/warning/hint message as well as mark |
| 935 | 1048 /// the AST node. |
| 936 var severity = error.errorCode.errorSeverity; | 1049 void _recordImplicitCast(Expression expr, DartType to, |
| 1050 {DartType from, bool opAssign: false}) { |
| 1051 assert(rules.isSubtypeOf(to, from)); |
| 1052 |
| 1053 // Inference "casts": |
| 1054 if (expr is Literal || expr is FunctionExpression) { |
| 1055 // fromT should be an exact type - this will almost certainly fail at |
| 1056 // runtime. |
| 1057 _recordMessage(expr, StrongModeCode.STATIC_TYPE_ERROR, [expr, from, to]); |
| 1058 return; |
| 1059 } |
| 1060 |
| 1061 if (expr is InstanceCreationExpression) { |
| 1062 ConstructorElement e = expr.staticElement; |
| 1063 if (e == null || !e.isFactory) { |
| 1064 // fromT should be an exact type - this will almost certainly fail at |
| 1065 // runtime. |
| 1066 |
| 1067 _recordMessage( |
| 1068 expr, StrongModeCode.STATIC_TYPE_ERROR, [expr, from, to]); |
| 1069 return; |
| 1070 } |
| 1071 } |
| 1072 |
| 1073 if (isKnownFunction(expr)) { |
| 1074 _recordMessage(expr, StrongModeCode.STATIC_TYPE_ERROR, [expr, from, to]); |
| 1075 return; |
| 1076 } |
| 1077 |
| 1078 // Composite cast: these are more likely to fail. |
| 1079 bool downCastComposite = false; |
| 1080 if (!rules.isGroundType(to)) { |
| 1081 // This cast is (probably) due to our different treatment of dynamic. |
| 1082 // It may be more likely to fail at runtime. |
| 1083 if (from is InterfaceType) { |
| 1084 // For class types, we'd like to allow non-generic down casts, e.g., |
| 1085 // Iterable<T> to List<T>. The intuition here is that raw (generic) |
| 1086 // casts are problematic, and we should complain about those. |
| 1087 var typeArgs = from.typeArguments; |
| 1088 downCastComposite = |
| 1089 typeArgs.isEmpty || typeArgs.any((t) => t.isDynamic); |
| 1090 } else { |
| 1091 downCastComposite = true; |
| 1092 } |
| 1093 } |
| 1094 |
| 1095 var parent = expr.parent; |
| 1096 ErrorCode errorCode; |
| 1097 if (downCastComposite) { |
| 1098 errorCode = StrongModeCode.DOWN_CAST_COMPOSITE; |
| 1099 } else if (from.isDynamic) { |
| 1100 errorCode = StrongModeCode.DYNAMIC_CAST; |
| 1101 } else if (parent is VariableDeclaration && parent.initializer == expr) { |
| 1102 errorCode = StrongModeCode.ASSIGNMENT_CAST; |
| 1103 } else { |
| 1104 errorCode = opAssign |
| 1105 ? StrongModeCode.DOWN_CAST_IMPLICIT_ASSIGN |
| 1106 : StrongModeCode.DOWN_CAST_IMPLICIT; |
| 1107 } |
| 1108 _recordMessage(expr, errorCode, [from, to]); |
| 1109 if (opAssign) { |
| 1110 setImplicitAssignmentCast(expr, to); |
| 1111 } else { |
| 1112 setImplicitCast(expr, to); |
| 1113 } |
| 1114 _hasImplicitCasts = true; |
| 1115 } |
| 1116 |
| 1117 void _recordMessage(AstNode node, ErrorCode errorCode, List arguments) { |
| 1118 var severity = errorCode.errorSeverity; |
| 937 if (severity == ErrorSeverity.ERROR) _failure = true; | 1119 if (severity == ErrorSeverity.ERROR) _failure = true; |
| 938 if (severity != ErrorSeverity.INFO || _hints) { | 1120 if (severity != ErrorSeverity.INFO || _options.strongModeHints) { |
| 1121 int begin = node is AnnotatedNode |
| 1122 ? node.firstTokenAfterCommentAndMetadata.offset |
| 1123 : node.offset; |
| 1124 int length = node.end - begin; |
| 1125 var source = (node.root as CompilationUnit).element.source; |
| 1126 var error = |
| 1127 new AnalysisError(source, begin, length, errorCode, arguments); |
| 939 reporter.onError(error); | 1128 reporter.onError(error); |
| 940 } | 1129 } |
| 941 | |
| 942 if (info is CoercionInfo) { | |
| 943 // TODO(jmesserly): if we're run again on the same AST, we'll produce the | |
| 944 // same annotations. This should be harmless. This might go away once | |
| 945 // CodeChecker is integrated better with analyzer, as it will know that | |
| 946 // checking has already been performed. | |
| 947 // assert(CoercionInfo.get(info.node) == null); | |
| 948 CoercionInfo.set(info.node, info); | |
| 949 } | |
| 950 } | 1130 } |
| 951 } | 1131 } |
| 952 | 1132 |
| 953 /// Looks up the declaration that matches [member] in [type] and returns it's | 1133 /// Checks for overriding declarations of fields and methods. This is used to |
| 954 /// declared type. | 1134 /// check overrides between classes and superclasses, interfaces, and mixin |
| 955 FunctionType _getMemberType(InterfaceType type, ExecutableElement member) => | 1135 /// applications. |
| 956 _memberTypeGetter(member)(type); | 1136 class _OverrideChecker { |
| 957 | 1137 final StrongTypeSystemImpl rules; |
| 958 typedef FunctionType _MemberTypeGetter(InterfaceType type); | 1138 final CodeChecker _checker; |
| 959 | 1139 |
| 960 _MemberTypeGetter _memberTypeGetter(ExecutableElement member) { | 1140 _OverrideChecker(CodeChecker checker) |
| 961 String memberName = member.name; | 1141 : _checker = checker, |
| 962 final isGetter = member is PropertyAccessorElement && member.isGetter; | 1142 rules = checker.rules; |
| 963 final isSetter = member is PropertyAccessorElement && member.isSetter; | 1143 |
| 964 | 1144 void check(ClassDeclaration node) { |
| 965 FunctionType f(InterfaceType type) { | 1145 if (node.element.type.isObject) return; |
| 966 ExecutableElement baseMethod; | 1146 _checkSuperOverrides(node); |
| 967 try { | 1147 _checkMixinApplicationOverrides(node); |
| 968 if (isGetter) { | 1148 _checkAllInterfaceOverrides(node); |
| 969 assert(!isSetter); | 1149 } |
| 970 // Look for getter or field. | 1150 |
| 971 baseMethod = type.getGetter(memberName); | 1151 /// Checks that implementations correctly override all reachable interfaces. |
| 972 } else if (isSetter) { | 1152 /// In particular, we need to check these overrides for the definitions in |
| 973 baseMethod = type.getSetter(memberName); | 1153 /// the class itself and each its superclasses. If a superclass is not |
| 974 } else { | 1154 /// abstract, then we can skip its transitive interfaces. For example, in: |
| 975 baseMethod = type.getMethod(memberName); | 1155 /// |
| 976 } | 1156 /// B extends C implements G |
| 977 } catch (e) { | 1157 /// A extends B with E, F implements H, I |
| 978 // TODO(sigmund): remove this try-catch block (see issue #48). | 1158 /// |
| 979 } | 1159 /// we check: |
| 980 if (baseMethod == null || baseMethod.isStatic) return null; | 1160 /// |
| 981 return baseMethod.type; | 1161 /// C against G, H, and I |
| 982 } | 1162 /// B against G, H, and I |
| 983 ; | 1163 /// E against H and I // no check against G because B is a concrete class |
| 984 return f; | 1164 /// F against H and I |
| 1165 /// A against H and I |
| 1166 void _checkAllInterfaceOverrides(ClassDeclaration node) { |
| 1167 var seen = new Set<String>(); |
| 1168 // Helper function to collect all reachable interfaces. |
| 1169 find(InterfaceType interfaceType, Set result) { |
| 1170 if (interfaceType == null || interfaceType.isObject) return; |
| 1171 if (result.contains(interfaceType)) return; |
| 1172 result.add(interfaceType); |
| 1173 find(interfaceType.superclass, result); |
| 1174 interfaceType.mixins.forEach((i) => find(i, result)); |
| 1175 interfaceType.interfaces.forEach((i) => find(i, result)); |
| 1176 } |
| 1177 |
| 1178 // Check all interfaces reachable from the `implements` clause in the |
| 1179 // current class against definitions here and in superclasses. |
| 1180 var localInterfaces = new Set<InterfaceType>(); |
| 1181 var type = node.element.type; |
| 1182 type.interfaces.forEach((i) => find(i, localInterfaces)); |
| 1183 _checkInterfacesOverrides(node, localInterfaces, seen, |
| 1184 includeParents: true); |
| 1185 |
| 1186 // Check also how we override locally the interfaces from parent classes if |
| 1187 // the parent class is abstract. Otherwise, these will be checked as |
| 1188 // overrides on the concrete superclass. |
| 1189 var superInterfaces = new Set<InterfaceType>(); |
| 1190 var parent = type.superclass; |
| 1191 // TODO(sigmund): we don't seem to be reporting the analyzer error that a |
| 1192 // non-abstract class is not implementing an interface. See |
| 1193 // https://github.com/dart-lang/dart-dev-compiler/issues/25 |
| 1194 while (parent != null && parent.element.isAbstract) { |
| 1195 parent.interfaces.forEach((i) => find(i, superInterfaces)); |
| 1196 parent = parent.superclass; |
| 1197 } |
| 1198 _checkInterfacesOverrides(node, superInterfaces, seen, |
| 1199 includeParents: false); |
| 1200 } |
| 1201 |
| 1202 /// Check that individual methods and fields in [node] correctly override |
| 1203 /// the declarations in [baseType]. |
| 1204 /// |
| 1205 /// The [errorLocation] node indicates where errors are reported, see |
| 1206 /// [_checkSingleOverride] for more details. |
| 1207 _checkIndividualOverridesFromClass(ClassDeclaration node, |
| 1208 InterfaceType baseType, Set<String> seen, bool isSubclass) { |
| 1209 for (var member in node.members) { |
| 1210 if (member is FieldDeclaration) { |
| 1211 if (member.isStatic) { |
| 1212 continue; |
| 1213 } |
| 1214 for (var variable in member.fields.variables) { |
| 1215 var element = variable.element as PropertyInducingElement; |
| 1216 var name = element.name; |
| 1217 if (seen.contains(name)) { |
| 1218 continue; |
| 1219 } |
| 1220 var getter = element.getter; |
| 1221 var setter = element.setter; |
| 1222 bool found = _checkSingleOverride( |
| 1223 getter, baseType, variable.name, member, isSubclass); |
| 1224 if (!variable.isFinal && |
| 1225 !variable.isConst && |
| 1226 _checkSingleOverride( |
| 1227 setter, baseType, variable.name, member, isSubclass)) { |
| 1228 found = true; |
| 1229 } |
| 1230 if (found) { |
| 1231 seen.add(name); |
| 1232 } |
| 1233 } |
| 1234 } else if (member is MethodDeclaration) { |
| 1235 if (member.isStatic) { |
| 1236 continue; |
| 1237 } |
| 1238 var method = member.element; |
| 1239 if (seen.contains(method.name)) { |
| 1240 continue; |
| 1241 } |
| 1242 if (_checkSingleOverride( |
| 1243 method, baseType, member.name, member, isSubclass)) { |
| 1244 seen.add(method.name); |
| 1245 } |
| 1246 } else { |
| 1247 assert(member is ConstructorDeclaration); |
| 1248 } |
| 1249 } |
| 1250 } |
| 1251 |
| 1252 /// Check that individual methods and fields in [subType] correctly override |
| 1253 /// the declarations in [baseType]. |
| 1254 /// |
| 1255 /// The [errorLocation] node indicates where errors are reported, see |
| 1256 /// [_checkSingleOverride] for more details. |
| 1257 /// |
| 1258 /// The set [seen] is used to avoid reporting overrides more than once. It |
| 1259 /// is used when invoking this function multiple times when checking several |
| 1260 /// types in a class hierarchy. Errors are reported only the first time an |
| 1261 /// invalid override involving a specific member is encountered. |
| 1262 _checkIndividualOverridesFromType( |
| 1263 InterfaceType subType, |
| 1264 InterfaceType baseType, |
| 1265 AstNode errorLocation, |
| 1266 Set<String> seen, |
| 1267 bool isSubclass) { |
| 1268 void checkHelper(ExecutableElement e) { |
| 1269 if (e.isStatic) return; |
| 1270 if (seen.contains(e.name)) return; |
| 1271 if (_checkSingleOverride(e, baseType, null, errorLocation, isSubclass)) { |
| 1272 seen.add(e.name); |
| 1273 } |
| 1274 } |
| 1275 |
| 1276 subType.methods.forEach(checkHelper); |
| 1277 subType.accessors.forEach(checkHelper); |
| 1278 } |
| 1279 |
| 1280 /// Checks that [cls] and its super classes (including mixins) correctly |
| 1281 /// overrides each interface in [interfaces]. If [includeParents] is false, |
| 1282 /// then mixins are still checked, but the base type and it's transitive |
| 1283 /// supertypes are not. |
| 1284 /// |
| 1285 /// [cls] can be either a [ClassDeclaration] or a [InterfaceType]. For |
| 1286 /// [ClassDeclaration]s errors are reported on the member that contains the |
| 1287 /// invalid override, for [InterfaceType]s we use [errorLocation] instead. |
| 1288 void _checkInterfacesOverrides( |
| 1289 cls, Iterable<InterfaceType> interfaces, Set<String> seen, |
| 1290 {Set<InterfaceType> visited, |
| 1291 bool includeParents: true, |
| 1292 AstNode errorLocation}) { |
| 1293 var node = cls is ClassDeclaration ? cls : null; |
| 1294 var type = cls is InterfaceType ? cls : node.element.type; |
| 1295 |
| 1296 if (visited == null) { |
| 1297 visited = new Set<InterfaceType>(); |
| 1298 } else if (visited.contains(type)) { |
| 1299 // Malformed type. |
| 1300 return; |
| 1301 } else { |
| 1302 visited.add(type); |
| 1303 } |
| 1304 |
| 1305 // Check direct overrides on [type] |
| 1306 for (var interfaceType in interfaces) { |
| 1307 if (node != null) { |
| 1308 _checkIndividualOverridesFromClass(node, interfaceType, seen, false); |
| 1309 } else { |
| 1310 _checkIndividualOverridesFromType( |
| 1311 type, interfaceType, errorLocation, seen, false); |
| 1312 } |
| 1313 } |
| 1314 |
| 1315 // Check overrides from its mixins |
| 1316 for (int i = 0; i < type.mixins.length; i++) { |
| 1317 var loc = errorLocation ?? node.withClause.mixinTypes[i]; |
| 1318 for (var interfaceType in interfaces) { |
| 1319 // We copy [seen] so we can report separately if more than one mixin or |
| 1320 // the base class have an invalid override. |
| 1321 _checkIndividualOverridesFromType( |
| 1322 type.mixins[i], interfaceType, loc, new Set.from(seen), false); |
| 1323 } |
| 1324 } |
| 1325 |
| 1326 // Check overrides from its superclasses |
| 1327 if (includeParents) { |
| 1328 var parent = type.superclass; |
| 1329 if (parent.isObject) { |
| 1330 return; |
| 1331 } |
| 1332 var loc = errorLocation ?? node.extendsClause; |
| 1333 // No need to copy [seen] here because we made copies above when reporting |
| 1334 // errors on mixins. |
| 1335 _checkInterfacesOverrides(parent, interfaces, seen, |
| 1336 visited: visited, includeParents: true, errorLocation: loc); |
| 1337 } |
| 1338 } |
| 1339 |
| 1340 /// Check overrides from mixin applications themselves. For example, in: |
| 1341 /// |
| 1342 /// A extends B with E, F |
| 1343 /// |
| 1344 /// we check: |
| 1345 /// |
| 1346 /// B & E against B (equivalently how E overrides B) |
| 1347 /// B & E & F against B & E (equivalently how F overrides both B and E) |
| 1348 void _checkMixinApplicationOverrides(ClassDeclaration node) { |
| 1349 var type = node.element.type; |
| 1350 var parent = type.superclass; |
| 1351 var mixins = type.mixins; |
| 1352 |
| 1353 // Check overrides from applying mixins |
| 1354 for (int i = 0; i < mixins.length; i++) { |
| 1355 var seen = new Set<String>(); |
| 1356 var current = mixins[i]; |
| 1357 var errorLocation = node.withClause.mixinTypes[i]; |
| 1358 for (int j = i - 1; j >= 0; j--) { |
| 1359 _checkIndividualOverridesFromType( |
| 1360 current, mixins[j], errorLocation, seen, true); |
| 1361 } |
| 1362 _checkIndividualOverridesFromType( |
| 1363 current, parent, errorLocation, seen, true); |
| 1364 } |
| 1365 } |
| 1366 |
| 1367 /// Checks that [element] correctly overrides its corresponding member in |
| 1368 /// [type]. Returns `true` if an override was found, that is, if [element] has |
| 1369 /// a corresponding member in [type] that it overrides. |
| 1370 /// |
| 1371 /// The [errorLocation] is a node where the error is reported. For example, a |
| 1372 /// bad override of a method in a class with respect to its superclass is |
| 1373 /// reported directly at the method declaration. However, invalid overrides |
| 1374 /// from base classes to interfaces, mixins to the base they are applied to, |
| 1375 /// or mixins to interfaces are reported at the class declaration, since the |
| 1376 /// base class or members on their own were not incorrect, only combining them |
| 1377 /// with the interface was problematic. For example, these are example error |
| 1378 /// locations in these cases: |
| 1379 /// |
| 1380 /// error: base class introduces an invalid override. The type of B.foo is |
| 1381 /// not a subtype of E.foo: |
| 1382 /// class A extends B implements E { ... } |
| 1383 /// ^^^^^^^^^ |
| 1384 /// |
| 1385 /// error: mixin introduces an invalid override. The type of C.foo is not |
| 1386 /// a subtype of E.foo: |
| 1387 /// class A extends B with C implements E { ... } |
| 1388 /// ^ |
| 1389 /// |
| 1390 /// When checking for overrides from a type and it's super types, [node] is |
| 1391 /// the AST node that defines [element]. This is used to determine whether the |
| 1392 /// type of the element could be inferred from the types in the super classes. |
| 1393 bool _checkSingleOverride(ExecutableElement element, InterfaceType type, |
| 1394 AstNode node, AstNode errorLocation, bool isSubclass) { |
| 1395 assert(!element.isStatic); |
| 1396 |
| 1397 FunctionType subType = _elementType(element); |
| 1398 // TODO(vsm): Test for generic |
| 1399 FunctionType baseType = _getMemberType(type, element); |
| 1400 if (baseType == null) return false; |
| 1401 |
| 1402 if (isSubclass && element is PropertyAccessorElement) { |
| 1403 // Disallow any overriding if the base class defines this member |
| 1404 // as a field. We effectively treat fields as final / non-virtual, |
| 1405 // unless they are explicitly marked as @virtual |
| 1406 var field = _getMemberField(type, element); |
| 1407 if (field != null && !field.isVirtual) { |
| 1408 _checker._recordMessage( |
| 1409 errorLocation, StrongModeCode.INVALID_FIELD_OVERRIDE, [ |
| 1410 element.enclosingElement.name, |
| 1411 element.name, |
| 1412 subType, |
| 1413 type, |
| 1414 baseType |
| 1415 ]); |
| 1416 } |
| 1417 } |
| 1418 FunctionType concreteSubType = subType; |
| 1419 FunctionType concreteBaseType = baseType; |
| 1420 if (concreteSubType.typeFormals.isNotEmpty) { |
| 1421 if (concreteBaseType.typeFormals.isEmpty) { |
| 1422 concreteSubType = rules.instantiateToBounds(concreteSubType); |
| 1423 } |
| 1424 } |
| 1425 |
| 1426 if (!rules.isOverrideSubtypeOf(concreteSubType, concreteBaseType)) { |
| 1427 ErrorCode errorCode; |
| 1428 if (errorLocation is ExtendsClause) { |
| 1429 errorCode = StrongModeCode.INVALID_METHOD_OVERRIDE_FROM_BASE; |
| 1430 } else if (errorLocation.parent is WithClause) { |
| 1431 errorCode = StrongModeCode.INVALID_METHOD_OVERRIDE_FROM_MIXIN; |
| 1432 } else { |
| 1433 errorCode = StrongModeCode.INVALID_METHOD_OVERRIDE; |
| 1434 } |
| 1435 |
| 1436 _checker._recordMessage(errorLocation, errorCode, [ |
| 1437 element.enclosingElement.name, |
| 1438 element.name, |
| 1439 subType, |
| 1440 type, |
| 1441 baseType |
| 1442 ]); |
| 1443 } |
| 1444 |
| 1445 // If we have any covariant parameters and we're comparing against a |
| 1446 // superclass, we check all superclasses instead of stopping the search. |
| 1447 bool hasCovariant = element.parameters.any((p) => p.isCovariant); |
| 1448 bool keepSearching = hasCovariant && isSubclass; |
| 1449 return !keepSearching; |
| 1450 } |
| 1451 |
| 1452 /// Check overrides between a class and its superclasses and mixins. For |
| 1453 /// example, in: |
| 1454 /// |
| 1455 /// A extends B with E, F |
| 1456 /// |
| 1457 /// we check A against B, B super classes, E, and F. |
| 1458 /// |
| 1459 /// Internally we avoid reporting errors twice and we visit classes bottom up |
| 1460 /// to ensure we report the most immediate invalid override first. For |
| 1461 /// example, in the following code we'll report that `Test` has an invalid |
| 1462 /// override with respect to `Parent` (as opposed to an invalid override with |
| 1463 /// respect to `Grandparent`): |
| 1464 /// |
| 1465 /// class Grandparent { |
| 1466 /// m(A a) {} |
| 1467 /// } |
| 1468 /// class Parent extends Grandparent { |
| 1469 /// m(A a) {} |
| 1470 /// } |
| 1471 /// class Test extends Parent { |
| 1472 /// m(B a) {} // invalid override |
| 1473 /// } |
| 1474 void _checkSuperOverrides(ClassDeclaration node) { |
| 1475 var seen = new Set<String>(); |
| 1476 var current = node.element.type; |
| 1477 var visited = new Set<InterfaceType>(); |
| 1478 do { |
| 1479 visited.add(current); |
| 1480 current.mixins.reversed.forEach( |
| 1481 (m) => _checkIndividualOverridesFromClass(node, m, seen, true)); |
| 1482 _checkIndividualOverridesFromClass(node, current.superclass, seen, true); |
| 1483 current = current.superclass; |
| 1484 } while (!current.isObject && !visited.contains(current)); |
| 1485 } |
| 985 } | 1486 } |
| OLD | NEW |