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

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

Issue 1402783004: port DDC checker code to analyzer (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | pkg/analyzer/lib/src/task/strong/info.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 ErrorReporter _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.reportError(error);
322 }
323 }
324
325 /// Checks the body of functions and properties.
326 class CodeChecker extends RecursiveAstVisitor {
327 final TypeRules rules;
328 final ErrorReporter reporter;
329 final _OverrideChecker _overrideChecker;
330 bool _failure = false;
331 bool get failure => _failure || _overrideChecker._failure;
332
333 void reset() {
334 _failure = false;
335 _overrideChecker._failure = false;
336 }
337
338 CodeChecker(TypeRules rules, ErrorReporter reporter)
339 : rules = rules,
340 reporter = reporter,
341 _overrideChecker = new _OverrideChecker(rules, reporter);
342
343 @override
344 void visitComment(Comment node) {
345 // skip, no need to do typechecking inside comments (they may contain
346 // comment references which would require resolution).
347 }
348
349 @override
350 void visitClassDeclaration(ClassDeclaration node) {
351 _overrideChecker.check(node);
352 super.visitClassDeclaration(node);
353 }
354
355 @override
356 void visitAssignmentExpression(AssignmentExpression node) {
357 var token = node.operator;
358 if (token.type != TokenType.EQ) {
359 _checkCompoundAssignment(node);
360 } else {
361 DartType staticType = _getStaticType(node.leftHandSide);
362 checkAssignment(node.rightHandSide, staticType);
363 }
364 node.visitChildren(this);
365 }
366
367 /// Check constructor declaration to ensure correct super call placement.
368 @override
369 void visitConstructorDeclaration(ConstructorDeclaration node) {
370 node.visitChildren(this);
371
372 final init = node.initializers;
373 for (int i = 0, last = init.length - 1; i < last; i++) {
374 final node = init[i];
375 if (node is SuperConstructorInvocation) {
376 _recordMessage(new InvalidSuperInvocation(node));
377 }
378 }
379 }
380
381 @override
382 void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
383 var field = node.fieldName;
384 var element = field.staticElement;
385 DartType staticType = rules.elementType(element);
386 checkAssignment(node.expression, staticType);
387 node.visitChildren(this);
388 }
389
390 @override
391 void visitForEachStatement(ForEachStatement node) {
392 // Check that the expression is an Iterable.
393 var expr = node.iterable;
394 var iterableType = node.awaitKeyword != null
395 ? rules.provider.streamType
396 : rules.provider.iterableType;
397 var loopVariable = node.identifier != null
398 ? node.identifier
399 : node.loopVariable?.identifier;
400 if (loopVariable != null) {
401 var iteratorType = loopVariable.staticType;
402 var checkedType = iterableType.substitute4([iteratorType]);
403 checkAssignment(expr, checkedType);
404 }
405 node.visitChildren(this);
406 }
407
408 @override
409 void visitForStatement(ForStatement node) {
410 if (node.condition != null) {
411 checkBoolean(node.condition);
412 }
413 node.visitChildren(this);
414 }
415
416 @override
417 void visitIfStatement(IfStatement node) {
418 checkBoolean(node.condition);
419 node.visitChildren(this);
420 }
421
422 @override
423 void visitDoStatement(DoStatement node) {
424 checkBoolean(node.condition);
425 node.visitChildren(this);
426 }
427
428 @override
429 void visitWhileStatement(WhileStatement node) {
430 checkBoolean(node.condition);
431 node.visitChildren(this);
432 }
433
434 @override
435 void visitSwitchStatement(SwitchStatement node) {
436 // SwitchStatement defines a boolean conversion to check the result of the
437 // case value == the switch value, but in dev_compiler we require a boolean
438 // return type from an overridden == operator (because Object.==), so
439 // checking in SwitchStatement shouldn't be necessary.
440 node.visitChildren(this);
441 }
442
443 @override
444 void visitListLiteral(ListLiteral node) {
445 var type = rules.provider.dynamicType;
446 if (node.typeArguments != null) {
447 var targs = node.typeArguments.arguments;
448 if (targs.length > 0) type = targs[0].type;
449 }
450 var elements = node.elements;
451 for (int i = 0; i < elements.length; i++) {
452 checkArgument(elements[i], type);
453 }
454 super.visitListLiteral(node);
455 }
456
457 @override
458 void visitMapLiteral(MapLiteral node) {
459 var ktype = rules.provider.dynamicType;
460 var vtype = rules.provider.dynamicType;
461 if (node.typeArguments != null) {
462 var targs = node.typeArguments.arguments;
463 if (targs.length > 0) ktype = targs[0].type;
464 if (targs.length > 1) vtype = targs[1].type;
465 }
466 var entries = node.entries;
467 for (int i = 0; i < entries.length; i++) {
468 var entry = entries[i];
469 checkArgument(entry.key, ktype);
470 checkArgument(entry.value, vtype);
471 }
472 super.visitMapLiteral(node);
473 }
474
475 // Check invocations
476 void checkArgumentList(ArgumentList node, FunctionType type) {
477 NodeList<Expression> list = node.arguments;
478 int len = list.length;
479 for (int i = 0; i < len; ++i) {
480 Expression arg = list[i];
481 ParameterElement element = arg.staticParameterElement;
482 if (element == null) {
483 if (type.parameters.length < len) {
484 // We found an argument mismatch, the analyzer will report this too,
485 // so no need to insert an error for this here.
486 continue;
487 }
488 element = type.parameters[i];
489 // TODO(vsm): When can this happen?
490 assert(element != null);
491 }
492 DartType expectedType = rules.elementType(element);
493 if (expectedType == null) expectedType = rules.provider.dynamicType;
494 checkArgument(arg, expectedType);
495 }
496 }
497
498 void checkArgument(Expression arg, DartType expectedType) {
499 // Preserve named argument structure, so their immediate parent is the
500 // method invocation.
501 if (arg is NamedExpression) {
502 arg = (arg as NamedExpression).expression;
503 }
504 checkAssignment(arg, expectedType);
505 }
506
507 void checkFunctionApplication(
508 Expression node, Expression f, ArgumentList list) {
509 if (rules.isDynamicCall(f)) {
510 // If f is Function and this is a method invocation, we should have
511 // gotten an analyzer error, so no need to issue another error.
512 _recordDynamicInvoke(node, f);
513 } else {
514 checkArgumentList(list, rules.getTypeAsCaller(f));
515 }
516 }
517
518 @override
519 visitMethodInvocation(MethodInvocation node) {
520 var target = node.realTarget;
521 if (rules.isDynamicTarget(target) &&
522 !_isObjectMethod(node, node.methodName)) {
523 _recordDynamicInvoke(node, target);
524
525 // Mark the tear-off as being dynamic, too. This lets us distinguish
526 // cases like:
527 //
528 // dynamic d;
529 // d.someMethod(...); // the whole method call must be a dynamic send.
530 //
531 // ... from case like:
532 //
533 // SomeType s;
534 // s.someDynamicField(...); // static get, followed by dynamic call.
535 //
536 // The first case is handled here, the second case is handled below when
537 // we call [checkFunctionApplication].
538 DynamicInvoke.set(node.methodName, true);
539 } else {
540 checkFunctionApplication(node, node.methodName, node.argumentList);
541 }
542 node.visitChildren(this);
543 }
544
545 @override
546 void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
547 checkFunctionApplication(node, node.function, node.argumentList);
548 node.visitChildren(this);
549 }
550
551 @override
552 void visitRedirectingConstructorInvocation(
553 RedirectingConstructorInvocation node) {
554 var type = node.staticElement.type;
555 checkArgumentList(node.argumentList, type);
556 node.visitChildren(this);
557 }
558
559 @override
560 void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
561 var element = node.staticElement;
562 if (element == null) {
563 _recordMessage(new MissingTypeError(node));
564 } else {
565 var type = node.staticElement.type;
566 checkArgumentList(node.argumentList, type);
567 }
568 node.visitChildren(this);
569 }
570
571 void _checkReturnOrYield(Expression expression, AstNode node,
572 {bool yieldStar: false}) {
573 var body = node.getAncestor((n) => n is FunctionBody);
574 var type = rules.getExpectedReturnType(body, yieldStar: yieldStar);
575 if (type == null) {
576 // We have a type mismatch: the async/async*/sync* modifier does
577 // not match the return or yield type. We should have already gotten an
578 // analyzer error in this case.
579 return;
580 }
581 // TODO(vsm): Enforce void or dynamic (to void?) when expression is null.
582 if (expression != null) checkAssignment(expression, type);
583 }
584
585 @override
586 void visitExpressionFunctionBody(ExpressionFunctionBody node) {
587 _checkReturnOrYield(node.expression, node);
588 node.visitChildren(this);
589 }
590
591 @override
592 void visitReturnStatement(ReturnStatement node) {
593 _checkReturnOrYield(node.expression, node);
594 node.visitChildren(this);
595 }
596
597 @override
598 void visitYieldStatement(YieldStatement node) {
599 _checkReturnOrYield(node.expression, node, yieldStar: node.star != null);
600 node.visitChildren(this);
601 }
602
603 @override
604 void visitPropertyAccess(PropertyAccess node) {
605 var target = node.realTarget;
606 if (rules.isDynamicTarget(target) &&
607 !_isObjectProperty(target, node.propertyName)) {
608 _recordDynamicInvoke(node, target);
609 }
610 node.visitChildren(this);
611 }
612
613 @override
614 void visitPrefixedIdentifier(PrefixedIdentifier node) {
615 final target = node.prefix;
616 if (rules.isDynamicTarget(target) &&
617 !_isObjectProperty(target, node.identifier)) {
618 _recordDynamicInvoke(node, target);
619 }
620 node.visitChildren(this);
621 }
622
623 @override
624 void visitDefaultFormalParameter(DefaultFormalParameter node) {
625 // Check that defaults have the proper subtype.
626 var parameter = node.parameter;
627 var parameterType = rules.elementType(parameter.element);
628 assert(parameterType != null);
629 var defaultValue = node.defaultValue;
630 if (defaultValue != null) {
631 checkAssignment(defaultValue, parameterType);
632 }
633
634 node.visitChildren(this);
635 }
636
637 @override
638 void visitFieldFormalParameter(FieldFormalParameter node) {
639 var element = node.element;
640 var typeName = node.type;
641 if (typeName != null) {
642 var type = rules.elementType(element);
643 var fieldElement =
644 node.identifier.staticElement as FieldFormalParameterElement;
645 var fieldType = rules.elementType(fieldElement.field);
646 if (!rules.isSubTypeOf(type, fieldType)) {
647 var staticInfo =
648 new InvalidParameterDeclaration(rules, node, fieldType);
649 _recordMessage(staticInfo);
650 }
651 }
652 node.visitChildren(this);
653 }
654
655 @override
656 void visitInstanceCreationExpression(InstanceCreationExpression node) {
657 var arguments = node.argumentList;
658 var element = node.staticElement;
659 if (element != null) {
660 var type = rules.elementType(node.staticElement);
661 checkArgumentList(arguments, type);
662 } else {
663 _recordMessage(new MissingTypeError(node));
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 var type = expr.staticType;
920 if (type == null) {
921 reporter.reportError(new MissingTypeError(expr).toAnalysisError());
922 }
923 return type ?? rules.provider.dynamicType;
924 }
925
926 void _recordDynamicInvoke(AstNode node, AstNode target) {
927 reporter.reportError(new DynamicInvoke(rules, node).toAnalysisError());
928 // TODO(jmesserly): we may eventually want to record if the whole operation
929 // (node) was dynamic, rather than the target, but this is an easier fit
930 // with what we used to do.
931 DynamicInvoke.set(target, true);
932 }
933
934 void _recordMessage(StaticInfo info) {
935 if (info == null) return;
936 var error = info.toAnalysisError();
937 if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true;
938 reporter.reportError(error);
939
940 if (info is CoercionInfo) {
941 // TODO(jmesserly): if we're run again on the same AST, we'll produce the
942 // same annotations. This should be harmless. This might go away once
943 // CodeChecker is integrated better with analyzer, as it will know that
944 // checking has already been performed.
945 // assert(CoercionInfo.get(info.node) == null);
946 CoercionInfo.set(info.node, info);
947 }
948 }
949 }
950
951
952 /// Looks up the declaration that matches [member] in [type] and returns it's
953 /// declared type.
954 FunctionType _getMemberType(InterfaceType type, ExecutableElement member) =>
955 _memberTypeGetter(member)(type);
956
957 typedef FunctionType _MemberTypeGetter(InterfaceType type);
958
959 _MemberTypeGetter _memberTypeGetter(ExecutableElement member) {
960 String memberName = member.name;
961 final isGetter = member is PropertyAccessorElement && member.isGetter;
962 final isSetter = member is PropertyAccessorElement && member.isSetter;
963
964 FunctionType f(InterfaceType type) {
965 ExecutableElement baseMethod;
966 try {
967 if (isGetter) {
968 assert(!isSetter);
969 // Look for getter or field.
970 baseMethod = type.getGetter(memberName);
971 } else if (isSetter) {
972 baseMethod = type.getSetter(memberName);
973 } else {
974 baseMethod = type.getMethod(memberName);
975 }
976 } catch (e) {
977 // TODO(sigmund): remove this try-catch block (see issue #48).
978 }
979 if (baseMethod == null || baseMethod.isStatic) return null;
980 return baseMethod.type;
981 }
982 ;
983 return f;
984 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analyzer/lib/src/task/strong/info.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698