OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 // TODO(jmesserly): this was ported from package:dev_compiler, and needs to be |
| 6 // refactored to fit into analyzer. |
| 7 library analyzer.src.task.strong.checker; |
| 8 |
| 9 import 'package:analyzer/analyzer.dart'; |
| 10 import 'package:analyzer/src/generated/ast.dart'; |
| 11 import 'package:analyzer/src/generated/element.dart'; |
| 12 import 'package:analyzer/src/generated/scanner.dart' show Token, TokenType; |
| 13 |
| 14 import 'info.dart'; |
| 15 import 'rules.dart'; |
| 16 |
| 17 /// Checks for overriding declarations of fields and methods. This is used to |
| 18 /// check overrides between classes and superclasses, interfaces, and mixin |
| 19 /// applications. |
| 20 class _OverrideChecker { |
| 21 bool _failure = false; |
| 22 final TypeRules _rules; |
| 23 final AnalysisErrorListener _reporter; |
| 24 |
| 25 _OverrideChecker(this._rules, this._reporter); |
| 26 |
| 27 void check(ClassDeclaration node) { |
| 28 if (node.element.type.isObject) return; |
| 29 _checkSuperOverrides(node); |
| 30 _checkMixinApplicationOverrides(node); |
| 31 _checkAllInterfaceOverrides(node); |
| 32 } |
| 33 |
| 34 /// Check overrides from mixin applications themselves. For example, in: |
| 35 /// |
| 36 /// A extends B with E, F |
| 37 /// |
| 38 /// we check: |
| 39 /// |
| 40 /// B & E against B (equivalently how E overrides B) |
| 41 /// B & E & F against B & E (equivalently how F overrides both B and E) |
| 42 void _checkMixinApplicationOverrides(ClassDeclaration node) { |
| 43 var type = node.element.type; |
| 44 var parent = type.superclass; |
| 45 var mixins = type.mixins; |
| 46 |
| 47 // Check overrides from applying mixins |
| 48 for (int i = 0; i < mixins.length; i++) { |
| 49 var seen = new Set<String>(); |
| 50 var current = mixins[i]; |
| 51 var errorLocation = node.withClause.mixinTypes[i]; |
| 52 for (int j = i - 1; j >= 0; j--) { |
| 53 _checkIndividualOverridesFromType( |
| 54 current, mixins[j], errorLocation, seen); |
| 55 } |
| 56 _checkIndividualOverridesFromType(current, parent, errorLocation, seen); |
| 57 } |
| 58 } |
| 59 |
| 60 /// Check overrides between a class and its superclasses and mixins. For |
| 61 /// example, in: |
| 62 /// |
| 63 /// A extends B with E, F |
| 64 /// |
| 65 /// we check A against B, B super classes, E, and F. |
| 66 /// |
| 67 /// Internally we avoid reporting errors twice and we visit classes bottom up |
| 68 /// to ensure we report the most immediate invalid override first. For |
| 69 /// example, in the following code we'll report that `Test` has an invalid |
| 70 /// override with respect to `Parent` (as opposed to an invalid override with |
| 71 /// respect to `Grandparent`): |
| 72 /// |
| 73 /// class Grandparent { |
| 74 /// m(A a) {} |
| 75 /// } |
| 76 /// class Parent extends Grandparent { |
| 77 /// m(A a) {} |
| 78 /// } |
| 79 /// class Test extends Parent { |
| 80 /// m(B a) {} // invalid override |
| 81 /// } |
| 82 void _checkSuperOverrides(ClassDeclaration node) { |
| 83 var seen = new Set<String>(); |
| 84 var current = node.element.type; |
| 85 var visited = new Set<InterfaceType>(); |
| 86 do { |
| 87 visited.add(current); |
| 88 current.mixins.reversed |
| 89 .forEach((m) => _checkIndividualOverridesFromClass(node, m, seen)); |
| 90 _checkIndividualOverridesFromClass(node, current.superclass, seen); |
| 91 current = current.superclass; |
| 92 } while (!current.isObject && !visited.contains(current)); |
| 93 } |
| 94 |
| 95 /// Checks that implementations correctly override all reachable interfaces. |
| 96 /// In particular, we need to check these overrides for the definitions in |
| 97 /// the class itself and each its superclasses. If a superclass is not |
| 98 /// abstract, then we can skip its transitive interfaces. For example, in: |
| 99 /// |
| 100 /// B extends C implements G |
| 101 /// A extends B with E, F implements H, I |
| 102 /// |
| 103 /// we check: |
| 104 /// |
| 105 /// C against G, H, and I |
| 106 /// B against G, H, and I |
| 107 /// E against H and I // no check against G because B is a concrete class |
| 108 /// F against H and I |
| 109 /// A against H and I |
| 110 void _checkAllInterfaceOverrides(ClassDeclaration node) { |
| 111 var seen = new Set<String>(); |
| 112 // Helper function to collect all reachable interfaces. |
| 113 find(InterfaceType interfaceType, Set result) { |
| 114 if (interfaceType == null || interfaceType.isObject) return; |
| 115 if (result.contains(interfaceType)) return; |
| 116 result.add(interfaceType); |
| 117 find(interfaceType.superclass, result); |
| 118 interfaceType.mixins.forEach((i) => find(i, result)); |
| 119 interfaceType.interfaces.forEach((i) => find(i, result)); |
| 120 } |
| 121 |
| 122 // Check all interfaces reachable from the `implements` clause in the |
| 123 // current class against definitions here and in superclasses. |
| 124 var localInterfaces = new Set<InterfaceType>(); |
| 125 var type = node.element.type; |
| 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 } |
| 253 } else { |
| 254 if ((member as MethodDeclaration).isStatic) continue; |
| 255 var method = (member as MethodDeclaration).element; |
| 256 if (seen.contains(method.name)) continue; |
| 257 if (_checkSingleOverride(method, baseType, member, member)) { |
| 258 seen.add(method.name); |
| 259 } |
| 260 } |
| 261 } |
| 262 } |
| 263 |
| 264 /// Checks that [element] correctly overrides its corresponding member in |
| 265 /// [type]. Returns `true` if an override was found, that is, if [element] has |
| 266 /// a corresponding member in [type] that it overrides. |
| 267 /// |
| 268 /// The [errorLocation] is a node where the error is reported. For example, a |
| 269 /// bad override of a method in a class with respect to its superclass is |
| 270 /// reported directly at the method declaration. However, invalid overrides |
| 271 /// from base classes to interfaces, mixins to the base they are applied to, |
| 272 /// or mixins to interfaces are reported at the class declaration, since the |
| 273 /// base class or members on their own were not incorrect, only combining them |
| 274 /// with the interface was problematic. For example, these are example error |
| 275 /// locations in these cases: |
| 276 /// |
| 277 /// error: base class introduces an invalid override. The type of B.foo is |
| 278 /// not a subtype of E.foo: |
| 279 /// class A extends B implements E { ... } |
| 280 /// ^^^^^^^^^ |
| 281 /// |
| 282 /// error: mixin introduces an invalid override. The type of C.foo is not |
| 283 /// a subtype of E.foo: |
| 284 /// class A extends B with C implements E { ... } |
| 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 |
| 347 @override |
| 348 void visitComment(Comment node) { |
| 349 // skip, no need to do typechecking inside comments (they may contain |
| 350 // comment references which would require resolution). |
| 351 } |
| 352 |
| 353 @override |
| 354 void visitClassDeclaration(ClassDeclaration node) { |
| 355 _overrideChecker.check(node); |
| 356 super.visitClassDeclaration(node); |
| 357 } |
| 358 |
| 359 @override |
| 360 void visitAssignmentExpression(AssignmentExpression node) { |
| 361 var token = node.operator; |
| 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); |
| 369 } |
| 370 |
| 371 /// Check constructor declaration to ensure correct super call placement. |
| 372 @override |
| 373 void visitConstructorDeclaration(ConstructorDeclaration node) { |
| 374 node.visitChildren(this); |
| 375 |
| 376 final init = node.initializers; |
| 377 for (int i = 0, last = init.length - 1; i < last; i++) { |
| 378 final node = init[i]; |
| 379 if (node is SuperConstructorInvocation) { |
| 380 _recordMessage(new InvalidSuperInvocation(node)); |
| 381 } |
| 382 } |
| 383 } |
| 384 |
| 385 @override |
| 386 void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| 387 var field = node.fieldName; |
| 388 var element = field.staticElement; |
| 389 DartType staticType = rules.elementType(element); |
| 390 checkAssignment(node.expression, staticType); |
| 391 node.visitChildren(this); |
| 392 } |
| 393 |
| 394 @override |
| 395 void visitForEachStatement(ForEachStatement node) { |
| 396 // Check that the expression is an Iterable. |
| 397 var expr = node.iterable; |
| 398 var iterableType = node.awaitKeyword != null |
| 399 ? rules.provider.streamType |
| 400 : rules.provider.iterableType; |
| 401 var loopVariable = node.identifier != null |
| 402 ? node.identifier |
| 403 : node.loopVariable?.identifier; |
| 404 if (loopVariable != null) { |
| 405 var iteratorType = loopVariable.staticType; |
| 406 var checkedType = iterableType.substitute4([iteratorType]); |
| 407 checkAssignment(expr, checkedType); |
| 408 } |
| 409 node.visitChildren(this); |
| 410 } |
| 411 |
| 412 @override |
| 413 void visitForStatement(ForStatement node) { |
| 414 if (node.condition != null) { |
| 415 checkBoolean(node.condition); |
| 416 } |
| 417 node.visitChildren(this); |
| 418 } |
| 419 |
| 420 @override |
| 421 void visitIfStatement(IfStatement node) { |
| 422 checkBoolean(node.condition); |
| 423 node.visitChildren(this); |
| 424 } |
| 425 |
| 426 @override |
| 427 void visitDoStatement(DoStatement node) { |
| 428 checkBoolean(node.condition); |
| 429 node.visitChildren(this); |
| 430 } |
| 431 |
| 432 @override |
| 433 void visitWhileStatement(WhileStatement node) { |
| 434 checkBoolean(node.condition); |
| 435 node.visitChildren(this); |
| 436 } |
| 437 |
| 438 @override |
| 439 void visitSwitchStatement(SwitchStatement node) { |
| 440 // SwitchStatement defines a boolean conversion to check the result of the |
| 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); |
| 445 } |
| 446 |
| 447 @override |
| 448 void visitListLiteral(ListLiteral node) { |
| 449 var type = rules.provider.dynamicType; |
| 450 if (node.typeArguments != null) { |
| 451 var targs = node.typeArguments.arguments; |
| 452 if (targs.length > 0) type = targs[0].type; |
| 453 } |
| 454 var elements = node.elements; |
| 455 for (int i = 0; i < elements.length; i++) { |
| 456 checkArgument(elements[i], type); |
| 457 } |
| 458 super.visitListLiteral(node); |
| 459 } |
| 460 |
| 461 @override |
| 462 void visitMapLiteral(MapLiteral node) { |
| 463 var ktype = rules.provider.dynamicType; |
| 464 var vtype = rules.provider.dynamicType; |
| 465 if (node.typeArguments != null) { |
| 466 var targs = node.typeArguments.arguments; |
| 467 if (targs.length > 0) ktype = targs[0].type; |
| 468 if (targs.length > 1) vtype = targs[1].type; |
| 469 } |
| 470 var entries = node.entries; |
| 471 for (int i = 0; i < entries.length; i++) { |
| 472 var entry = entries[i]; |
| 473 checkArgument(entry.key, ktype); |
| 474 checkArgument(entry.value, vtype); |
| 475 } |
| 476 super.visitMapLiteral(node); |
| 477 } |
| 478 |
| 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 |
| 523 visitMethodInvocation(MethodInvocation node) { |
| 524 var target = node.realTarget; |
| 525 if (rules.isDynamicTarget(target) && |
| 526 !_isObjectMethod(node, node.methodName)) { |
| 527 _recordDynamicInvoke(node, target); |
| 528 |
| 529 // Mark the tear-off as being dynamic, too. This lets us distinguish |
| 530 // cases like: |
| 531 // |
| 532 // dynamic d; |
| 533 // d.someMethod(...); // the whole method call must be a dynamic send. |
| 534 // |
| 535 // ... from case like: |
| 536 // |
| 537 // SomeType s; |
| 538 // s.someDynamicField(...); // static get, followed by dynamic call. |
| 539 // |
| 540 // The first case is handled here, the second case is handled below when |
| 541 // we call [checkFunctionApplication]. |
| 542 DynamicInvoke.set(node.methodName, true); |
| 543 } else { |
| 544 checkFunctionApplication(node, node.methodName, node.argumentList); |
| 545 } |
| 546 node.visitChildren(this); |
| 547 } |
| 548 |
| 549 @override |
| 550 void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
| 551 checkFunctionApplication(node, node.function, node.argumentList); |
| 552 node.visitChildren(this); |
| 553 } |
| 554 |
| 555 @override |
| 556 void visitRedirectingConstructorInvocation( |
| 557 RedirectingConstructorInvocation node) { |
| 558 var type = node.staticElement.type; |
| 559 checkArgumentList(node.argumentList, type); |
| 560 node.visitChildren(this); |
| 561 } |
| 562 |
| 563 @override |
| 564 void visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| 565 var element = node.staticElement; |
| 566 if (element != null) { |
| 567 var type = node.staticElement.type; |
| 568 checkArgumentList(node.argumentList, type); |
| 569 } |
| 570 node.visitChildren(this); |
| 571 } |
| 572 |
| 573 void _checkReturnOrYield(Expression expression, AstNode node, |
| 574 {bool yieldStar: false}) { |
| 575 var body = node.getAncestor((n) => n is FunctionBody); |
| 576 var type = rules.getExpectedReturnType(body, yieldStar: yieldStar); |
| 577 if (type == null) { |
| 578 // 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 |
| 580 // analyzer error in this case. |
| 581 return; |
| 582 } |
| 583 // TODO(vsm): Enforce void or dynamic (to void?) when expression is null. |
| 584 if (expression != null) checkAssignment(expression, type); |
| 585 } |
| 586 |
| 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) { |
| 688 var type = getType(typeName); |
| 689 if (!rules.isGroundType(type)) { |
| 690 _recordMessage(new NonGroundTypeCheckInfo(node, type)); |
| 691 } |
| 692 } |
| 693 |
| 694 @override |
| 695 void visitAsExpression(AsExpression node) { |
| 696 // We could do the same check as the IsExpression below, but that is |
| 697 // potentially too conservative. Instead, at runtime, we must fail hard |
| 698 // if the Dart as and the DDC as would return different values. |
| 699 node.visitChildren(this); |
| 700 } |
| 701 |
| 702 @override |
| 703 void visitIsExpression(IsExpression node) { |
| 704 _checkRuntimeTypeCheck(node, node.type); |
| 705 node.visitChildren(this); |
| 706 } |
| 707 |
| 708 @override |
| 709 void visitPrefixExpression(PrefixExpression node) { |
| 710 if (node.operator.type == TokenType.BANG) { |
| 711 checkBoolean(node.operand); |
| 712 } else { |
| 713 _checkUnary(node); |
| 714 } |
| 715 node.visitChildren(this); |
| 716 } |
| 717 |
| 718 @override |
| 719 void visitPostfixExpression(PostfixExpression node) { |
| 720 _checkUnary(node); |
| 721 node.visitChildren(this); |
| 722 } |
| 723 |
| 724 void _checkUnary(/*PrefixExpression|PostfixExpression*/ node) { |
| 725 var op = node.operator; |
| 726 if (op.isUserDefinableOperator || |
| 727 op.type == TokenType.PLUS_PLUS || |
| 728 op.type == TokenType.MINUS_MINUS) { |
| 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 } |
| 762 } else { |
| 763 // Non-method operator. |
| 764 switch (op.type) { |
| 765 case TokenType.AMPERSAND_AMPERSAND: |
| 766 case TokenType.BAR_BAR: |
| 767 checkBoolean(node.leftOperand); |
| 768 checkBoolean(node.rightOperand); |
| 769 break; |
| 770 case TokenType.BANG_EQ: |
| 771 break; |
| 772 case TokenType.QUESTION_QUESTION: |
| 773 break; |
| 774 default: |
| 775 assert(false); |
| 776 } |
| 777 } |
| 778 node.visitChildren(this); |
| 779 } |
| 780 |
| 781 @override |
| 782 void visitConditionalExpression(ConditionalExpression node) { |
| 783 checkBoolean(node.condition); |
| 784 node.visitChildren(this); |
| 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 { |
| 793 var element = node.staticElement; |
| 794 if (element is MethodElement) { |
| 795 var type = element.type; |
| 796 // Analyzer should enforce number of parameter types, but check in |
| 797 // case we have erroneous input. |
| 798 if (type.normalParameterTypes.isNotEmpty) { |
| 799 checkArgument(node.index, type.normalParameterTypes[0]); |
| 800 } |
| 801 } else { |
| 802 // TODO(vsm): Assert that the analyzer found an error here? |
| 803 } |
| 804 } |
| 805 node.visitChildren(this); |
| 806 } |
| 807 |
| 808 DartType getType(TypeName name) { |
| 809 return (name == null) ? rules.provider.dynamicType : name.type; |
| 810 } |
| 811 |
| 812 /// Analyzer checks boolean conversions, but we need to check too, because |
| 813 /// it uses the default assignability rules that allow `dynamic` and `Object` |
| 814 /// to be assigned to bool with no message. |
| 815 void checkBoolean(Expression expr) => |
| 816 checkAssignment(expr, rules.provider.boolType); |
| 817 |
| 818 void checkAssignment(Expression expr, DartType type) { |
| 819 if (expr is ParenthesizedExpression) { |
| 820 checkAssignment(expr.expression, type); |
| 821 } else { |
| 822 _recordMessage(rules.checkAssignment(expr, type)); |
| 823 } |
| 824 } |
| 825 |
| 826 DartType _specializedBinaryReturnType( |
| 827 TokenType op, DartType t1, DartType t2, DartType normalReturnType) { |
| 828 // This special cases binary return types as per 16.26 and 16.27 of the |
| 829 // Dart language spec. |
| 830 switch (op) { |
| 831 case TokenType.PLUS: |
| 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 |
| 927 // (node) was dynamic, rather than the target, but this is an easier fit |
| 928 // with what we used to do. |
| 929 DynamicInvoke.set(target, true); |
| 930 } |
| 931 |
| 932 void _recordMessage(StaticInfo info) { |
| 933 if (info == null) return; |
| 934 var error = info.toAnalysisError(); |
| 935 |
| 936 var severity = error.errorCode.errorSeverity; |
| 937 if (severity == ErrorSeverity.ERROR) _failure = true; |
| 938 if (severity != ErrorSeverity.INFO || _hints) { |
| 939 reporter.onError(error); |
| 940 } |
| 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 } |
| 951 } |
| 952 |
| 953 /// Looks up the declaration that matches [member] in [type] and returns it's |
| 954 /// declared type. |
| 955 FunctionType _getMemberType(InterfaceType type, ExecutableElement member) => |
| 956 _memberTypeGetter(member)(type); |
| 957 |
| 958 typedef FunctionType _MemberTypeGetter(InterfaceType type); |
| 959 |
| 960 _MemberTypeGetter _memberTypeGetter(ExecutableElement member) { |
| 961 String memberName = member.name; |
| 962 final isGetter = member is PropertyAccessorElement && member.isGetter; |
| 963 final isSetter = member is PropertyAccessorElement && member.isSetter; |
| 964 |
| 965 FunctionType f(InterfaceType type) { |
| 966 ExecutableElement baseMethod; |
| 967 try { |
| 968 if (isGetter) { |
| 969 assert(!isSetter); |
| 970 // Look for getter or field. |
| 971 baseMethod = type.getGetter(memberName); |
| 972 } else if (isSetter) { |
| 973 baseMethod = type.getSetter(memberName); |
| 974 } else { |
| 975 baseMethod = type.getMethod(memberName); |
| 976 } |
| 977 } catch (e) { |
| 978 // TODO(sigmund): remove this try-catch block (see issue #48). |
| 979 } |
| 980 if (baseMethod == null || baseMethod.isStatic) return null; |
| 981 return baseMethod.type; |
| 982 } |
| 983 ; |
| 984 return f; |
| 985 } |
OLD | NEW |