| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dev_compiler.src.checker.checker; | 5 library dev_compiler.src.checker.checker; |
| 6 | 6 |
| 7 import 'package:analyzer/analyzer.dart'; | 7 export 'package:analyzer/src/task/strong/checker.dart'; |
| 8 import 'package:analyzer/src/generated/ast.dart'; | |
| 9 import 'package:analyzer/src/generated/element.dart'; | |
| 10 import 'package:analyzer/src/generated/scanner.dart' show Token, TokenType; | |
| 11 | |
| 12 import '../info.dart'; | |
| 13 import '../utils.dart' show getMemberType; | |
| 14 import 'rules.dart'; | |
| 15 | |
| 16 /// Checks for overriding declarations of fields and methods. This is used to | |
| 17 /// check overrides between classes and superclasses, interfaces, and mixin | |
| 18 /// applications. | |
| 19 class _OverrideChecker { | |
| 20 bool _failure = false; | |
| 21 final TypeRules _rules; | |
| 22 final AnalysisErrorListener _reporter; | |
| 23 | |
| 24 _OverrideChecker(this._rules, this._reporter); | |
| 25 | |
| 26 void check(ClassDeclaration node) { | |
| 27 if (node.element.type.isObject) return; | |
| 28 _checkSuperOverrides(node); | |
| 29 _checkMixinApplicationOverrides(node); | |
| 30 _checkAllInterfaceOverrides(node); | |
| 31 } | |
| 32 | |
| 33 /// Check overrides from mixin applications themselves. For example, in: | |
| 34 /// | |
| 35 /// A extends B with E, F | |
| 36 /// | |
| 37 /// we check: | |
| 38 /// | |
| 39 /// B & E against B (equivalently how E overrides B) | |
| 40 /// B & E & F against B & E (equivalently how F overrides both B and E) | |
| 41 void _checkMixinApplicationOverrides(ClassDeclaration node) { | |
| 42 var type = node.element.type; | |
| 43 var parent = type.superclass; | |
| 44 var mixins = type.mixins; | |
| 45 | |
| 46 // Check overrides from applying mixins | |
| 47 for (int i = 0; i < mixins.length; i++) { | |
| 48 var seen = new Set<String>(); | |
| 49 var current = mixins[i]; | |
| 50 var errorLocation = node.withClause.mixinTypes[i]; | |
| 51 for (int j = i - 1; j >= 0; j--) { | |
| 52 _checkIndividualOverridesFromType( | |
| 53 current, mixins[j], errorLocation, seen); | |
| 54 } | |
| 55 _checkIndividualOverridesFromType(current, parent, errorLocation, seen); | |
| 56 } | |
| 57 } | |
| 58 | |
| 59 /// Check overrides between a class and its superclasses and mixins. For | |
| 60 /// example, in: | |
| 61 /// | |
| 62 /// A extends B with E, F | |
| 63 /// | |
| 64 /// we check A against B, B super classes, E, and F. | |
| 65 /// | |
| 66 /// Internally we avoid reporting errors twice and we visit classes bottom up | |
| 67 /// to ensure we report the most immediate invalid override first. For | |
| 68 /// example, in the following code we'll report that `Test` has an invalid | |
| 69 /// override with respect to `Parent` (as opposed to an invalid override with | |
| 70 /// respect to `Grandparent`): | |
| 71 /// | |
| 72 /// class Grandparent { | |
| 73 /// m(A a) {} | |
| 74 /// } | |
| 75 /// class Parent extends Grandparent { | |
| 76 /// m(A a) {} | |
| 77 /// } | |
| 78 /// class Test extends Parent { | |
| 79 /// m(B a) {} // invalid override | |
| 80 /// } | |
| 81 void _checkSuperOverrides(ClassDeclaration node) { | |
| 82 var seen = new Set<String>(); | |
| 83 var current = node.element.type; | |
| 84 var visited = new Set<InterfaceType>(); | |
| 85 do { | |
| 86 visited.add(current); | |
| 87 current.mixins.reversed | |
| 88 .forEach((m) => _checkIndividualOverridesFromClass(node, m, seen)); | |
| 89 _checkIndividualOverridesFromClass(node, current.superclass, seen); | |
| 90 current = current.superclass; | |
| 91 } while (!current.isObject && !visited.contains(current)); | |
| 92 } | |
| 93 | |
| 94 /// Checks that implementations correctly override all reachable interfaces. | |
| 95 /// In particular, we need to check these overrides for the definitions in | |
| 96 /// the class itself and each its superclasses. If a superclass is not | |
| 97 /// abstract, then we can skip its transitive interfaces. For example, in: | |
| 98 /// | |
| 99 /// B extends C implements G | |
| 100 /// A extends B with E, F implements H, I | |
| 101 /// | |
| 102 /// we check: | |
| 103 /// | |
| 104 /// C against G, H, and I | |
| 105 /// B against G, H, and I | |
| 106 /// E against H and I // no check against G because B is a concrete class | |
| 107 /// F against H and I | |
| 108 /// A against H and I | |
| 109 void _checkAllInterfaceOverrides(ClassDeclaration node) { | |
| 110 var seen = new Set<String>(); | |
| 111 // Helper function to collect all reachable interfaces. | |
| 112 find(InterfaceType interfaceType, Set result) { | |
| 113 if (interfaceType == null || interfaceType.isObject) return; | |
| 114 if (result.contains(interfaceType)) return; | |
| 115 result.add(interfaceType); | |
| 116 find(interfaceType.superclass, result); | |
| 117 interfaceType.mixins.forEach((i) => find(i, result)); | |
| 118 interfaceType.interfaces.forEach((i) => find(i, result)); | |
| 119 } | |
| 120 | |
| 121 // Check all interfaces reachable from the `implements` clause in the | |
| 122 // current class against definitions here and in superclasses. | |
| 123 var localInterfaces = new Set<InterfaceType>(); | |
| 124 var type = node.element.type; | |
| 125 type.interfaces.forEach((i) => find(i, localInterfaces)); | |
| 126 _checkInterfacesOverrides(node, localInterfaces, seen, | |
| 127 includeParents: true); | |
| 128 | |
| 129 // Check also how we override locally the interfaces from parent classes if | |
| 130 // the parent class is abstract. Otherwise, these will be checked as | |
| 131 // overrides on the concrete superclass. | |
| 132 var superInterfaces = new Set<InterfaceType>(); | |
| 133 var parent = type.superclass; | |
| 134 // TODO(sigmund): we don't seem to be reporting the analyzer error that a | |
| 135 // non-abstract class is not implementing an interface. See | |
| 136 // https://github.com/dart-lang/dart-dev-compiler/issues/25 | |
| 137 while (parent != null && parent.element.isAbstract) { | |
| 138 parent.interfaces.forEach((i) => find(i, superInterfaces)); | |
| 139 parent = parent.superclass; | |
| 140 } | |
| 141 _checkInterfacesOverrides(node, superInterfaces, seen, | |
| 142 includeParents: false); | |
| 143 } | |
| 144 | |
| 145 /// Checks that [cls] and its super classes (including mixins) correctly | |
| 146 /// overrides each interface in [interfaces]. If [includeParents] is false, | |
| 147 /// then mixins are still checked, but the base type and it's transitive | |
| 148 /// supertypes are not. | |
| 149 /// | |
| 150 /// [cls] can be either a [ClassDeclaration] or a [InterfaceType]. For | |
| 151 /// [ClassDeclaration]s errors are reported on the member that contains the | |
| 152 /// invalid override, for [InterfaceType]s we use [errorLocation] instead. | |
| 153 void _checkInterfacesOverrides( | |
| 154 cls, Iterable<InterfaceType> interfaces, Set<String> seen, | |
| 155 {Set<InterfaceType> visited, | |
| 156 bool includeParents: true, | |
| 157 AstNode errorLocation}) { | |
| 158 var node = cls is ClassDeclaration ? cls : null; | |
| 159 var type = cls is InterfaceType ? cls : node.element.type; | |
| 160 | |
| 161 if (visited == null) { | |
| 162 visited = new Set<InterfaceType>(); | |
| 163 } else if (visited.contains(type)) { | |
| 164 // Malformed type. | |
| 165 return; | |
| 166 } else { | |
| 167 visited.add(type); | |
| 168 } | |
| 169 | |
| 170 // Check direct overrides on [type] | |
| 171 for (var interfaceType in interfaces) { | |
| 172 if (node != null) { | |
| 173 _checkIndividualOverridesFromClass(node, interfaceType, seen); | |
| 174 } else { | |
| 175 _checkIndividualOverridesFromType( | |
| 176 type, interfaceType, errorLocation, seen); | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 // Check overrides from its mixins | |
| 181 for (int i = 0; i < type.mixins.length; i++) { | |
| 182 var loc = | |
| 183 errorLocation != null ? errorLocation : node.withClause.mixinTypes[i]; | |
| 184 for (var interfaceType in interfaces) { | |
| 185 // We copy [seen] so we can report separately if more than one mixin or | |
| 186 // the base class have an invalid override. | |
| 187 _checkIndividualOverridesFromType( | |
| 188 type.mixins[i], interfaceType, loc, new Set.from(seen)); | |
| 189 } | |
| 190 } | |
| 191 | |
| 192 // Check overrides from its superclasses | |
| 193 if (includeParents) { | |
| 194 var parent = type.superclass; | |
| 195 if (parent.isObject) return; | |
| 196 var loc = errorLocation != null ? errorLocation : node.extendsClause; | |
| 197 // No need to copy [seen] here because we made copies above when reporting | |
| 198 // errors on mixins. | |
| 199 _checkInterfacesOverrides(parent, interfaces, seen, | |
| 200 visited: visited, includeParents: true, errorLocation: loc); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 /// Check that individual methods and fields in [subType] correctly override | |
| 205 /// the declarations in [baseType]. | |
| 206 /// | |
| 207 /// The [errorLocation] node indicates where errors are reported, see | |
| 208 /// [_checkSingleOverride] for more details. | |
| 209 /// | |
| 210 /// The set [seen] is used to avoid reporting overrides more than once. It | |
| 211 /// is used when invoking this function multiple times when checking several | |
| 212 /// types in a class hierarchy. Errors are reported only the first time an | |
| 213 /// invalid override involving a specific member is encountered. | |
| 214 _checkIndividualOverridesFromType(InterfaceType subType, | |
| 215 InterfaceType baseType, AstNode errorLocation, Set<String> seen) { | |
| 216 void checkHelper(ExecutableElement e) { | |
| 217 if (e.isStatic) return; | |
| 218 if (seen.contains(e.name)) return; | |
| 219 if (_checkSingleOverride(e, baseType, null, errorLocation)) { | |
| 220 seen.add(e.name); | |
| 221 } | |
| 222 } | |
| 223 subType.methods.forEach(checkHelper); | |
| 224 subType.accessors.forEach(checkHelper); | |
| 225 } | |
| 226 | |
| 227 /// Check that individual methods and fields in [subType] correctly override | |
| 228 /// the declarations in [baseType]. | |
| 229 /// | |
| 230 /// The [errorLocation] node indicates where errors are reported, see | |
| 231 /// [_checkSingleOverride] for more details. | |
| 232 _checkIndividualOverridesFromClass( | |
| 233 ClassDeclaration node, InterfaceType baseType, Set<String> seen) { | |
| 234 for (var member in node.members) { | |
| 235 if (member is ConstructorDeclaration) continue; | |
| 236 if (member is FieldDeclaration) { | |
| 237 if (member.isStatic) continue; | |
| 238 for (var variable in member.fields.variables) { | |
| 239 var element = variable.element as PropertyInducingElement; | |
| 240 var name = element.name; | |
| 241 if (seen.contains(name)) continue; | |
| 242 var getter = element.getter; | |
| 243 var setter = element.setter; | |
| 244 bool found = _checkSingleOverride(getter, baseType, variable, member); | |
| 245 if (!variable.isFinal && | |
| 246 !variable.isConst && | |
| 247 _checkSingleOverride(setter, baseType, variable, member)) { | |
| 248 found = true; | |
| 249 } | |
| 250 if (found) seen.add(name); | |
| 251 } | |
| 252 } else { | |
| 253 if ((member as MethodDeclaration).isStatic) continue; | |
| 254 var method = (member as MethodDeclaration).element; | |
| 255 if (seen.contains(method.name)) continue; | |
| 256 if (_checkSingleOverride(method, baseType, member, member)) { | |
| 257 seen.add(method.name); | |
| 258 } | |
| 259 } | |
| 260 } | |
| 261 } | |
| 262 | |
| 263 /// Checks that [element] correctly overrides its corresponding member in | |
| 264 /// [type]. Returns `true` if an override was found, that is, if [element] has | |
| 265 /// a corresponding member in [type] that it overrides. | |
| 266 /// | |
| 267 /// The [errorLocation] is a node where the error is reported. For example, a | |
| 268 /// bad override of a method in a class with respect to its superclass is | |
| 269 /// reported directly at the method declaration. However, invalid overrides | |
| 270 /// from base classes to interfaces, mixins to the base they are applied to, | |
| 271 /// or mixins to interfaces are reported at the class declaration, since the | |
| 272 /// base class or members on their own were not incorrect, only combining them | |
| 273 /// with the interface was problematic. For example, these are example error | |
| 274 /// locations in these cases: | |
| 275 /// | |
| 276 /// error: base class introduces an invalid override. The type of B.foo is | |
| 277 /// not a subtype of E.foo: | |
| 278 /// class A extends B implements E { ... } | |
| 279 /// ^^^^^^^^^ | |
| 280 /// | |
| 281 /// error: mixin introduces an invalid override. The type of C.foo is not | |
| 282 /// a subtype of E.foo: | |
| 283 /// class A extends B with C implements E { ... } | |
| 284 /// ^ | |
| 285 /// | |
| 286 /// When checking for overrides from a type and it's super types, [node] is | |
| 287 /// the AST node that defines [element]. This is used to determine whether the | |
| 288 /// type of the element could be inferred from the types in the super classes. | |
| 289 bool _checkSingleOverride(ExecutableElement element, InterfaceType type, | |
| 290 AstNode node, AstNode errorLocation) { | |
| 291 assert(!element.isStatic); | |
| 292 | |
| 293 FunctionType subType = _rules.elementType(element); | |
| 294 // TODO(vsm): Test for generic | |
| 295 FunctionType baseType = getMemberType(type, element); | |
| 296 | |
| 297 if (baseType == null) return false; | |
| 298 if (!_rules.isAssignable(subType, baseType)) { | |
| 299 // See whether non-assignable cases fit one of our common patterns: | |
| 300 // | |
| 301 // Common pattern 1: Inferable return type (on getters and methods) | |
| 302 // class A { | |
| 303 // int get foo => ...; | |
| 304 // String toString() { ... } | |
| 305 // } | |
| 306 // class B extends A { | |
| 307 // get foo => e; // no type specified. | |
| 308 // toString() { ... } // no return type specified. | |
| 309 // } | |
| 310 _recordMessage(new InvalidMethodOverride( | |
| 311 errorLocation, element, type, subType, baseType)); | |
| 312 } | |
| 313 return true; | |
| 314 } | |
| 315 | |
| 316 void _recordMessage(StaticInfo info) { | |
| 317 if (info == null) return; | |
| 318 var error = info.toAnalysisError(); | |
| 319 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; | |
| 320 _reporter.onError(error); | |
| 321 } | |
| 322 } | |
| 323 | |
| 324 /// Checks the body of functions and properties. | |
| 325 class CodeChecker extends RecursiveAstVisitor { | |
| 326 final TypeRules rules; | |
| 327 final AnalysisErrorListener reporter; | |
| 328 final _OverrideChecker _overrideChecker; | |
| 329 bool _failure = false; | |
| 330 bool get failure => _failure || _overrideChecker._failure; | |
| 331 | |
| 332 void reset() { | |
| 333 _failure = false; | |
| 334 _overrideChecker._failure = false; | |
| 335 } | |
| 336 | |
| 337 CodeChecker(TypeRules rules, AnalysisErrorListener reporter) | |
| 338 : rules = rules, | |
| 339 reporter = reporter, | |
| 340 _overrideChecker = new _OverrideChecker(rules, reporter); | |
| 341 | |
| 342 @override | |
| 343 void visitComment(Comment node) { | |
| 344 // skip, no need to do typechecking inside comments (they may contain | |
| 345 // comment references which would require resolution). | |
| 346 } | |
| 347 | |
| 348 @override | |
| 349 void visitClassDeclaration(ClassDeclaration node) { | |
| 350 _overrideChecker.check(node); | |
| 351 super.visitClassDeclaration(node); | |
| 352 } | |
| 353 | |
| 354 @override | |
| 355 void visitAssignmentExpression(AssignmentExpression node) { | |
| 356 var token = node.operator; | |
| 357 if (token.type != TokenType.EQ) { | |
| 358 _checkCompoundAssignment(node); | |
| 359 } else { | |
| 360 DartType staticType = _getStaticType(node.leftHandSide); | |
| 361 checkAssignment(node.rightHandSide, staticType); | |
| 362 } | |
| 363 node.visitChildren(this); | |
| 364 } | |
| 365 | |
| 366 /// Check constructor declaration to ensure correct super call placement. | |
| 367 @override | |
| 368 void visitConstructorDeclaration(ConstructorDeclaration node) { | |
| 369 node.visitChildren(this); | |
| 370 | |
| 371 final init = node.initializers; | |
| 372 for (int i = 0, last = init.length - 1; i < last; i++) { | |
| 373 final node = init[i]; | |
| 374 if (node is SuperConstructorInvocation) { | |
| 375 _recordMessage(new InvalidSuperInvocation(node)); | |
| 376 } | |
| 377 } | |
| 378 } | |
| 379 | |
| 380 @override | |
| 381 void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { | |
| 382 var field = node.fieldName; | |
| 383 var element = field.staticElement; | |
| 384 DartType staticType = rules.elementType(element); | |
| 385 checkAssignment(node.expression, staticType); | |
| 386 node.visitChildren(this); | |
| 387 } | |
| 388 | |
| 389 @override | |
| 390 void visitForEachStatement(ForEachStatement node) { | |
| 391 // Check that the expression is an Iterable. | |
| 392 var expr = node.iterable; | |
| 393 var iterableType = node.awaitKeyword != null | |
| 394 ? rules.provider.streamType | |
| 395 : rules.provider.iterableType; | |
| 396 var loopVariable = node.identifier != null | |
| 397 ? node.identifier | |
| 398 : node.loopVariable?.identifier; | |
| 399 if (loopVariable != null) { | |
| 400 var iteratorType = loopVariable.staticType; | |
| 401 var checkedType = iterableType.substitute4([iteratorType]); | |
| 402 checkAssignment(expr, checkedType); | |
| 403 } | |
| 404 node.visitChildren(this); | |
| 405 } | |
| 406 | |
| 407 @override | |
| 408 void visitForStatement(ForStatement node) { | |
| 409 if (node.condition != null) { | |
| 410 checkBoolean(node.condition); | |
| 411 } | |
| 412 node.visitChildren(this); | |
| 413 } | |
| 414 | |
| 415 @override | |
| 416 void visitIfStatement(IfStatement node) { | |
| 417 checkBoolean(node.condition); | |
| 418 node.visitChildren(this); | |
| 419 } | |
| 420 | |
| 421 @override | |
| 422 void visitDoStatement(DoStatement node) { | |
| 423 checkBoolean(node.condition); | |
| 424 node.visitChildren(this); | |
| 425 } | |
| 426 | |
| 427 @override | |
| 428 void visitWhileStatement(WhileStatement node) { | |
| 429 checkBoolean(node.condition); | |
| 430 node.visitChildren(this); | |
| 431 } | |
| 432 | |
| 433 @override | |
| 434 void visitSwitchStatement(SwitchStatement node) { | |
| 435 // SwitchStatement defines a boolean conversion to check the result of the | |
| 436 // case value == the switch value, but in dev_compiler we require a boolean | |
| 437 // return type from an overridden == operator (because Object.==), so | |
| 438 // checking in SwitchStatement shouldn't be necessary. | |
| 439 node.visitChildren(this); | |
| 440 } | |
| 441 | |
| 442 @override | |
| 443 void visitListLiteral(ListLiteral node) { | |
| 444 var type = rules.provider.dynamicType; | |
| 445 if (node.typeArguments != null) { | |
| 446 var targs = node.typeArguments.arguments; | |
| 447 if (targs.length > 0) type = targs[0].type; | |
| 448 } | |
| 449 var elements = node.elements; | |
| 450 for (int i = 0; i < elements.length; i++) { | |
| 451 checkArgument(elements[i], type); | |
| 452 } | |
| 453 super.visitListLiteral(node); | |
| 454 } | |
| 455 | |
| 456 @override | |
| 457 void visitMapLiteral(MapLiteral node) { | |
| 458 var ktype = rules.provider.dynamicType; | |
| 459 var vtype = rules.provider.dynamicType; | |
| 460 if (node.typeArguments != null) { | |
| 461 var targs = node.typeArguments.arguments; | |
| 462 if (targs.length > 0) ktype = targs[0].type; | |
| 463 if (targs.length > 1) vtype = targs[1].type; | |
| 464 } | |
| 465 var entries = node.entries; | |
| 466 for (int i = 0; i < entries.length; i++) { | |
| 467 var entry = entries[i]; | |
| 468 checkArgument(entry.key, ktype); | |
| 469 checkArgument(entry.value, vtype); | |
| 470 } | |
| 471 super.visitMapLiteral(node); | |
| 472 } | |
| 473 | |
| 474 // Check invocations | |
| 475 void checkArgumentList(ArgumentList node, FunctionType type) { | |
| 476 NodeList<Expression> list = node.arguments; | |
| 477 int len = list.length; | |
| 478 for (int i = 0; i < len; ++i) { | |
| 479 Expression arg = list[i]; | |
| 480 ParameterElement element = arg.staticParameterElement; | |
| 481 if (element == null) { | |
| 482 if (type.parameters.length < len) { | |
| 483 // We found an argument mismatch, the analyzer will report this too, | |
| 484 // so no need to insert an error for this here. | |
| 485 continue; | |
| 486 } | |
| 487 element = type.parameters[i]; | |
| 488 // TODO(vsm): When can this happen? | |
| 489 assert(element != null); | |
| 490 } | |
| 491 DartType expectedType = rules.elementType(element); | |
| 492 if (expectedType == null) expectedType = rules.provider.dynamicType; | |
| 493 checkArgument(arg, expectedType); | |
| 494 } | |
| 495 } | |
| 496 | |
| 497 void checkArgument(Expression arg, DartType expectedType) { | |
| 498 // Preserve named argument structure, so their immediate parent is the | |
| 499 // method invocation. | |
| 500 if (arg is NamedExpression) { | |
| 501 arg = (arg as NamedExpression).expression; | |
| 502 } | |
| 503 checkAssignment(arg, expectedType); | |
| 504 } | |
| 505 | |
| 506 void checkFunctionApplication( | |
| 507 Expression node, Expression f, ArgumentList list) { | |
| 508 if (rules.isDynamicCall(f)) { | |
| 509 // If f is Function and this is a method invocation, we should have | |
| 510 // gotten an analyzer error, so no need to issue another error. | |
| 511 _recordDynamicInvoke(node, f); | |
| 512 } else { | |
| 513 checkArgumentList(list, rules.getTypeAsCaller(f)); | |
| 514 } | |
| 515 } | |
| 516 | |
| 517 @override | |
| 518 visitMethodInvocation(MethodInvocation node) { | |
| 519 var target = node.realTarget; | |
| 520 if (rules.isDynamicTarget(target) && | |
| 521 !_isObjectMethod(node, node.methodName)) { | |
| 522 _recordDynamicInvoke(node, target); | |
| 523 | |
| 524 // Mark the tear-off as being dynamic, too. This lets us distinguish | |
| 525 // cases like: | |
| 526 // | |
| 527 // dynamic d; | |
| 528 // d.someMethod(...); // the whole method call must be a dynamic send. | |
| 529 // | |
| 530 // ... from case like: | |
| 531 // | |
| 532 // SomeType s; | |
| 533 // s.someDynamicField(...); // static get, followed by dynamic call. | |
| 534 // | |
| 535 // The first case is handled here, the second case is handled below when | |
| 536 // we call [checkFunctionApplication]. | |
| 537 DynamicInvoke.set(node.methodName, true); | |
| 538 } else { | |
| 539 checkFunctionApplication(node, node.methodName, node.argumentList); | |
| 540 } | |
| 541 node.visitChildren(this); | |
| 542 } | |
| 543 | |
| 544 @override | |
| 545 void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { | |
| 546 checkFunctionApplication(node, node.function, node.argumentList); | |
| 547 node.visitChildren(this); | |
| 548 } | |
| 549 | |
| 550 @override | |
| 551 void visitRedirectingConstructorInvocation( | |
| 552 RedirectingConstructorInvocation node) { | |
| 553 var type = node.staticElement.type; | |
| 554 checkArgumentList(node.argumentList, type); | |
| 555 node.visitChildren(this); | |
| 556 } | |
| 557 | |
| 558 @override | |
| 559 void visitSuperConstructorInvocation(SuperConstructorInvocation node) { | |
| 560 var element = node.staticElement; | |
| 561 if (element == null) { | |
| 562 _recordMessage(new MissingTypeError(node)); | |
| 563 } else { | |
| 564 var type = node.staticElement.type; | |
| 565 checkArgumentList(node.argumentList, type); | |
| 566 } | |
| 567 node.visitChildren(this); | |
| 568 } | |
| 569 | |
| 570 void _checkReturnOrYield(Expression expression, AstNode node, | |
| 571 {bool yieldStar: false}) { | |
| 572 var body = node.getAncestor((n) => n is FunctionBody); | |
| 573 var type = rules.getExpectedReturnType(body, yieldStar: yieldStar); | |
| 574 if (type == null) { | |
| 575 // We have a type mismatch: the async/async*/sync* modifier does | |
| 576 // not match the return or yield type. We should have already gotten an | |
| 577 // analyzer error in this case. | |
| 578 return; | |
| 579 } | |
| 580 // TODO(vsm): Enforce void or dynamic (to void?) when expression is null. | |
| 581 if (expression != null) checkAssignment(expression, type); | |
| 582 } | |
| 583 | |
| 584 @override | |
| 585 void visitExpressionFunctionBody(ExpressionFunctionBody node) { | |
| 586 _checkReturnOrYield(node.expression, node); | |
| 587 node.visitChildren(this); | |
| 588 } | |
| 589 | |
| 590 @override | |
| 591 void visitReturnStatement(ReturnStatement node) { | |
| 592 _checkReturnOrYield(node.expression, node); | |
| 593 node.visitChildren(this); | |
| 594 } | |
| 595 | |
| 596 @override | |
| 597 void visitYieldStatement(YieldStatement node) { | |
| 598 _checkReturnOrYield(node.expression, node, yieldStar: node.star != null); | |
| 599 node.visitChildren(this); | |
| 600 } | |
| 601 | |
| 602 @override | |
| 603 void visitPropertyAccess(PropertyAccess node) { | |
| 604 var target = node.realTarget; | |
| 605 if (rules.isDynamicTarget(target) && | |
| 606 !_isObjectProperty(target, node.propertyName)) { | |
| 607 _recordDynamicInvoke(node, target); | |
| 608 } | |
| 609 node.visitChildren(this); | |
| 610 } | |
| 611 | |
| 612 @override | |
| 613 void visitPrefixedIdentifier(PrefixedIdentifier node) { | |
| 614 final target = node.prefix; | |
| 615 if (rules.isDynamicTarget(target) && | |
| 616 !_isObjectProperty(target, node.identifier)) { | |
| 617 _recordDynamicInvoke(node, target); | |
| 618 } | |
| 619 node.visitChildren(this); | |
| 620 } | |
| 621 | |
| 622 @override | |
| 623 void visitDefaultFormalParameter(DefaultFormalParameter node) { | |
| 624 // Check that defaults have the proper subtype. | |
| 625 var parameter = node.parameter; | |
| 626 var parameterType = rules.elementType(parameter.element); | |
| 627 assert(parameterType != null); | |
| 628 var defaultValue = node.defaultValue; | |
| 629 if (defaultValue != null) { | |
| 630 checkAssignment(defaultValue, parameterType); | |
| 631 } | |
| 632 | |
| 633 node.visitChildren(this); | |
| 634 } | |
| 635 | |
| 636 @override | |
| 637 void visitFieldFormalParameter(FieldFormalParameter node) { | |
| 638 var element = node.element; | |
| 639 var typeName = node.type; | |
| 640 if (typeName != null) { | |
| 641 var type = rules.elementType(element); | |
| 642 var fieldElement = | |
| 643 node.identifier.staticElement as FieldFormalParameterElement; | |
| 644 var fieldType = rules.elementType(fieldElement.field); | |
| 645 if (!rules.isSubTypeOf(type, fieldType)) { | |
| 646 var staticInfo = | |
| 647 new InvalidParameterDeclaration(rules, node, fieldType); | |
| 648 _recordMessage(staticInfo); | |
| 649 } | |
| 650 } | |
| 651 node.visitChildren(this); | |
| 652 } | |
| 653 | |
| 654 @override | |
| 655 void visitInstanceCreationExpression(InstanceCreationExpression node) { | |
| 656 var arguments = node.argumentList; | |
| 657 var element = node.staticElement; | |
| 658 if (element != null) { | |
| 659 var type = rules.elementType(node.staticElement); | |
| 660 checkArgumentList(arguments, type); | |
| 661 } else { | |
| 662 _recordMessage(new MissingTypeError(node)); | |
| 663 } | |
| 664 node.visitChildren(this); | |
| 665 } | |
| 666 | |
| 667 @override | |
| 668 void visitVariableDeclarationList(VariableDeclarationList node) { | |
| 669 TypeName type = node.type; | |
| 670 if (type == null) { | |
| 671 // No checks are needed when the type is var. Although internally the | |
| 672 // typing rules may have inferred a more precise type for the variable | |
| 673 // based on the initializer. | |
| 674 } else { | |
| 675 var dartType = getType(type); | |
| 676 for (VariableDeclaration variable in node.variables) { | |
| 677 var initializer = variable.initializer; | |
| 678 if (initializer != null) { | |
| 679 checkAssignment(initializer, dartType); | |
| 680 } | |
| 681 } | |
| 682 } | |
| 683 node.visitChildren(this); | |
| 684 } | |
| 685 | |
| 686 void _checkRuntimeTypeCheck(AstNode node, TypeName typeName) { | |
| 687 var type = getType(typeName); | |
| 688 if (!rules.isGroundType(type)) { | |
| 689 _recordMessage(new NonGroundTypeCheckInfo(node, type)); | |
| 690 } | |
| 691 } | |
| 692 | |
| 693 @override | |
| 694 void visitAsExpression(AsExpression node) { | |
| 695 // We could do the same check as the IsExpression below, but that is | |
| 696 // potentially too conservative. Instead, at runtime, we must fail hard | |
| 697 // if the Dart as and the DDC as would return different values. | |
| 698 node.visitChildren(this); | |
| 699 } | |
| 700 | |
| 701 @override | |
| 702 void visitIsExpression(IsExpression node) { | |
| 703 _checkRuntimeTypeCheck(node, node.type); | |
| 704 node.visitChildren(this); | |
| 705 } | |
| 706 | |
| 707 @override | |
| 708 void visitPrefixExpression(PrefixExpression node) { | |
| 709 if (node.operator.type == TokenType.BANG) { | |
| 710 checkBoolean(node.operand); | |
| 711 } else { | |
| 712 _checkUnary(node); | |
| 713 } | |
| 714 node.visitChildren(this); | |
| 715 } | |
| 716 | |
| 717 @override | |
| 718 void visitPostfixExpression(PostfixExpression node) { | |
| 719 _checkUnary(node); | |
| 720 node.visitChildren(this); | |
| 721 } | |
| 722 | |
| 723 void _checkUnary(/*PrefixExpression|PostfixExpression*/ node) { | |
| 724 var op = node.operator; | |
| 725 if (op.isUserDefinableOperator || | |
| 726 op.type == TokenType.PLUS_PLUS || | |
| 727 op.type == TokenType.MINUS_MINUS) { | |
| 728 if (rules.isDynamicTarget(node.operand)) { | |
| 729 _recordDynamicInvoke(node, node.operand); | |
| 730 } | |
| 731 // For ++ and --, even if it is not dynamic, we still need to check | |
| 732 // that the user defined method accepts an `int` as the RHS. | |
| 733 // We assume Analyzer has done this already. | |
| 734 } | |
| 735 } | |
| 736 | |
| 737 @override | |
| 738 void visitBinaryExpression(BinaryExpression node) { | |
| 739 var op = node.operator; | |
| 740 if (op.isUserDefinableOperator) { | |
| 741 if (rules.isDynamicTarget(node.leftOperand)) { | |
| 742 // Dynamic invocation | |
| 743 // TODO(vsm): Move this logic to the resolver? | |
| 744 if (op.type != TokenType.EQ_EQ && op.type != TokenType.BANG_EQ) { | |
| 745 _recordDynamicInvoke(node, node.leftOperand); | |
| 746 } | |
| 747 } else { | |
| 748 var element = node.staticElement; | |
| 749 // Method invocation. | |
| 750 if (element is MethodElement) { | |
| 751 var type = element.type; | |
| 752 // Analyzer should enforce number of parameter types, but check in | |
| 753 // case we have erroneous input. | |
| 754 if (type.normalParameterTypes.isNotEmpty) { | |
| 755 checkArgument(node.rightOperand, type.normalParameterTypes[0]); | |
| 756 } | |
| 757 } else { | |
| 758 // TODO(vsm): Assert that the analyzer found an error here? | |
| 759 } | |
| 760 } | |
| 761 } else { | |
| 762 // Non-method operator. | |
| 763 switch (op.type) { | |
| 764 case TokenType.AMPERSAND_AMPERSAND: | |
| 765 case TokenType.BAR_BAR: | |
| 766 checkBoolean(node.leftOperand); | |
| 767 checkBoolean(node.rightOperand); | |
| 768 break; | |
| 769 case TokenType.BANG_EQ: | |
| 770 break; | |
| 771 case TokenType.QUESTION_QUESTION: | |
| 772 break; | |
| 773 default: | |
| 774 assert(false); | |
| 775 } | |
| 776 } | |
| 777 node.visitChildren(this); | |
| 778 } | |
| 779 | |
| 780 @override | |
| 781 void visitConditionalExpression(ConditionalExpression node) { | |
| 782 checkBoolean(node.condition); | |
| 783 node.visitChildren(this); | |
| 784 } | |
| 785 | |
| 786 @override | |
| 787 void visitIndexExpression(IndexExpression node) { | |
| 788 var target = node.realTarget; | |
| 789 if (rules.isDynamicTarget(target)) { | |
| 790 _recordDynamicInvoke(node, target); | |
| 791 } else { | |
| 792 var element = node.staticElement; | |
| 793 if (element is MethodElement) { | |
| 794 var type = element.type; | |
| 795 // Analyzer should enforce number of parameter types, but check in | |
| 796 // case we have erroneous input. | |
| 797 if (type.normalParameterTypes.isNotEmpty) { | |
| 798 checkArgument(node.index, type.normalParameterTypes[0]); | |
| 799 } | |
| 800 } else { | |
| 801 // TODO(vsm): Assert that the analyzer found an error here? | |
| 802 } | |
| 803 } | |
| 804 node.visitChildren(this); | |
| 805 } | |
| 806 | |
| 807 DartType getType(TypeName name) { | |
| 808 return (name == null) ? rules.provider.dynamicType : name.type; | |
| 809 } | |
| 810 | |
| 811 /// Analyzer checks boolean conversions, but we need to check too, because | |
| 812 /// it uses the default assignability rules that allow `dynamic` and `Object` | |
| 813 /// to be assigned to bool with no message. | |
| 814 void checkBoolean(Expression expr) => | |
| 815 checkAssignment(expr, rules.provider.boolType); | |
| 816 | |
| 817 void checkAssignment(Expression expr, DartType type) { | |
| 818 if (expr is ParenthesizedExpression) { | |
| 819 checkAssignment(expr.expression, type); | |
| 820 } else { | |
| 821 _recordMessage(rules.checkAssignment(expr, type)); | |
| 822 } | |
| 823 } | |
| 824 | |
| 825 DartType _specializedBinaryReturnType( | |
| 826 TokenType op, DartType t1, DartType t2, DartType normalReturnType) { | |
| 827 // This special cases binary return types as per 16.26 and 16.27 of the | |
| 828 // Dart language spec. | |
| 829 switch (op) { | |
| 830 case TokenType.PLUS: | |
| 831 case TokenType.MINUS: | |
| 832 case TokenType.STAR: | |
| 833 case TokenType.TILDE_SLASH: | |
| 834 case TokenType.PERCENT: | |
| 835 case TokenType.PLUS_EQ: | |
| 836 case TokenType.MINUS_EQ: | |
| 837 case TokenType.STAR_EQ: | |
| 838 case TokenType.TILDE_SLASH_EQ: | |
| 839 case TokenType.PERCENT_EQ: | |
| 840 if (t1 == rules.provider.intType && | |
| 841 t2 == rules.provider.intType) return t1; | |
| 842 if (t1 == rules.provider.doubleType && | |
| 843 t2 == rules.provider.doubleType) return t1; | |
| 844 // This particular combo is not spelled out in the spec, but all | |
| 845 // implementations and analyzer seem to follow this. | |
| 846 if (t1 == rules.provider.doubleType && | |
| 847 t2 == rules.provider.intType) return t1; | |
| 848 } | |
| 849 return normalReturnType; | |
| 850 } | |
| 851 | |
| 852 void _checkCompoundAssignment(AssignmentExpression expr) { | |
| 853 var op = expr.operator.type; | |
| 854 assert(op.isAssignmentOperator && op != TokenType.EQ); | |
| 855 var methodElement = expr.staticElement; | |
| 856 if (methodElement == null) { | |
| 857 // Dynamic invocation | |
| 858 _recordDynamicInvoke(expr, expr.leftHandSide); | |
| 859 } else { | |
| 860 // Sanity check the operator | |
| 861 assert(methodElement.isOperator); | |
| 862 var functionType = methodElement.type; | |
| 863 var paramTypes = functionType.normalParameterTypes; | |
| 864 assert(paramTypes.length == 1); | |
| 865 assert(functionType.namedParameterTypes.isEmpty); | |
| 866 assert(functionType.optionalParameterTypes.isEmpty); | |
| 867 | |
| 868 // Check the lhs type | |
| 869 var staticInfo; | |
| 870 var rhsType = _getStaticType(expr.rightHandSide); | |
| 871 var lhsType = _getStaticType(expr.leftHandSide); | |
| 872 var returnType = _specializedBinaryReturnType( | |
| 873 op, lhsType, rhsType, functionType.returnType); | |
| 874 | |
| 875 if (!rules.isSubTypeOf(returnType, lhsType)) { | |
| 876 final numType = rules.provider.numType; | |
| 877 // Try to fix up the numerical case if possible. | |
| 878 if (rules.isSubTypeOf(lhsType, numType) && | |
| 879 rules.isSubTypeOf(lhsType, rhsType)) { | |
| 880 // This is also slightly different from spec, but allows us to keep | |
| 881 // compound operators in the int += num and num += dynamic cases. | |
| 882 staticInfo = DownCast.create( | |
| 883 rules, expr.rightHandSide, Coercion.cast(rhsType, lhsType)); | |
| 884 rhsType = lhsType; | |
| 885 } else { | |
| 886 // Static type error | |
| 887 staticInfo = new StaticTypeError(rules, expr, lhsType); | |
| 888 } | |
| 889 _recordMessage(staticInfo); | |
| 890 } | |
| 891 | |
| 892 // Check the rhs type | |
| 893 if (staticInfo is! CoercionInfo) { | |
| 894 var paramType = paramTypes.first; | |
| 895 staticInfo = rules.checkAssignment(expr.rightHandSide, paramType); | |
| 896 _recordMessage(staticInfo); | |
| 897 } | |
| 898 } | |
| 899 } | |
| 900 | |
| 901 bool _isObjectGetter(Expression target, SimpleIdentifier id) { | |
| 902 PropertyAccessorElement element = | |
| 903 rules.provider.objectType.element.getGetter(id.name); | |
| 904 return (element != null && !element.isStatic); | |
| 905 } | |
| 906 | |
| 907 bool _isObjectMethod(Expression target, SimpleIdentifier id) { | |
| 908 MethodElement element = | |
| 909 rules.provider.objectType.element.getMethod(id.name); | |
| 910 return (element != null && !element.isStatic); | |
| 911 } | |
| 912 | |
| 913 bool _isObjectProperty(Expression target, SimpleIdentifier id) { | |
| 914 return _isObjectGetter(target, id) || _isObjectMethod(target, id); | |
| 915 } | |
| 916 | |
| 917 DartType _getStaticType(Expression expr) { | |
| 918 var type = expr.staticType; | |
| 919 if (type == null) { | |
| 920 reporter.onError(new MissingTypeError(expr).toAnalysisError()); | |
| 921 } | |
| 922 return type ?? rules.provider.dynamicType; | |
| 923 } | |
| 924 | |
| 925 void _recordDynamicInvoke(AstNode node, AstNode target) { | |
| 926 reporter.onError(new DynamicInvoke(rules, node).toAnalysisError()); | |
| 927 // TODO(jmesserly): we may eventually want to record if the whole operation | |
| 928 // (node) was dynamic, rather than the target, but this is an easier fit | |
| 929 // with what we used to do. | |
| 930 DynamicInvoke.set(target, true); | |
| 931 } | |
| 932 | |
| 933 void _recordMessage(StaticInfo info) { | |
| 934 if (info == null) return; | |
| 935 var error = info.toAnalysisError(); | |
| 936 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; | |
| 937 reporter.onError(error); | |
| 938 | |
| 939 if (info is CoercionInfo) { | |
| 940 // TODO(jmesserly): if we're run again on the same AST, we'll produce the | |
| 941 // same annotations. This should be harmless. This might go away once | |
| 942 // CodeChecker is integrated better with analyzer, as it will know that | |
| 943 // checking has already been performed. | |
| 944 // assert(CoercionInfo.get(info.node) == null); | |
| 945 CoercionInfo.set(info.node, info); | |
| 946 } | |
| 947 } | |
| 948 } | |
| OLD | NEW |