Index: packages/analyzer/lib/src/task/strong/checker.dart |
diff --git a/packages/analyzer/lib/src/task/strong/checker.dart b/packages/analyzer/lib/src/task/strong/checker.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..9cfefe83a9e8890ab90e5071ffb2652c64712c11 |
--- /dev/null |
+++ b/packages/analyzer/lib/src/task/strong/checker.dart |
@@ -0,0 +1,985 @@ |
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+// TODO(jmesserly): this was ported from package:dev_compiler, and needs to be |
+// refactored to fit into analyzer. |
+library analyzer.src.task.strong.checker; |
+ |
+import 'package:analyzer/analyzer.dart'; |
+import 'package:analyzer/src/generated/ast.dart'; |
+import 'package:analyzer/src/generated/element.dart'; |
+import 'package:analyzer/src/generated/scanner.dart' show Token, TokenType; |
+ |
+import 'info.dart'; |
+import 'rules.dart'; |
+ |
+/// Checks for overriding declarations of fields and methods. This is used to |
+/// check overrides between classes and superclasses, interfaces, and mixin |
+/// applications. |
+class _OverrideChecker { |
+ bool _failure = false; |
+ final TypeRules _rules; |
+ final AnalysisErrorListener _reporter; |
+ |
+ _OverrideChecker(this._rules, this._reporter); |
+ |
+ void check(ClassDeclaration node) { |
+ if (node.element.type.isObject) return; |
+ _checkSuperOverrides(node); |
+ _checkMixinApplicationOverrides(node); |
+ _checkAllInterfaceOverrides(node); |
+ } |
+ |
+ /// Check overrides from mixin applications themselves. For example, in: |
+ /// |
+ /// A extends B with E, F |
+ /// |
+ /// we check: |
+ /// |
+ /// B & E against B (equivalently how E overrides B) |
+ /// B & E & F against B & E (equivalently how F overrides both B and E) |
+ void _checkMixinApplicationOverrides(ClassDeclaration node) { |
+ var type = node.element.type; |
+ var parent = type.superclass; |
+ var mixins = type.mixins; |
+ |
+ // Check overrides from applying mixins |
+ for (int i = 0; i < mixins.length; i++) { |
+ var seen = new Set<String>(); |
+ var current = mixins[i]; |
+ var errorLocation = node.withClause.mixinTypes[i]; |
+ for (int j = i - 1; j >= 0; j--) { |
+ _checkIndividualOverridesFromType( |
+ current, mixins[j], errorLocation, seen); |
+ } |
+ _checkIndividualOverridesFromType(current, parent, errorLocation, seen); |
+ } |
+ } |
+ |
+ /// Check overrides between a class and its superclasses and mixins. For |
+ /// example, in: |
+ /// |
+ /// A extends B with E, F |
+ /// |
+ /// we check A against B, B super classes, E, and F. |
+ /// |
+ /// Internally we avoid reporting errors twice and we visit classes bottom up |
+ /// to ensure we report the most immediate invalid override first. For |
+ /// example, in the following code we'll report that `Test` has an invalid |
+ /// override with respect to `Parent` (as opposed to an invalid override with |
+ /// respect to `Grandparent`): |
+ /// |
+ /// class Grandparent { |
+ /// m(A a) {} |
+ /// } |
+ /// class Parent extends Grandparent { |
+ /// m(A a) {} |
+ /// } |
+ /// class Test extends Parent { |
+ /// m(B a) {} // invalid override |
+ /// } |
+ void _checkSuperOverrides(ClassDeclaration node) { |
+ var seen = new Set<String>(); |
+ var current = node.element.type; |
+ var visited = new Set<InterfaceType>(); |
+ do { |
+ visited.add(current); |
+ current.mixins.reversed |
+ .forEach((m) => _checkIndividualOverridesFromClass(node, m, seen)); |
+ _checkIndividualOverridesFromClass(node, current.superclass, seen); |
+ current = current.superclass; |
+ } while (!current.isObject && !visited.contains(current)); |
+ } |
+ |
+ /// Checks that implementations correctly override all reachable interfaces. |
+ /// In particular, we need to check these overrides for the definitions in |
+ /// the class itself and each its superclasses. If a superclass is not |
+ /// abstract, then we can skip its transitive interfaces. For example, in: |
+ /// |
+ /// B extends C implements G |
+ /// A extends B with E, F implements H, I |
+ /// |
+ /// we check: |
+ /// |
+ /// C against G, H, and I |
+ /// B against G, H, and I |
+ /// E against H and I // no check against G because B is a concrete class |
+ /// F against H and I |
+ /// A against H and I |
+ void _checkAllInterfaceOverrides(ClassDeclaration node) { |
+ var seen = new Set<String>(); |
+ // Helper function to collect all reachable interfaces. |
+ find(InterfaceType interfaceType, Set result) { |
+ if (interfaceType == null || interfaceType.isObject) return; |
+ if (result.contains(interfaceType)) return; |
+ result.add(interfaceType); |
+ find(interfaceType.superclass, result); |
+ interfaceType.mixins.forEach((i) => find(i, result)); |
+ interfaceType.interfaces.forEach((i) => find(i, result)); |
+ } |
+ |
+ // Check all interfaces reachable from the `implements` clause in the |
+ // current class against definitions here and in superclasses. |
+ var localInterfaces = new Set<InterfaceType>(); |
+ var type = node.element.type; |
+ type.interfaces.forEach((i) => find(i, localInterfaces)); |
+ _checkInterfacesOverrides(node, localInterfaces, seen, |
+ includeParents: true); |
+ |
+ // Check also how we override locally the interfaces from parent classes if |
+ // the parent class is abstract. Otherwise, these will be checked as |
+ // overrides on the concrete superclass. |
+ var superInterfaces = new Set<InterfaceType>(); |
+ var parent = type.superclass; |
+ // TODO(sigmund): we don't seem to be reporting the analyzer error that a |
+ // non-abstract class is not implementing an interface. See |
+ // https://github.com/dart-lang/dart-dev-compiler/issues/25 |
+ while (parent != null && parent.element.isAbstract) { |
+ parent.interfaces.forEach((i) => find(i, superInterfaces)); |
+ parent = parent.superclass; |
+ } |
+ _checkInterfacesOverrides(node, superInterfaces, seen, |
+ includeParents: false); |
+ } |
+ |
+ /// Checks that [cls] and its super classes (including mixins) correctly |
+ /// overrides each interface in [interfaces]. If [includeParents] is false, |
+ /// then mixins are still checked, but the base type and it's transitive |
+ /// supertypes are not. |
+ /// |
+ /// [cls] can be either a [ClassDeclaration] or a [InterfaceType]. For |
+ /// [ClassDeclaration]s errors are reported on the member that contains the |
+ /// invalid override, for [InterfaceType]s we use [errorLocation] instead. |
+ void _checkInterfacesOverrides( |
+ cls, Iterable<InterfaceType> interfaces, Set<String> seen, |
+ {Set<InterfaceType> visited, |
+ bool includeParents: true, |
+ AstNode errorLocation}) { |
+ var node = cls is ClassDeclaration ? cls : null; |
+ var type = cls is InterfaceType ? cls : node.element.type; |
+ |
+ if (visited == null) { |
+ visited = new Set<InterfaceType>(); |
+ } else if (visited.contains(type)) { |
+ // Malformed type. |
+ return; |
+ } else { |
+ visited.add(type); |
+ } |
+ |
+ // Check direct overrides on [type] |
+ for (var interfaceType in interfaces) { |
+ if (node != null) { |
+ _checkIndividualOverridesFromClass(node, interfaceType, seen); |
+ } else { |
+ _checkIndividualOverridesFromType( |
+ type, interfaceType, errorLocation, seen); |
+ } |
+ } |
+ |
+ // Check overrides from its mixins |
+ for (int i = 0; i < type.mixins.length; i++) { |
+ var loc = |
+ errorLocation != null ? errorLocation : node.withClause.mixinTypes[i]; |
+ for (var interfaceType in interfaces) { |
+ // We copy [seen] so we can report separately if more than one mixin or |
+ // the base class have an invalid override. |
+ _checkIndividualOverridesFromType( |
+ type.mixins[i], interfaceType, loc, new Set.from(seen)); |
+ } |
+ } |
+ |
+ // Check overrides from its superclasses |
+ if (includeParents) { |
+ var parent = type.superclass; |
+ if (parent.isObject) return; |
+ var loc = errorLocation != null ? errorLocation : node.extendsClause; |
+ // No need to copy [seen] here because we made copies above when reporting |
+ // errors on mixins. |
+ _checkInterfacesOverrides(parent, interfaces, seen, |
+ visited: visited, includeParents: true, errorLocation: loc); |
+ } |
+ } |
+ |
+ /// Check that individual methods and fields in [subType] correctly override |
+ /// the declarations in [baseType]. |
+ /// |
+ /// The [errorLocation] node indicates where errors are reported, see |
+ /// [_checkSingleOverride] for more details. |
+ /// |
+ /// The set [seen] is used to avoid reporting overrides more than once. It |
+ /// is used when invoking this function multiple times when checking several |
+ /// types in a class hierarchy. Errors are reported only the first time an |
+ /// invalid override involving a specific member is encountered. |
+ _checkIndividualOverridesFromType(InterfaceType subType, |
+ InterfaceType baseType, AstNode errorLocation, Set<String> seen) { |
+ void checkHelper(ExecutableElement e) { |
+ if (e.isStatic) return; |
+ if (seen.contains(e.name)) return; |
+ if (_checkSingleOverride(e, baseType, null, errorLocation)) { |
+ seen.add(e.name); |
+ } |
+ } |
+ subType.methods.forEach(checkHelper); |
+ subType.accessors.forEach(checkHelper); |
+ } |
+ |
+ /// Check that individual methods and fields in [subType] correctly override |
+ /// the declarations in [baseType]. |
+ /// |
+ /// The [errorLocation] node indicates where errors are reported, see |
+ /// [_checkSingleOverride] for more details. |
+ _checkIndividualOverridesFromClass( |
+ ClassDeclaration node, InterfaceType baseType, Set<String> seen) { |
+ for (var member in node.members) { |
+ if (member is ConstructorDeclaration) continue; |
+ if (member is FieldDeclaration) { |
+ if (member.isStatic) continue; |
+ for (var variable in member.fields.variables) { |
+ var element = variable.element as PropertyInducingElement; |
+ var name = element.name; |
+ if (seen.contains(name)) continue; |
+ var getter = element.getter; |
+ var setter = element.setter; |
+ bool found = _checkSingleOverride(getter, baseType, variable, member); |
+ if (!variable.isFinal && |
+ !variable.isConst && |
+ _checkSingleOverride(setter, baseType, variable, member)) { |
+ found = true; |
+ } |
+ if (found) seen.add(name); |
+ } |
+ } else { |
+ if ((member as MethodDeclaration).isStatic) continue; |
+ var method = (member as MethodDeclaration).element; |
+ if (seen.contains(method.name)) continue; |
+ if (_checkSingleOverride(method, baseType, member, member)) { |
+ seen.add(method.name); |
+ } |
+ } |
+ } |
+ } |
+ |
+ /// Checks that [element] correctly overrides its corresponding member in |
+ /// [type]. Returns `true` if an override was found, that is, if [element] has |
+ /// a corresponding member in [type] that it overrides. |
+ /// |
+ /// The [errorLocation] is a node where the error is reported. For example, a |
+ /// bad override of a method in a class with respect to its superclass is |
+ /// reported directly at the method declaration. However, invalid overrides |
+ /// from base classes to interfaces, mixins to the base they are applied to, |
+ /// or mixins to interfaces are reported at the class declaration, since the |
+ /// base class or members on their own were not incorrect, only combining them |
+ /// with the interface was problematic. For example, these are example error |
+ /// locations in these cases: |
+ /// |
+ /// error: base class introduces an invalid override. The type of B.foo is |
+ /// not a subtype of E.foo: |
+ /// class A extends B implements E { ... } |
+ /// ^^^^^^^^^ |
+ /// |
+ /// error: mixin introduces an invalid override. The type of C.foo is not |
+ /// a subtype of E.foo: |
+ /// class A extends B with C implements E { ... } |
+ /// ^ |
+ /// |
+ /// When checking for overrides from a type and it's super types, [node] is |
+ /// the AST node that defines [element]. This is used to determine whether the |
+ /// type of the element could be inferred from the types in the super classes. |
+ bool _checkSingleOverride(ExecutableElement element, InterfaceType type, |
+ AstNode node, AstNode errorLocation) { |
+ assert(!element.isStatic); |
+ |
+ FunctionType subType = _rules.elementType(element); |
+ // TODO(vsm): Test for generic |
+ FunctionType baseType = _getMemberType(type, element); |
+ |
+ if (baseType == null) return false; |
+ if (!_rules.isAssignable(subType, baseType)) { |
+ // See whether non-assignable cases fit one of our common patterns: |
+ // |
+ // Common pattern 1: Inferable return type (on getters and methods) |
+ // class A { |
+ // int get foo => ...; |
+ // String toString() { ... } |
+ // } |
+ // class B extends A { |
+ // get foo => e; // no type specified. |
+ // toString() { ... } // no return type specified. |
+ // } |
+ _recordMessage(new InvalidMethodOverride( |
+ errorLocation, element, type, subType, baseType)); |
+ } |
+ return true; |
+ } |
+ |
+ void _recordMessage(StaticInfo info) { |
+ if (info == null) return; |
+ var error = info.toAnalysisError(); |
+ if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; |
+ _reporter.onError(error); |
+ } |
+} |
+ |
+/// Checks the body of functions and properties. |
+class CodeChecker extends RecursiveAstVisitor { |
+ final TypeRules rules; |
+ final AnalysisErrorListener reporter; |
+ final _OverrideChecker _overrideChecker; |
+ final bool _hints; |
+ |
+ bool _failure = false; |
+ bool get failure => _failure || _overrideChecker._failure; |
+ |
+ void reset() { |
+ _failure = false; |
+ _overrideChecker._failure = false; |
+ } |
+ |
+ CodeChecker(TypeRules rules, AnalysisErrorListener reporter, |
+ {bool hints: false}) |
+ : rules = rules, |
+ reporter = reporter, |
+ _hints = hints, |
+ _overrideChecker = new _OverrideChecker(rules, reporter); |
+ |
+ @override |
+ void visitComment(Comment node) { |
+ // skip, no need to do typechecking inside comments (they may contain |
+ // comment references which would require resolution). |
+ } |
+ |
+ @override |
+ void visitClassDeclaration(ClassDeclaration node) { |
+ _overrideChecker.check(node); |
+ super.visitClassDeclaration(node); |
+ } |
+ |
+ @override |
+ void visitAssignmentExpression(AssignmentExpression node) { |
+ var token = node.operator; |
+ if (token.type != TokenType.EQ) { |
+ _checkCompoundAssignment(node); |
+ } else { |
+ DartType staticType = _getStaticType(node.leftHandSide); |
+ checkAssignment(node.rightHandSide, staticType); |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ /// Check constructor declaration to ensure correct super call placement. |
+ @override |
+ void visitConstructorDeclaration(ConstructorDeclaration node) { |
+ node.visitChildren(this); |
+ |
+ final init = node.initializers; |
+ for (int i = 0, last = init.length - 1; i < last; i++) { |
+ final node = init[i]; |
+ if (node is SuperConstructorInvocation) { |
+ _recordMessage(new InvalidSuperInvocation(node)); |
+ } |
+ } |
+ } |
+ |
+ @override |
+ void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
+ var field = node.fieldName; |
+ var element = field.staticElement; |
+ DartType staticType = rules.elementType(element); |
+ checkAssignment(node.expression, staticType); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitForEachStatement(ForEachStatement node) { |
+ // Check that the expression is an Iterable. |
+ var expr = node.iterable; |
+ var iterableType = node.awaitKeyword != null |
+ ? rules.provider.streamType |
+ : rules.provider.iterableType; |
+ var loopVariable = node.identifier != null |
+ ? node.identifier |
+ : node.loopVariable?.identifier; |
+ if (loopVariable != null) { |
+ var iteratorType = loopVariable.staticType; |
+ var checkedType = iterableType.substitute4([iteratorType]); |
+ checkAssignment(expr, checkedType); |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitForStatement(ForStatement node) { |
+ if (node.condition != null) { |
+ checkBoolean(node.condition); |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitIfStatement(IfStatement node) { |
+ checkBoolean(node.condition); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitDoStatement(DoStatement node) { |
+ checkBoolean(node.condition); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitWhileStatement(WhileStatement node) { |
+ checkBoolean(node.condition); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitSwitchStatement(SwitchStatement node) { |
+ // SwitchStatement defines a boolean conversion to check the result of the |
+ // case value == the switch value, but in dev_compiler we require a boolean |
+ // return type from an overridden == operator (because Object.==), so |
+ // checking in SwitchStatement shouldn't be necessary. |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitListLiteral(ListLiteral node) { |
+ var type = rules.provider.dynamicType; |
+ if (node.typeArguments != null) { |
+ var targs = node.typeArguments.arguments; |
+ if (targs.length > 0) type = targs[0].type; |
+ } |
+ var elements = node.elements; |
+ for (int i = 0; i < elements.length; i++) { |
+ checkArgument(elements[i], type); |
+ } |
+ super.visitListLiteral(node); |
+ } |
+ |
+ @override |
+ void visitMapLiteral(MapLiteral node) { |
+ var ktype = rules.provider.dynamicType; |
+ var vtype = rules.provider.dynamicType; |
+ if (node.typeArguments != null) { |
+ var targs = node.typeArguments.arguments; |
+ if (targs.length > 0) ktype = targs[0].type; |
+ if (targs.length > 1) vtype = targs[1].type; |
+ } |
+ var entries = node.entries; |
+ for (int i = 0; i < entries.length; i++) { |
+ var entry = entries[i]; |
+ checkArgument(entry.key, ktype); |
+ checkArgument(entry.value, vtype); |
+ } |
+ super.visitMapLiteral(node); |
+ } |
+ |
+ // Check invocations |
+ void checkArgumentList(ArgumentList node, FunctionType type) { |
+ NodeList<Expression> list = node.arguments; |
+ int len = list.length; |
+ for (int i = 0; i < len; ++i) { |
+ Expression arg = list[i]; |
+ ParameterElement element = arg.staticParameterElement; |
+ if (element == null) { |
+ if (type.parameters.length < len) { |
+ // We found an argument mismatch, the analyzer will report this too, |
+ // so no need to insert an error for this here. |
+ continue; |
+ } |
+ element = type.parameters[i]; |
+ // TODO(vsm): When can this happen? |
+ assert(element != null); |
+ } |
+ DartType expectedType = rules.elementType(element); |
+ if (expectedType == null) expectedType = rules.provider.dynamicType; |
+ checkArgument(arg, expectedType); |
+ } |
+ } |
+ |
+ void checkArgument(Expression arg, DartType expectedType) { |
+ // Preserve named argument structure, so their immediate parent is the |
+ // method invocation. |
+ if (arg is NamedExpression) { |
+ arg = (arg as NamedExpression).expression; |
+ } |
+ checkAssignment(arg, expectedType); |
+ } |
+ |
+ void checkFunctionApplication( |
+ Expression node, Expression f, ArgumentList list) { |
+ if (rules.isDynamicCall(f)) { |
+ // If f is Function and this is a method invocation, we should have |
+ // gotten an analyzer error, so no need to issue another error. |
+ _recordDynamicInvoke(node, f); |
+ } else { |
+ checkArgumentList(list, rules.getTypeAsCaller(f)); |
+ } |
+ } |
+ |
+ @override |
+ visitMethodInvocation(MethodInvocation node) { |
+ var target = node.realTarget; |
+ if (rules.isDynamicTarget(target) && |
+ !_isObjectMethod(node, node.methodName)) { |
+ _recordDynamicInvoke(node, target); |
+ |
+ // Mark the tear-off as being dynamic, too. This lets us distinguish |
+ // cases like: |
+ // |
+ // dynamic d; |
+ // d.someMethod(...); // the whole method call must be a dynamic send. |
+ // |
+ // ... from case like: |
+ // |
+ // SomeType s; |
+ // s.someDynamicField(...); // static get, followed by dynamic call. |
+ // |
+ // The first case is handled here, the second case is handled below when |
+ // we call [checkFunctionApplication]. |
+ DynamicInvoke.set(node.methodName, true); |
+ } else { |
+ checkFunctionApplication(node, node.methodName, node.argumentList); |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
+ checkFunctionApplication(node, node.function, node.argumentList); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitRedirectingConstructorInvocation( |
+ RedirectingConstructorInvocation node) { |
+ var type = node.staticElement.type; |
+ checkArgumentList(node.argumentList, type); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
+ var element = node.staticElement; |
+ if (element != null) { |
+ var type = node.staticElement.type; |
+ checkArgumentList(node.argumentList, type); |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ void _checkReturnOrYield(Expression expression, AstNode node, |
+ {bool yieldStar: false}) { |
+ var body = node.getAncestor((n) => n is FunctionBody); |
+ var type = rules.getExpectedReturnType(body, yieldStar: yieldStar); |
+ if (type == null) { |
+ // We have a type mismatch: the async/async*/sync* modifier does |
+ // not match the return or yield type. We should have already gotten an |
+ // analyzer error in this case. |
+ return; |
+ } |
+ // TODO(vsm): Enforce void or dynamic (to void?) when expression is null. |
+ if (expression != null) checkAssignment(expression, type); |
+ } |
+ |
+ @override |
+ void visitExpressionFunctionBody(ExpressionFunctionBody node) { |
+ _checkReturnOrYield(node.expression, node); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitReturnStatement(ReturnStatement node) { |
+ _checkReturnOrYield(node.expression, node); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitYieldStatement(YieldStatement node) { |
+ _checkReturnOrYield(node.expression, node, yieldStar: node.star != null); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitPropertyAccess(PropertyAccess node) { |
+ var target = node.realTarget; |
+ if (rules.isDynamicTarget(target) && |
+ !_isObjectProperty(target, node.propertyName)) { |
+ _recordDynamicInvoke(node, target); |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitPrefixedIdentifier(PrefixedIdentifier node) { |
+ final target = node.prefix; |
+ if (rules.isDynamicTarget(target) && |
+ !_isObjectProperty(target, node.identifier)) { |
+ _recordDynamicInvoke(node, target); |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitDefaultFormalParameter(DefaultFormalParameter node) { |
+ // Check that defaults have the proper subtype. |
+ var parameter = node.parameter; |
+ var parameterType = rules.elementType(parameter.element); |
+ assert(parameterType != null); |
+ var defaultValue = node.defaultValue; |
+ if (defaultValue != null) { |
+ checkAssignment(defaultValue, parameterType); |
+ } |
+ |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitFieldFormalParameter(FieldFormalParameter node) { |
+ var element = node.element; |
+ var typeName = node.type; |
+ if (typeName != null) { |
+ var type = rules.elementType(element); |
+ var fieldElement = |
+ node.identifier.staticElement as FieldFormalParameterElement; |
+ var fieldType = rules.elementType(fieldElement.field); |
+ if (!rules.isSubTypeOf(type, fieldType)) { |
+ var staticInfo = |
+ new InvalidParameterDeclaration(rules, node, fieldType); |
+ _recordMessage(staticInfo); |
+ } |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitInstanceCreationExpression(InstanceCreationExpression node) { |
+ var arguments = node.argumentList; |
+ var element = node.staticElement; |
+ if (element != null) { |
+ var type = rules.elementType(node.staticElement); |
+ checkArgumentList(arguments, type); |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitVariableDeclarationList(VariableDeclarationList node) { |
+ TypeName type = node.type; |
+ if (type == null) { |
+ // No checks are needed when the type is var. Although internally the |
+ // typing rules may have inferred a more precise type for the variable |
+ // based on the initializer. |
+ } else { |
+ var dartType = getType(type); |
+ for (VariableDeclaration variable in node.variables) { |
+ var initializer = variable.initializer; |
+ if (initializer != null) { |
+ checkAssignment(initializer, dartType); |
+ } |
+ } |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ void _checkRuntimeTypeCheck(AstNode node, TypeName typeName) { |
+ var type = getType(typeName); |
+ if (!rules.isGroundType(type)) { |
+ _recordMessage(new NonGroundTypeCheckInfo(node, type)); |
+ } |
+ } |
+ |
+ @override |
+ void visitAsExpression(AsExpression node) { |
+ // We could do the same check as the IsExpression below, but that is |
+ // potentially too conservative. Instead, at runtime, we must fail hard |
+ // if the Dart as and the DDC as would return different values. |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitIsExpression(IsExpression node) { |
+ _checkRuntimeTypeCheck(node, node.type); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitPrefixExpression(PrefixExpression node) { |
+ if (node.operator.type == TokenType.BANG) { |
+ checkBoolean(node.operand); |
+ } else { |
+ _checkUnary(node); |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitPostfixExpression(PostfixExpression node) { |
+ _checkUnary(node); |
+ node.visitChildren(this); |
+ } |
+ |
+ void _checkUnary(/*PrefixExpression|PostfixExpression*/ node) { |
+ var op = node.operator; |
+ if (op.isUserDefinableOperator || |
+ op.type == TokenType.PLUS_PLUS || |
+ op.type == TokenType.MINUS_MINUS) { |
+ if (rules.isDynamicTarget(node.operand)) { |
+ _recordDynamicInvoke(node, node.operand); |
+ } |
+ // For ++ and --, even if it is not dynamic, we still need to check |
+ // that the user defined method accepts an `int` as the RHS. |
+ // We assume Analyzer has done this already. |
+ } |
+ } |
+ |
+ @override |
+ void visitBinaryExpression(BinaryExpression node) { |
+ var op = node.operator; |
+ if (op.isUserDefinableOperator) { |
+ if (rules.isDynamicTarget(node.leftOperand)) { |
+ // Dynamic invocation |
+ // TODO(vsm): Move this logic to the resolver? |
+ if (op.type != TokenType.EQ_EQ && op.type != TokenType.BANG_EQ) { |
+ _recordDynamicInvoke(node, node.leftOperand); |
+ } |
+ } else { |
+ var element = node.staticElement; |
+ // Method invocation. |
+ if (element is MethodElement) { |
+ var type = element.type; |
+ // Analyzer should enforce number of parameter types, but check in |
+ // case we have erroneous input. |
+ if (type.normalParameterTypes.isNotEmpty) { |
+ checkArgument(node.rightOperand, type.normalParameterTypes[0]); |
+ } |
+ } else { |
+ // TODO(vsm): Assert that the analyzer found an error here? |
+ } |
+ } |
+ } else { |
+ // Non-method operator. |
+ switch (op.type) { |
+ case TokenType.AMPERSAND_AMPERSAND: |
+ case TokenType.BAR_BAR: |
+ checkBoolean(node.leftOperand); |
+ checkBoolean(node.rightOperand); |
+ break; |
+ case TokenType.BANG_EQ: |
+ break; |
+ case TokenType.QUESTION_QUESTION: |
+ break; |
+ default: |
+ assert(false); |
+ } |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitConditionalExpression(ConditionalExpression node) { |
+ checkBoolean(node.condition); |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ void visitIndexExpression(IndexExpression node) { |
+ var target = node.realTarget; |
+ if (rules.isDynamicTarget(target)) { |
+ _recordDynamicInvoke(node, target); |
+ } else { |
+ var element = node.staticElement; |
+ if (element is MethodElement) { |
+ var type = element.type; |
+ // Analyzer should enforce number of parameter types, but check in |
+ // case we have erroneous input. |
+ if (type.normalParameterTypes.isNotEmpty) { |
+ checkArgument(node.index, type.normalParameterTypes[0]); |
+ } |
+ } else { |
+ // TODO(vsm): Assert that the analyzer found an error here? |
+ } |
+ } |
+ node.visitChildren(this); |
+ } |
+ |
+ DartType getType(TypeName name) { |
+ return (name == null) ? rules.provider.dynamicType : name.type; |
+ } |
+ |
+ /// Analyzer checks boolean conversions, but we need to check too, because |
+ /// it uses the default assignability rules that allow `dynamic` and `Object` |
+ /// to be assigned to bool with no message. |
+ void checkBoolean(Expression expr) => |
+ checkAssignment(expr, rules.provider.boolType); |
+ |
+ void checkAssignment(Expression expr, DartType type) { |
+ if (expr is ParenthesizedExpression) { |
+ checkAssignment(expr.expression, type); |
+ } else { |
+ _recordMessage(rules.checkAssignment(expr, type)); |
+ } |
+ } |
+ |
+ DartType _specializedBinaryReturnType( |
+ TokenType op, DartType t1, DartType t2, DartType normalReturnType) { |
+ // This special cases binary return types as per 16.26 and 16.27 of the |
+ // Dart language spec. |
+ switch (op) { |
+ case TokenType.PLUS: |
+ case TokenType.MINUS: |
+ case TokenType.STAR: |
+ case TokenType.TILDE_SLASH: |
+ case TokenType.PERCENT: |
+ case TokenType.PLUS_EQ: |
+ case TokenType.MINUS_EQ: |
+ case TokenType.STAR_EQ: |
+ case TokenType.TILDE_SLASH_EQ: |
+ case TokenType.PERCENT_EQ: |
+ if (t1 == rules.provider.intType && |
+ t2 == rules.provider.intType) return t1; |
+ if (t1 == rules.provider.doubleType && |
+ t2 == rules.provider.doubleType) return t1; |
+ // This particular combo is not spelled out in the spec, but all |
+ // implementations and analyzer seem to follow this. |
+ if (t1 == rules.provider.doubleType && |
+ t2 == rules.provider.intType) return t1; |
+ } |
+ return normalReturnType; |
+ } |
+ |
+ void _checkCompoundAssignment(AssignmentExpression expr) { |
+ var op = expr.operator.type; |
+ assert(op.isAssignmentOperator && op != TokenType.EQ); |
+ var methodElement = expr.staticElement; |
+ if (methodElement == null) { |
+ // Dynamic invocation |
+ _recordDynamicInvoke(expr, expr.leftHandSide); |
+ } else { |
+ // Sanity check the operator |
+ assert(methodElement.isOperator); |
+ var functionType = methodElement.type; |
+ var paramTypes = functionType.normalParameterTypes; |
+ assert(paramTypes.length == 1); |
+ assert(functionType.namedParameterTypes.isEmpty); |
+ assert(functionType.optionalParameterTypes.isEmpty); |
+ |
+ // Check the lhs type |
+ var staticInfo; |
+ var rhsType = _getStaticType(expr.rightHandSide); |
+ var lhsType = _getStaticType(expr.leftHandSide); |
+ var returnType = _specializedBinaryReturnType( |
+ op, lhsType, rhsType, functionType.returnType); |
+ |
+ if (!rules.isSubTypeOf(returnType, lhsType)) { |
+ final numType = rules.provider.numType; |
+ // Try to fix up the numerical case if possible. |
+ if (rules.isSubTypeOf(lhsType, numType) && |
+ rules.isSubTypeOf(lhsType, rhsType)) { |
+ // This is also slightly different from spec, but allows us to keep |
+ // compound operators in the int += num and num += dynamic cases. |
+ staticInfo = DownCast.create( |
+ rules, expr.rightHandSide, Coercion.cast(rhsType, lhsType)); |
+ rhsType = lhsType; |
+ } else { |
+ // Static type error |
+ staticInfo = new StaticTypeError(rules, expr, lhsType); |
+ } |
+ _recordMessage(staticInfo); |
+ } |
+ |
+ // Check the rhs type |
+ if (staticInfo is! CoercionInfo) { |
+ var paramType = paramTypes.first; |
+ staticInfo = rules.checkAssignment(expr.rightHandSide, paramType); |
+ _recordMessage(staticInfo); |
+ } |
+ } |
+ } |
+ |
+ bool _isObjectGetter(Expression target, SimpleIdentifier id) { |
+ PropertyAccessorElement element = |
+ rules.provider.objectType.element.getGetter(id.name); |
+ return (element != null && !element.isStatic); |
+ } |
+ |
+ bool _isObjectMethod(Expression target, SimpleIdentifier id) { |
+ MethodElement element = |
+ rules.provider.objectType.element.getMethod(id.name); |
+ return (element != null && !element.isStatic); |
+ } |
+ |
+ bool _isObjectProperty(Expression target, SimpleIdentifier id) { |
+ return _isObjectGetter(target, id) || _isObjectMethod(target, id); |
+ } |
+ |
+ DartType _getStaticType(Expression expr) { |
+ return expr.staticType ?? rules.provider.dynamicType; |
+ } |
+ |
+ void _recordDynamicInvoke(AstNode node, AstNode target) { |
+ if (_hints) { |
+ reporter.onError(new DynamicInvoke(rules, node).toAnalysisError()); |
+ } |
+ // TODO(jmesserly): we may eventually want to record if the whole operation |
+ // (node) was dynamic, rather than the target, but this is an easier fit |
+ // with what we used to do. |
+ DynamicInvoke.set(target, true); |
+ } |
+ |
+ void _recordMessage(StaticInfo info) { |
+ if (info == null) return; |
+ var error = info.toAnalysisError(); |
+ |
+ var severity = error.errorCode.errorSeverity; |
+ if (severity == ErrorSeverity.ERROR) _failure = true; |
+ if (severity != ErrorSeverity.INFO || _hints) { |
+ reporter.onError(error); |
+ } |
+ |
+ if (info is CoercionInfo) { |
+ // TODO(jmesserly): if we're run again on the same AST, we'll produce the |
+ // same annotations. This should be harmless. This might go away once |
+ // CodeChecker is integrated better with analyzer, as it will know that |
+ // checking has already been performed. |
+ // assert(CoercionInfo.get(info.node) == null); |
+ CoercionInfo.set(info.node, info); |
+ } |
+ } |
+} |
+ |
+/// Looks up the declaration that matches [member] in [type] and returns it's |
+/// declared type. |
+FunctionType _getMemberType(InterfaceType type, ExecutableElement member) => |
+ _memberTypeGetter(member)(type); |
+ |
+typedef FunctionType _MemberTypeGetter(InterfaceType type); |
+ |
+_MemberTypeGetter _memberTypeGetter(ExecutableElement member) { |
+ String memberName = member.name; |
+ final isGetter = member is PropertyAccessorElement && member.isGetter; |
+ final isSetter = member is PropertyAccessorElement && member.isSetter; |
+ |
+ FunctionType f(InterfaceType type) { |
+ ExecutableElement baseMethod; |
+ try { |
+ if (isGetter) { |
+ assert(!isSetter); |
+ // Look for getter or field. |
+ baseMethod = type.getGetter(memberName); |
+ } else if (isSetter) { |
+ baseMethod = type.getSetter(memberName); |
+ } else { |
+ baseMethod = type.getMethod(memberName); |
+ } |
+ } catch (e) { |
+ // TODO(sigmund): remove this try-catch block (see issue #48). |
+ } |
+ if (baseMethod == null || baseMethod.isStatic) return null; |
+ return baseMethod.type; |
+ } |
+ ; |
+ return f; |
+} |