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 |