Move DDC to analyzer-based checker
diff --git a/lib/src/checker/checker.dart b/lib/src/checker/checker.dart index 0af5675..10bae9c 100644 --- a/lib/src/checker/checker.dart +++ b/lib/src/checker/checker.dart
@@ -4,945 +4,4 @@ library dev_compiler.src.checker.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 '../utils.dart' show getMemberType; -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; - bool _failure = false; - bool get failure => _failure || _overrideChecker._failure; - - void reset() { - _failure = false; - _overrideChecker._failure = false; - } - - CodeChecker(TypeRules rules, AnalysisErrorListener reporter) - : rules = rules, - reporter = reporter, - _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) { - _recordMessage(new MissingTypeError(node)); - } else { - 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); - } else { - _recordMessage(new MissingTypeError(node)); - } - 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) { - var type = expr.staticType; - if (type == null) { - reporter.onError(new MissingTypeError(expr).toAnalysisError()); - } - return type ?? rules.provider.dynamicType; - } - - void _recordDynamicInvoke(AstNode node, AstNode target) { - 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(); - if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) _failure = true; - 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); - } - } -} +export 'package:analyzer/src/task/strong/checker.dart';
diff --git a/lib/src/checker/resolver.dart b/lib/src/checker/resolver.dart index f05998e..1e141cf 100644 --- a/lib/src/checker/resolver.dart +++ b/lib/src/checker/resolver.dart
@@ -19,6 +19,7 @@ import '../../strong_mode.dart' show StrongModeOptions; import '../utils.dart'; +import 'rules.dart'; final _log = new logger.Logger('dev_compiler.src.resolver');
diff --git a/lib/src/checker/rules.dart b/lib/src/checker/rules.dart index f4d35bc..eebfcac 100644 --- a/lib/src/checker/rules.dart +++ b/lib/src/checker/rules.dart
@@ -4,731 +4,4 @@ library dev_compiler.src.checker.rules; -import 'package:analyzer/src/generated/ast.dart'; -import 'package:analyzer/src/generated/element.dart'; -import 'package:analyzer/src/generated/resolver.dart'; - -import '../../strong_mode.dart' show StrongModeOptions; -import '../info.dart'; -import '../utils.dart' as utils; - -class TypeRules { - final TypeProvider provider; - - /// Map of fields / properties / methods on Object. - final Map<String, DartType> objectMembers; - - final StrongModeOptions options; - DownwardsInference inferrer; - - TypeRules(TypeProvider provider, {this.options}) - : provider = provider, - objectMembers = utils.getObjectMemberMap(provider) { - inferrer = new DownwardsInference(this); - } - - /// Given a type t, if t is an interface type with a call method - /// defined, return the function type for the call method, otherwise - /// return null. - FunctionType getCallMethodType(DartType t) { - if (t is InterfaceType) { - ClassElement element = t.element; - InheritanceManager manager = new InheritanceManager(element.library); - FunctionType callType = manager.lookupMemberType(t, "call"); - return callType; - } - return null; - } - - /// Given an expression, return its type assuming it is - /// in the caller position of a call (that is, accounting - /// for the possibility of a call method). Returns null - /// if expression is not statically callable. - FunctionType getTypeAsCaller(Expression applicand) { - var t = getStaticType(applicand); - if (t is InterfaceType) { - return getCallMethodType(t); - } - if (t is FunctionType) return t; - return null; - } - - /// Gets the expected return type of the given function [body], either from - /// a normal return/yield, or from a yield*. - DartType getExpectedReturnType(FunctionBody body, {bool yieldStar: false}) { - FunctionType functionType; - var parent = body.parent; - if (parent is Declaration) { - functionType = elementType(parent.element); - } else { - assert(parent is FunctionExpression); - functionType = getStaticType(parent); - } - - var type = functionType.returnType; - - InterfaceType expectedType = null; - if (body.isAsynchronous) { - if (body.isGenerator) { - // Stream<T> -> T - expectedType = provider.streamType; - } else { - // Future<T> -> T - // TODO(vsm): Revisit with issue #228. - expectedType = provider.futureType; - } - } else { - if (body.isGenerator) { - // Iterable<T> -> T - expectedType = provider.iterableType; - } else { - // T -> T - return type; - } - } - if (yieldStar) { - if (type.isDynamic) { - // Ensure it's at least a Stream / Iterable. - return expectedType.substitute4([provider.dynamicType]); - } else { - // Analyzer will provide a separate error if expected type - // is not compatible with type. - return type; - } - } - if (type.isDynamic) { - return type; - } else if (type is InterfaceType && type.element == expectedType.element) { - return type.typeArguments[0]; - } else { - // Malformed type - fallback on analyzer error. - return null; - } - } - - DartType getStaticType(Expression expr) { - return expr.staticType ?? provider.dynamicType; - } - - bool _isBottom(DartType t, {bool dynamicIsBottom: false}) { - if (t.isDynamic && dynamicIsBottom) return true; - // TODO(vsm): We need direct support for non-nullability in DartType. - // This should check on "true/nonnullable" Bottom - if (t.isBottom) return true; - return false; - } - - bool _isTop(DartType t, {bool dynamicIsBottom: false}) { - if (t.isDynamic && !dynamicIsBottom) return true; - if (t.isObject) return true; - return false; - } - - bool _anyParameterType(FunctionType ft, bool predicate(DartType t)) { - return ft.normalParameterTypes.any(predicate) || - ft.optionalParameterTypes.any(predicate) || - ft.namedParameterTypes.values.any(predicate); - } - - // TODO(leafp): Revisit this. - bool isGroundType(DartType t) { - if (t is TypeParameterType) return false; - if (_isTop(t)) return true; - - if (t is FunctionType) { - if (!_isTop(t.returnType) || - _anyParameterType(t, (pt) => !_isBottom(pt, dynamicIsBottom: true))) { - return false; - } else { - return true; - } - } - - if (t is InterfaceType) { - var typeArguments = t.typeArguments; - for (var typeArgument in typeArguments) { - if (!_isTop(typeArgument)) return false; - } - return true; - } - - // We should not see any other type aside from malformed code. - return false; - } - - /// Check that f1 is a subtype of f2. [ignoreReturn] is used in the DDC - /// checker to determine whether f1 would be a subtype of f2 if the return - /// type of f1 is set to match f2's return type. - // [fuzzyArrows] indicates whether or not the f1 and f2 should be - // treated as fuzzy arrow types (and hence dynamic parameters to f2 treated as - // bottom). - bool isFunctionSubTypeOf(FunctionType f1, FunctionType f2, - {bool fuzzyArrows: true, bool ignoreReturn: false}) { - final r1s = f1.normalParameterTypes; - final o1s = f1.optionalParameterTypes; - final n1s = f1.namedParameterTypes; - final r2s = f2.normalParameterTypes; - final o2s = f2.optionalParameterTypes; - final n2s = f2.namedParameterTypes; - final ret1 = ignoreReturn ? f2.returnType : f1.returnType; - final ret2 = f2.returnType; - - // A -> B <: C -> D if C <: A and - // either D is void or B <: D - if (!ret2.isVoid && !isSubTypeOf(ret1, ret2)) return false; - - // Reject if one has named and the other has optional - if (n1s.length > 0 && o2s.length > 0) return false; - if (n2s.length > 0 && o1s.length > 0) return false; - - // f2 has named parameters - if (n2s.length > 0) { - // Check that every named parameter in f2 has a match in f1 - for (String k2 in n2s.keys) { - if (!n1s.containsKey(k2)) return false; - if (!isSubTypeOf(n2s[k2], n1s[k2], - dynamicIsBottom: fuzzyArrows)) return false; - } - } - // If we get here, we either have no named parameters, - // or else the named parameters match and we have no optional - // parameters - - // If f1 has more required parameters, reject - if (r1s.length > r2s.length) return false; - - // If f2 has more required + optional parameters, reject - if (r2s.length + o2s.length > r1s.length + o1s.length) return false; - - // The parameter lists must look like the following at this point - // where rrr is a region of required, and ooo is a region of optionals. - // f1: rrr ooo ooo ooo - // f2: rrr rrr ooo - int rr = r1s.length; // required in both - int or = r2s.length - r1s.length; // optional in f1, required in f2 - int oo = o2s.length; // optional in both - - for (int i = 0; i < rr; ++i) { - if (!isSubTypeOf(r2s[i], r1s[i], - dynamicIsBottom: fuzzyArrows)) return false; - } - for (int i = 0, j = rr; i < or; ++i, ++j) { - if (!isSubTypeOf(r2s[j], o1s[i], - dynamicIsBottom: fuzzyArrows)) return false; - } - for (int i = or, j = 0; i < oo; ++i, ++j) { - if (!isSubTypeOf(o2s[j], o1s[i], - dynamicIsBottom: fuzzyArrows)) return false; - } - return true; - } - - bool _isInterfaceSubTypeOf(InterfaceType i1, InterfaceType i2) { - if (i1 == i2) return true; - - if (i1.element == i2.element) { - List<DartType> tArgs1 = i1.typeArguments; - List<DartType> tArgs2 = i2.typeArguments; - - // TODO(leafp): Verify that this is always true - // Do raw types get filled in? - assert(tArgs1.length == tArgs2.length); - - for (int i = 0; i < tArgs1.length; i++) { - DartType t1 = tArgs1[i]; - DartType t2 = tArgs2[i]; - if (!isSubTypeOf(t1, t2)) return false; - } - return true; - } - - if (i2.isDartCoreFunction) { - if (i1.element.getMethod("call") != null) return true; - } - - if (i1 == provider.objectType) return false; - - if (_isInterfaceSubTypeOf(i1.superclass, i2)) return true; - - for (final parent in i1.interfaces) { - if (_isInterfaceSubTypeOf(parent, i2)) return true; - } - - for (final parent in i1.mixins) { - if (_isInterfaceSubTypeOf(parent, i2)) return true; - } - - return false; - } - - bool isSubTypeOf(DartType t1, DartType t2, {bool dynamicIsBottom: false}) { - if (t1 == t2) return true; - - // Trivially true. - if (_isTop(t2, dynamicIsBottom: dynamicIsBottom) || - _isBottom(t1, dynamicIsBottom: dynamicIsBottom)) { - return true; - } - - // Trivially false. - if (_isTop(t1, dynamicIsBottom: dynamicIsBottom) || - _isBottom(t2, dynamicIsBottom: dynamicIsBottom)) { - return false; - } - - // The null type is a subtype of any nullable type, which is all Dart types. - // TODO(vsm): Note, t1.isBottom still allows for null confusingly. - // _isBottom(t1) does not necessarily imply t1.isBottom if there are - // nonnullable types in the system. - if (t1.isBottom) { - return true; - } - - // S <: T where S is a type variable - // T is not dynamic or object (handled above) - // S != T (handled above) - // So only true if bound of S is S' and - // S' <: T - if (t1 is TypeParameterType) { - DartType bound = t1.element.bound; - if (bound == null) return false; - return isSubTypeOf(bound, t2); - } - - if (t2 is TypeParameterType) { - return false; - } - - if (t2.isDartCoreFunction) { - if (t1 is FunctionType) return true; - if (t1.element is ClassElement) { - if ((t1.element as ClassElement).getMethod("call") != null) return true; - } - } - - // "Traditional" name-based subtype check. - if (t1 is InterfaceType && t2 is InterfaceType) { - return _isInterfaceSubTypeOf(t1, t2); - } - - if (t1 is! FunctionType && t2 is! FunctionType) return false; - - if (t1 is InterfaceType && t2 is FunctionType) { - var callType = getCallMethodType(t1); - if (callType == null) return false; - return isFunctionSubTypeOf(callType, t2); - } - - if (t1 is FunctionType && t2 is InterfaceType) { - return false; - } - - // Functions - // Note: it appears under the hood all Dart functions map to a class / - // hidden type that: - // (a) subtypes Object (an internal _FunctionImpl in the VM) - // (b) implements Function - // (c) provides standard Object members (hashCode, toString) - // (d) contains private members (corresponding to _FunctionImpl?) - // (e) provides a call method to handle the actual function invocation - // - // The standard Dart subtyping rules are structural in nature. I.e., - // bivariant on arguments and return type. - // - // The below tries for a more traditional subtyping rule: - // - covariant on return type - // - contravariant on parameters - // - 'sensible' (?) rules on optional and/or named params - // but doesn't properly mix with class subtyping. I suspect Java 8 lambdas - // essentially map to dynamic (and rely on invokedynamic) due to similar - // issues. - return isFunctionSubTypeOf(t1 as FunctionType, t2 as FunctionType); - } - - bool isAssignable(DartType t1, DartType t2) { - return isSubTypeOf(t1, t2); - } - - // Produce a coercion which coerces something of type fromT - // to something of type toT. - // If wrap is true and both are function types, a closure - // wrapper coercion is produced using _wrapTo (see above) - // Returns the error coercion if the types cannot be coerced - // according to our current criteria. - Coercion _coerceTo(DartType fromT, DartType toT) { - // We can use anything as void - if (toT.isVoid) return Coercion.identity(toT); - - // fromT <: toT, no coercion needed - if (isSubTypeOf(fromT, toT)) return Coercion.identity(toT); - - // For now, reject conversions between function types and - // call method objects. We could choose to allow casts here. - // Wrapping a function type to assign it to a call method - // object will never succeed. Wrapping the other way could - // be allowed. - if ((fromT is FunctionType && getCallMethodType(toT) != null) || - (toT is FunctionType && getCallMethodType(fromT) != null)) { - return Coercion.error(); - } - - // Downcast if toT <: fromT - if (isSubTypeOf(toT, fromT)) return Coercion.cast(fromT, toT); - - // Downcast if toT <===> fromT - // The intention here is to allow casts that are sideways in the restricted - // type system, but allowed in the regular dart type system, since these - // are likely to succeed. The canonical example is List<dynamic> and - // Iterable<T> for some concrete T (e.g. Object). These are unrelated - // in the restricted system, but List<dynamic> <: Iterable<T> in dart. - if (fromT.isAssignableTo(toT)) { - return Coercion.cast(fromT, toT); - } - return Coercion.error(); - } - - StaticInfo checkAssignment(Expression expr, DartType toT) { - final fromT = getStaticType(expr); - final Coercion c = _coerceTo(fromT, toT); - if (c is Identity) return null; - if (c is CoercionError) return new StaticTypeError(this, expr, toT); - var reason = null; - - var errors = <String>[]; - var ok = inferrer.inferExpression(expr, toT, errors); - if (ok) return InferredType.create(this, expr, toT); - reason = (errors.isNotEmpty) ? errors.first : null; - - if (c is Cast) return DownCast.create(this, expr, c, reason: reason); - assert(false); - return null; - } - - DartType elementType(Element e) { - if (e == null) { - // Malformed code - just return dynamic. - return provider.dynamicType; - } - return (e as dynamic).type; - } - - /// Returns `true` if the target expression is dynamic. - // TODO(jmesserly): remove this in favor of utils? Or a static method here? - bool isDynamicTarget(Expression target) => utils.isDynamicTarget(target); - - /// Returns `true` if the expression is a dynamic function call or method - /// invocation. - bool isDynamicCall(Expression call) { - var ft = getTypeAsCaller(call); - // TODO(leafp): This will currently return true if t is Function - // This is probably the most correct thing to do for now, since - // this code is also used by the back end. Maybe revisit at some - // point? - if (ft == null) return true; - // Dynamic as the parameter type is treated as bottom. A function with - // a dynamic parameter type requires a dynamic call in general. - // However, as an optimization, if we have an original definition, we know - // dynamic is reified as Object - in this case a regular call is fine. - if (call is SimpleIdentifier) { - var element = call.staticElement; - if (element is FunctionElement || element is MethodElement) { - // An original declaration. - return false; - } - } - - return _anyParameterType(ft, (pt) => pt.isDynamic); - } -} - -class DownwardsInference { - final TypeRules rules; - - DownwardsInference(this.rules); - - /// Called for each list literal which gets inferred - void annotateListLiteral(ListLiteral e, List<DartType> targs) {} - - /// Called for each map literal which gets inferred - void annotateMapLiteral(MapLiteral e, List<DartType> targs) {} - - /// Called for each new/const which gets inferred - void annotateInstanceCreationExpression( - InstanceCreationExpression e, List<DartType> targs) {} - - /// Called for cast from dynamic required for inference to succeed - void annotateCastFromDynamic(Expression e, DartType t) {} - - /// Called for each function expression return type inferred - void annotateFunctionExpression(FunctionExpression e, DartType returnType) {} - - /// Downward inference - bool inferExpression(Expression e, DartType t, List<String> errors) { - // Don't cast top level expressions, only sub-expressions - return _inferExpression(e, t, errors, cast: false); - } - - /// Downward inference - bool _inferExpression(Expression e, DartType t, List<String> errors, - {cast: true}) { - if (e is ConditionalExpression) { - return _inferConditionalExpression(e, t, errors); - } - if (e is ParenthesizedExpression) { - return _inferParenthesizedExpression(e, t, errors); - } - if (rules.isSubTypeOf(rules.getStaticType(e), t)) return true; - if (cast && rules.getStaticType(e).isDynamic) { - annotateCastFromDynamic(e, t); - return true; - } - if (e is FunctionExpression) return _inferFunctionExpression(e, t, errors); - if (e is ListLiteral) return _inferListLiteral(e, t, errors); - if (e is MapLiteral) return _inferMapLiteral(e, t, errors); - if (e is NamedExpression) return _inferNamedExpression(e, t, errors); - if (e is InstanceCreationExpression) { - return _inferInstanceCreationExpression(e, t, errors); - } - errors.add("$e cannot be typed as $t"); - return false; - } - - /// If t1 = I<dynamic, ..., dynamic>, then look for a supertype - /// of t1 of the form K<S0, ..., Sm> where t2 = K<S0', ..., Sm'> - /// If the supertype exists, use the constraints S0 <: S0', ... Sm <: Sm' - /// to derive a concrete instantation for I of the form <T0, ..., Tn>, - /// such that I<T0, .., Tn> <: t2 - List<DartType> _matchTypes(InterfaceType t1, InterfaceType t2) { - if (t1 == t2) return t2.typeArguments; - var tArgs1 = t1.typeArguments; - var tArgs2 = t2.typeArguments; - // If t1 isn't a raw type, bail out - if (tArgs1 != null && tArgs1.any((t) => !t.isDynamic)) return null; - - // This is our inferred type argument list. We start at all dynamic, - // and fill in with inferred types when we reach a match. - var actuals = - new List<DartType>.filled(tArgs1.length, rules.provider.dynamicType); - - // When we find the supertype of t1 with the same - // classname as t2 (see below), we have the following: - // If t1 is an instantiation of a class T1<X0, ..., Xn> - // and t2 is an instantiation of a class T2<Y0, ...., Ym> - // of the form t2 = T2<S0, ..., Sm> - // then we want to choose instantiations for the Xi - // T0, ..., Tn such that T1<T0, ..., Tn> <: t2 . - // To find this, we simply instantate T1 with - // X0, ..., Xn, and then find its superclass - // T2<T0', ..., Tn'>. We then solve the constraint - // set T0' <: S0, ..., Tn' <: Sn for the Xi. - // Currently, we only handle constraints where - // the Ti' is one of the Xi'. If there are multiple - // constraints on some Xi, we choose the lower of the - // two (if it exists). - bool permute(List<DartType> permutedArgs) { - if (permutedArgs == null) return false; - var ps = t1.typeParameters; - var ts = ps.map((p) => p.type).toList(); - for (int i = 0; i < permutedArgs.length; i++) { - var tVar = permutedArgs[i]; - var tActual = tArgs2[i]; - var index = ts.indexOf(tVar); - if (index >= 0 && rules.isSubTypeOf(tActual, actuals[index])) { - actuals[index] = tActual; - } - } - return actuals.any((x) => !x.isDynamic); - } - - // Look for the first supertype of t1 with the same class name as t2. - bool match(InterfaceType t1) { - if (t1.element == t2.element) { - return permute(t1.typeArguments); - } - - if (t1 == rules.provider.objectType) return false; - - if (match(t1.superclass)) return true; - - for (final parent in t1.interfaces) { - if (match(parent)) return true; - } - - for (final parent in t1.mixins) { - if (match(parent)) return true; - } - return false; - } - - // We have that t1 = T1<dynamic, ..., dynamic>. - // To match t1 against t2, we use the uninstantiated version - // of t1, essentially treating it as an instantiation with - // fresh variables, and solve for the variables. - // t1.element.type will be of the form T1<X0, ..., Xn> - if (!match(t1.element.type)) return null; - var newT1 = t1.element.type.substitute4(actuals); - // If we found a solution, return it. - if (rules.isSubTypeOf(newT1, t2)) return actuals; - return null; - } - - /// These assume that e is not already a subtype of t - - bool _inferConditionalExpression( - ConditionalExpression e, DartType t, errors) { - return _inferExpression(e.thenExpression, t, errors) && - _inferExpression(e.elseExpression, t, errors); - } - - bool _inferParenthesizedExpression( - ParenthesizedExpression e, DartType t, errors) { - return _inferExpression(e.expression, t, errors); - } - - bool _inferInstanceCreationExpression( - InstanceCreationExpression e, DartType t, errors) { - var arguments = e.argumentList.arguments; - var rawType = rules.getStaticType(e); - // rawType is the instantiated type of the instance - if (rawType is! InterfaceType) return false; - var type = (rawType as InterfaceType); - if (type.typeParameters == null || - type.typeParameters.length == 0) return false; - if (e.constructorName.type == null) return false; - // classTypeName is the type name of the class being instantiated - var classTypeName = e.constructorName.type; - // Check that we were not passed any type arguments - if (classTypeName.typeArguments != null) return false; - // Infer type arguments - if (t is! InterfaceType) return false; - var targs = _matchTypes(type, t); - if (targs == null) return false; - if (e.staticElement == null) return false; - var constructorElement = e.staticElement; - // From the constructor element get: - // the instantiated type of the constructor, then - // the uninstantiated element for the constructor, then - // the uninstantiated type for the constructor - var rawConstructorElement = - constructorElement.type.element as ConstructorElement; - var baseType = rawConstructorElement.type; - if (baseType == null) return false; - // From the interface type (instantiated), get: - // the uninstantiated element, then - // the uninstantiated type, then - // the type arguments (aka the type parameters) - var tparams = type.element.type.typeArguments; - // Take the uninstantiated constructor type, and replace the type - // parameters with the inferred arguments. - var fType = baseType.substitute2(targs, tparams); - { - var rTypes = fType.normalParameterTypes; - var oTypes = fType.optionalParameterTypes; - var pTypes = new List.from(rTypes)..addAll(oTypes); - var pArgs = arguments.where((x) => x is! NamedExpression); - var pi = 0; - for (var arg in pArgs) { - if (pi >= pTypes.length) return false; - var argType = pTypes[pi]; - if (!_inferExpression(arg, argType, errors)) return false; - pi++; - } - var nTypes = fType.namedParameterTypes; - for (var arg0 in arguments) { - if (arg0 is! NamedExpression) continue; - var arg = arg0 as NamedExpression; - SimpleIdentifier nameNode = arg.name.label; - String name = nameNode.name; - var argType = nTypes[name]; - if (argType == null) return false; - if (!_inferExpression(arg, argType, errors)) return false; - } - } - annotateInstanceCreationExpression(e, targs); - return true; - } - - bool _inferNamedExpression(NamedExpression e, DartType t, errors) { - return _inferExpression(e.expression, t, errors); - } - - bool _inferFunctionExpression(FunctionExpression e, DartType t, errors) { - if (t is! FunctionType) return false; - var fType = t as FunctionType; - var eType = e.staticType as FunctionType; - if (eType is! FunctionType) return false; - - // We have a function literal, so we can treat the arrow type - // as non-fuzzy. Since we're not improving on parameter types - // currently, if this check fails then we cannot succeed, so - // bail out. Otherwise, we never need to check the parameter types - // again. - if (!rules.isFunctionSubTypeOf(eType, fType, - fuzzyArrows: false, ignoreReturn: true)) return false; - - // This only entered inference because of fuzzy typing. - // The function type is already specific enough, we can just - // succeed and treat it as a successful inference - if (rules.isSubTypeOf(eType.returnType, fType.returnType)) return true; - - // Fuzzy typing again, handle the void case (not caught by the previous) - if (fType.returnType.isVoid) return true; - - if (e.body is! ExpressionFunctionBody) return false; - var body = (e.body as ExpressionFunctionBody).expression; - if (!_inferExpression(body, fType.returnType, errors)) return false; - - // TODO(leafp): Try narrowing the argument types if possible - // to get better code in the function body. This requires checking - // that the body is well-typed at the more specific type. - - // At this point, we know that the parameter types are in the appropriate subtype - // relation, and we have checked that we can type the body at the appropriate return - // type, so we can are done. - annotateFunctionExpression(e, fType.returnType); - return true; - } - - bool _inferListLiteral(ListLiteral e, DartType t, errors) { - var dyn = rules.provider.dynamicType; - var listT = rules.provider.listType.substitute4([dyn]); - // List <: t (using dart rules) must be true - if (!listT.isSubtypeOf(t)) return false; - // The list literal must have no type arguments - if (e.typeArguments != null) return false; - if (t is! InterfaceType) return false; - var targs = _matchTypes(listT, t); - if (targs == null) return false; - assert(targs.length == 1); - var etype = targs[0]; - assert(!etype.isDynamic); - var elements = e.elements; - var b = elements.every((e) => _inferExpression(e, etype, errors)); - if (b) annotateListLiteral(e, targs); - return b; - } - - bool _inferMapLiteral(MapLiteral e, DartType t, errors) { - var dyn = rules.provider.dynamicType; - var mapT = rules.provider.mapType.substitute4([dyn, dyn]); - // Map <: t (using dart rules) must be true - if (!mapT.isSubtypeOf(t)) return false; - // The map literal must have no type arguments - if (e.typeArguments != null) return false; - if (t is! InterfaceType) return false; - var targs = _matchTypes(mapT, t); - if (targs == null) return false; - assert(targs.length == 2); - var kType = targs[0]; - var vType = targs[1]; - assert(!(kType.isDynamic && vType.isDynamic)); - var entries = e.entries; - bool inferEntry(MapLiteralEntry entry) { - return _inferExpression(entry.key, kType, errors) && - _inferExpression(entry.value, vType, errors); - } - var b = entries.every(inferEntry); - if (b) annotateMapLiteral(e, targs); - return b; - } -} +export 'package:analyzer/src/task/strong/rules.dart';
diff --git a/lib/src/codegen/code_generator.dart b/lib/src/codegen/code_generator.dart index eb4135d..12cc751 100644 --- a/lib/src/codegen/code_generator.dart +++ b/lib/src/codegen/code_generator.dart
@@ -23,7 +23,7 @@ CodeGenerator(AbstractCompiler compiler) : compiler = compiler, - rules = compiler.rules, + rules = new TypeRules(compiler.context.typeProvider), context = compiler.context, options = compiler.options.codegenOptions;
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart index 99a2408..872a7a5 100644 --- a/lib/src/codegen/js_codegen.dart +++ b/lib/src/codegen/js_codegen.dart
@@ -105,11 +105,10 @@ Map<String, DartType> _objectMembers; - JSCodegenVisitor(AbstractCompiler compiler, this.currentLibrary, + JSCodegenVisitor(AbstractCompiler compiler, this.rules, this.currentLibrary, this._extensionTypes, this._fieldsNeedingStorage) : compiler = compiler, options = compiler.options.codegenOptions, - rules = compiler.rules, _types = compiler.context.typeProvider { _loader = new ModuleItemLoadOrder(_emitModuleItem); @@ -125,7 +124,7 @@ JS.Program emitLibrary(LibraryUnit library) { // Modify the AST to make coercions explicit. - new CoercionReifier(library, compiler).reify(); + new CoercionReifier(library, rules).reify(); // Build the public namespace for this library. This allows us to do // constant time lookups (contrast with `Element.getChild(name)`). @@ -3280,7 +3279,7 @@ var library = unit.library.element.library; var fields = findFieldsNeedingStorage(unit); var codegen = - new JSCodegenVisitor(compiler, library, _extensionTypes, fields); + new JSCodegenVisitor(compiler, rules, library, _extensionTypes, fields); var module = codegen.emitLibrary(unit); var out = compiler.getOutputPath(library.source.uri); return writeJsLibrary(module, out,
diff --git a/lib/src/codegen/reify_coercions.dart b/lib/src/codegen/reify_coercions.dart index 8a2c542..3bf1d91 100644 --- a/lib/src/codegen/reify_coercions.dart +++ b/lib/src/codegen/reify_coercions.dart
@@ -9,7 +9,6 @@ import 'package:analyzer/src/generated/element.dart'; import 'package:logging/logging.dart' as logger; -import '../compiler.dart' show AbstractCompiler; import '../checker/rules.dart'; import '../info.dart'; @@ -100,11 +99,11 @@ CoercionReifier._( this._cm, this._tm, this._vm, this._library, this._inferrer); - factory CoercionReifier(LibraryUnit library, AbstractCompiler compiler) { + factory CoercionReifier(LibraryUnit library, TypeRules rules) { var vm = new VariableManager(); var tm = new TypeManager(library.library.element.enclosingElement, vm); var cm = new CoercionManager(vm, tm); - var inferrer = new _Inference(compiler.rules, tm); + var inferrer = new _Inference(rules, tm); return new CoercionReifier._(cm, tm, vm, library, inferrer); }
diff --git a/lib/src/compiler.dart b/lib/src/compiler.dart index 8194518..d4f61a4 100644 --- a/lib/src/compiler.dart +++ b/lib/src/compiler.dart
@@ -25,8 +25,6 @@ import 'package:path/path.dart' as path; import 'analysis_context.dart'; -import 'checker/checker.dart'; -import 'checker/rules.dart'; import 'codegen/html_codegen.dart' as html_codegen; import 'codegen/js_codegen.dart'; import 'info.dart' @@ -100,7 +98,7 @@ _dartCore = context.typeProvider.objectType.element.library; } - ErrorCollector get reporter => checker.reporter; + ErrorCollector get reporter => super.reporter; /// Compiles every file in [options.inputs]. /// Returns true on successful compile. @@ -211,10 +209,7 @@ for (var element in unitElements) { var unit = context.resolveCompilationUnit(element.source, library); units.add(unit); - failureInLib = logErrors(element.source) || failureInLib; - checker.reset(); - checker.visitCompilationUnit(unit); - if (checker.failure) failureInLib = true; + failureInLib = computeErrors(element.source) || failureInLib; } if (failureInLib) _compilationRecord[library] = false; @@ -345,19 +340,11 @@ abstract class AbstractCompiler { final CompilerOptions options; final AnalysisContext context; - final CodeChecker checker; + final AnalysisErrorListener reporter; - AbstractCompiler(AnalysisContext context, CompilerOptions options, - [AnalysisErrorListener reporter]) - : context = context, - options = options, - checker = new CodeChecker( - new TypeRules(context.typeProvider, options: options.strongOptions), - reporter ?? AnalysisErrorListener.NULL_LISTENER); + AbstractCompiler(this.context, this.options, [this.reporter]); String get outputDir => options.codegenOptions.outputDir; - TypeRules get rules => checker.rules; - AnalysisErrorListener get reporter => checker.reporter; Uri stringToUri(String uriString) { var uri = uriString.startsWith('dart:') || uriString.startsWith('package:') @@ -440,23 +427,33 @@ /// Log any errors encountered when resolving [source] and return whether any /// errors were found. - bool logErrors(Source source) { - List<AnalysisError> errors = context.computeErrors(source); + bool computeErrors(Source source) { + AnalysisContext errorContext = context; + // TODO(jmesserly): should this be a fix somewhere in analyzer? + // otherwise we fail to find the parts. + if (source.uri.scheme == 'dart') { + errorContext = context.sourceFactory.dartSdk.context; + } + List<AnalysisError> errors = errorContext.computeErrors(source); bool failure = false; - if (errors.isNotEmpty) { - for (var error in errors) { - // Always skip TODOs. - if (error.errorCode.type == ErrorType.TODO) continue; + for (var error in errors) { + // Always skip TODOs. + if (error.errorCode.type == ErrorType.TODO) continue; - // Skip hints for now. In the future these could be turned on via flags. - if (error.errorCode.errorSeverity.ordinal < - ErrorSeverity.WARNING.ordinal) { - continue; + // TODO(jmesserly): for now, treat DDC errors as having a different + // error level from Analayzer ones. + if (error.errorCode.name.startsWith('dev_compiler')) { + reporter.onError(error); + if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) { + failure = true; } - + } else if (error.errorCode.errorSeverity.ordinal >= + ErrorSeverity.WARNING.ordinal) { // All analyzer warnings or errors are errors for DDC. failure = true; reporter.onError(error); + } else { + // Skip hints for now. } } return failure;
diff --git a/lib/src/info.dart b/lib/src/info.dart index c48fb36..75e74a3 100644 --- a/lib/src/info.dart +++ b/lib/src/info.dart
@@ -8,20 +8,19 @@ import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/element.dart'; -import 'package:analyzer/src/generated/error.dart'; import 'package:analyzer/src/generated/parser.dart'; -import 'checker/rules.dart'; import 'utils.dart' as utils; +import 'package:analyzer/src/task/strong/info.dart'; +export 'package:analyzer/src/task/strong/info.dart'; /// Represents a summary of the results collected by running the program /// checker. class CheckerResults { final List<LibraryInfo> libraries; - final TypeRules rules; final bool failure; - CheckerResults(this.libraries, this.rules, this.failure); + CheckerResults(this.libraries, this.failure); } /// Computed information about each library. @@ -102,474 +101,3 @@ return clone; } } - -// The abstract type of coercions mapping one type to another. -// This class also exposes static builder functions which -// check for errors and reduce redundant coercions to the identity. -abstract class Coercion { - final DartType fromType; - final DartType toType; - Coercion(this.fromType, this.toType); - static Coercion cast(DartType fromT, DartType toT) => new Cast(fromT, toT); - static Coercion identity(DartType type) => new Identity(type); - static Coercion error() => new CoercionError(); -} - -// Coercion which casts one type to another -class Cast extends Coercion { - Cast(DartType fromType, DartType toType) : super(fromType, toType); -} - -// The identity coercion -class Identity extends Coercion { - Identity(DartType fromType) : super(fromType, fromType); -} - -// The error coercion. This coercion signals that a coercion -// could not be generated. The code generator should not see -// these. -class CoercionError extends Coercion { - CoercionError() : super(null, null); -} - -// TODO(jmesserly): this could use some refactoring. These are essentially -// like ErrorCodes in analyzer, but we're including some details in our message. -// Analyzer instead has template strings, and replaces '{0}' with the first -// argument. -abstract class StaticInfo { - /// AST Node this info is attached to. - AstNode get node; - - // TODO(jmesserly): review the usage of error codes. We probably want our own, - // as well as some DDC specific [ErrorType]s. - ErrorCode toErrorCode(); - - // TODO(jmesserly): what convention to use here? - String get name => 'dev_compiler.$runtimeType'; - - List<Object> get arguments => [node]; - - AnalysisError toAnalysisError() { - int begin = node is AnnotatedNode - ? (node as AnnotatedNode).firstTokenAfterCommentAndMetadata.offset - : node.offset; - int length = node.end - begin; - var source = (node.root as CompilationUnit).element.source; - return new AnalysisError(source, begin, length, toErrorCode(), arguments); - } -} - -/// Implicitly injected expression conversion. -abstract class CoercionInfo extends StaticInfo { - final TypeRules rules; - - final Expression node; - - DartType get convertedType; - - CoercionInfo(this.rules, this.node); - - DartType get baseType => rules.getStaticType(node); - DartType get staticType => convertedType; - - String get message; - toErrorCode() => new HintCode(name, message); - - static const String _propertyName = 'dev_compiler.src.info.CoercionInfo'; - - /// Gets the coercion info associated with this node. - static CoercionInfo get(AstNode node) => node.getProperty(_propertyName); - - /// Sets the coercion info associated with this node. - static CoercionInfo set(AstNode node, CoercionInfo info) { - node.setProperty(_propertyName, info); - return info; - } -} - -// Base class for all casts from base type to sub type. -abstract class DownCast extends CoercionInfo { - Cast _cast; - - DownCast._internal(TypeRules rules, Expression expression, this._cast) - : super(rules, expression) { - assert(_cast.toType != baseType && - _cast.fromType == baseType && - (baseType.isDynamic || - // Call methods make the following non-redundant - _cast.toType.isSubtypeOf(baseType) || - baseType.isAssignableTo(_cast.toType))); - } - - Cast get cast => _cast; - - DartType get convertedType => _cast.toType; - - @override List<Object> get arguments => [node, baseType, convertedType]; - @override String get message => '{0} ({1}) will need runtime check ' - 'to cast to type {2}'; - - // Factory to create correct DownCast variant. - static StaticInfo create(TypeRules rules, Expression expression, Cast cast, - {String reason}) { - final fromT = cast.fromType; - final toT = cast.toType; - - // toT <:_R fromT => to <: fromT - // NB: classes with call methods are subtypes of function - // types, but the function type is not assignable to the class - assert(toT.isSubtypeOf(fromT) || fromT.isAssignableTo(toT)); - - // Handle null call specially. - if (expression is NullLiteral) { - // TODO(vsm): Create a NullCast for this once we revisit nonnullability. - return new DownCastImplicit(rules, expression, cast); - } - - // Inference "casts": - if (expression is Literal) { - // fromT should be an exact type - this will almost certainly fail at - // runtime. - return new StaticTypeError(rules, expression, toT, reason: reason); - } - if (expression is FunctionExpression) { - // fromT should be an exact type - this will almost certainly fail at - // runtime. - return new UninferredClosure(rules, expression, cast); - } - if (expression is InstanceCreationExpression) { - // fromT should be an exact type - this will almost certainly fail at - // runtime. - return new StaticTypeError(rules, expression, toT, reason: reason); - } - - // Composite cast: these are more likely to fail. - if (!rules.isGroundType(toT)) { - // This cast is (probably) due to our different treatment of dynamic. - // It may be more likely to fail at runtime. - return new DownCastComposite(rules, expression, cast); - } - - // Dynamic cast - if (fromT.isDynamic) { - return new DynamicCast(rules, expression, cast); - } - - // Assignment cast - var parent = expression.parent; - if (parent is VariableDeclaration && (parent.initializer == expression)) { - return new AssignmentCast(rules, expression, cast); - } - - // Other casts - return new DownCastImplicit(rules, expression, cast); - } -} - -// -// Standard down casts. These casts are implicitly injected by the compiler. -// - -// A down cast from dynamic to T. -class DynamicCast extends DownCast { - DynamicCast(TypeRules rules, Expression expression, Cast cast) - : super._internal(rules, expression, cast); - - toErrorCode() => new HintCode(name, message); -} - -// A down cast due to a variable declaration to a ground type. E.g., -// T x = expr; -// where T is ground. We exclude non-ground types as these behave differently -// compared to standard Dart. -class AssignmentCast extends DownCast { - AssignmentCast(TypeRules rules, Expression expression, Cast cast) - : super._internal(rules, expression, cast); - - toErrorCode() => new HintCode(name, message); -} - -// -// Temporary "casts" of allocation sites - literals, constructor invocations, -// and closures. These should be handled by contextual inference. In most -// cases, inference will be sufficient, though in some it may unmask an actual -// error: e.g., -// List<int> l = [1, 2, 3]; // Inference succeeds -// List<String> l = [1, 2, 3]; // Inference reveals static type error -// We're marking all as warnings for now. -// -// TODO(vsm,leafp): Remove this. -class UninferredClosure extends DownCast { - UninferredClosure(TypeRules rules, FunctionExpression expression, Cast cast) - : super._internal(rules, expression, cast); - - toErrorCode() => new StaticTypeWarningCode(name, message); -} - -// -// Implicit down casts. These are only injected by the compiler by flag. -// - -// A down cast to a non-ground type. These behave differently from standard -// Dart and may be more likely to fail at runtime. -class DownCastComposite extends DownCast { - DownCastComposite(TypeRules rules, Expression expression, Cast cast) - : super._internal(rules, expression, cast); - - toErrorCode() => new StaticTypeWarningCode(name, message); -} - -// A down cast to a non-ground type. These behave differently from standard -// Dart and may be more likely to fail at runtime. -class DownCastImplicit extends DownCast { - DownCastImplicit(TypeRules rules, Expression expression, Cast cast) - : super._internal(rules, expression, cast); - - toErrorCode() => new HintCode(name, message); -} - -// An inferred type for the wrapped expression, which may need to be -// reified into the term -abstract class InferredTypeBase extends CoercionInfo { - final DartType _type; - - InferredTypeBase._internal(TypeRules rules, Expression expression, this._type) - : super(rules, expression); - - DartType get type => _type; - DartType get convertedType => type; - @override String get message => '{0} has inferred type {1}'; - @override List get arguments => [node, type]; - - toErrorCode() => new HintCode(name, message); -} - -// Standard / unspecialized inferred type -class InferredType extends InferredTypeBase { - InferredType(TypeRules rules, Expression expression, DartType type) - : super._internal(rules, expression, type); - - // Factory to create correct InferredType variant. - static InferredTypeBase create( - TypeRules rules, Expression expression, DartType type) { - // Specialized inference: - if (expression is Literal) { - return new InferredTypeLiteral(rules, expression, type); - } - if (expression is InstanceCreationExpression) { - return new InferredTypeAllocation(rules, expression, type); - } - if (expression is FunctionExpression) { - return new InferredTypeClosure(rules, expression, type); - } - return new InferredType(rules, expression, type); - } -} - -// An infered type for a literal expression. -class InferredTypeLiteral extends InferredTypeBase { - InferredTypeLiteral(TypeRules rules, Expression expression, DartType type) - : super._internal(rules, expression, type); -} - -// An inferred type for a non-literal allocation site. -class InferredTypeAllocation extends InferredTypeBase { - InferredTypeAllocation(TypeRules rules, Expression expression, DartType type) - : super._internal(rules, expression, type); -} - -// An inferred type for a closure expression -class InferredTypeClosure extends InferredTypeBase { - InferredTypeClosure(TypeRules rules, Expression expression, DartType type) - : super._internal(rules, expression, type); -} - -class DynamicInvoke extends CoercionInfo { - DynamicInvoke(TypeRules rules, Expression expression) - : super(rules, expression); - - DartType get convertedType => rules.provider.dynamicType; - String get message => '{0} requires dynamic invoke'; - toErrorCode() => new HintCode(name, message); - - static const String _propertyName = 'dev_compiler.src.info.DynamicInvoke'; - - /// Whether this [node] is the target of a dynamic operation. - static bool get(AstNode node) { - var value = node.getProperty(_propertyName); - return value != null ? value : false; - } - - /// Sets whether this node is the target of a dynamic operation. - static bool set(AstNode node, bool value) { - // Free the storage for things that aren't dynamic. - if (value == false) value = null; - node.setProperty(_propertyName, value); - return value; - } -} - -abstract class StaticError extends StaticInfo { - final AstNode node; - - StaticError(this.node); - - String get message; - - toErrorCode() => new CompileTimeErrorCode(name, message); -} - -class StaticTypeError extends StaticError { - final DartType baseType; - final DartType expectedType; - String reason = null; - - StaticTypeError(TypeRules rules, Expression expression, this.expectedType, - {this.reason}) - : baseType = rules.getStaticType(expression), - super(expression); - - @override List<Object> get arguments => [node, baseType, expectedType]; - @override String get message => - 'Type check failed: {0} ({1}) is not of type {2}' + - ((reason == null) ? '' : ' because $reason'); -} - -class InvalidVariableDeclaration extends StaticError { - final DartType expectedType; - - InvalidVariableDeclaration( - TypeRules rules, AstNode declaration, this.expectedType) - : super(declaration); - - @override List<Object> get arguments => [expectedType]; - @override String get message => 'Type check failed: null is not of type {0}'; -} - -class InvalidParameterDeclaration extends StaticError { - final DartType expectedType; - - InvalidParameterDeclaration( - TypeRules rules, FormalParameter declaration, this.expectedType) - : super(declaration); - - @override List<Object> get arguments => [node, expectedType]; - @override String get message => 'Type check failed: {0} is not of type {1}'; -} - -class NonGroundTypeCheckInfo extends StaticInfo { - final DartType type; - final AstNode node; - - NonGroundTypeCheckInfo(this.node, this.type) { - assert(node is IsExpression || node is AsExpression); - } - - @override List<Object> get arguments => [type]; - String get message => - "Runtime check on non-ground type {0} may throw StrongModeError"; - - toErrorCode() => new HintCode(name, message); -} - -// Invalid override of an instance member of a class. -abstract class InvalidOverride extends StaticError { - /// Member declaration with the invalid override. - final ExecutableElement element; - - /// Type (class or interface) that provides the base declaration. - final InterfaceType base; - - /// Actual type of the overridden member. - final DartType subType; - - /// Actual type of the base member. - final DartType baseType; - - /// Whether the error comes from combining a base class and an interface - final bool fromBaseClass; - - /// Whether the error comes from a mixin (either overriding a base class or an - /// interface declaration). - final bool fromMixin; - - InvalidOverride( - AstNode node, this.element, this.base, this.subType, this.baseType) - : fromBaseClass = node is ExtendsClause, - fromMixin = node.parent is WithClause, - super(node); - - ClassElement get parent => element.enclosingElement; - - @override List<Object> get arguments => - [parent.name, element.name, subType, base, baseType]; - - String _messageHelper(String errorName) { - var lcErrorName = errorName.toLowerCase(); - var intro = fromBaseClass - ? 'Base class introduces an $lcErrorName' - : (fromMixin ? 'Mixin introduces an $lcErrorName' : errorName); - return '$intro. The type of {0}.{1} ({2}) is not a ' - 'subtype of {3}.{1} ({4}).'; - } -} - -// Invalid override due to incompatible type. I.e., the overridden signature -// is not compatible with the original. -class InvalidMethodOverride extends InvalidOverride { - InvalidMethodOverride(AstNode node, ExecutableElement element, - InterfaceType base, FunctionType subType, FunctionType baseType) - : super(node, element, base, subType, baseType); - - String get message => _messageHelper('Invalid override'); -} - -/// Used to mark unexpected situations in our compiler were we couldn't compute -/// the type of an expression. -// TODO(sigmund): This is normally a result of another error that is caught by -// the analyzer, so this should likely be removed in the future. -class MissingTypeError extends StaticInfo { - final AstNode node; - toErrorCode() => new StaticTypeWarningCode(name, message); - - MissingTypeError(this.node); - - @override List<Object> get arguments => [node, node.runtimeType]; - String get message => "type analysis didn't compute the type of: {0} {1}"; -} - -/// Dart constructors have one weird quirk, illustrated with this example: -/// -/// class Base { -/// var x; -/// Base() : x = print('Base.1') { -/// print('Base.2'); -/// } -/// } -/// -/// class Derived extends Base { -/// var y, z; -/// Derived() -/// : y = print('Derived.1'), -/// super(), -/// z = print('Derived.2') { -/// print('Derived.3'); -/// } -/// } -/// -/// The order will be Derived.1, Base.1, Derived.2, Base.2, Derived.3; this -/// ordering preserves the invariant that code can't observe uninitialized -/// state, however it results in super constructor body not being run -/// immediately after super initializers. Normally this isn't observable, but it -/// could be if initializers have side effects. -/// -/// Better to have `super` at the end, as required by the Dart style guide: -/// <http://goo.gl/q1T4BB> -/// -/// For now this is the only pattern we support. -class InvalidSuperInvocation extends StaticError { - InvalidSuperInvocation(SuperConstructorInvocation node) : super(node); - - @override String get message => "super call must be last in an initializer " - "list (see http://goo.gl/q1T4BB): {0}"; -}
diff --git a/lib/src/server/server.dart b/lib/src/server/server.dart index 9a6320f..079b231 100644 --- a/lib/src/server/server.dart +++ b/lib/src/server/server.dart
@@ -84,7 +84,7 @@ var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2); _log.fine('Compiled ${_libraries.length} libraries in ${time} s\n'); return new CheckerResults( - _libraries, rules, _failure || options.codegenOptions.forceCompile); + _libraries, _failure || options.codegenOptions.forceCompile); } bool _buildSource(SourceNode node) { @@ -154,9 +154,7 @@ for (var unit in libraryUnit.libraryThenParts) { var unitSource = unit.element.source; // TODO(sigmund): integrate analyzer errors with static-info (issue #6). - failureInLib = logErrors(unitSource) || failureInLib; - checker.visitCompilationUnit(unit); - if (checker.failure) failureInLib = true; + failureInLib = computeErrors(unitSource) || failureInLib; } if (failureInLib) { _failure = true; @@ -310,4 +308,4 @@ } final _log = new Logger('dev_compiler.src.server'); -final _earlyErrorResult = new CheckerResults(const [], null, true); +final _earlyErrorResult = new CheckerResults(const [], true);
diff --git a/lib/src/utils.dart b/lib/src/utils.dart index b561c59..b38f531 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart
@@ -380,30 +380,6 @@ // TODO(vsm): Move this onto the appropriate class. Ideally, we'd attach // it to TypeProvider. -final _objectMap = new Expando('providerToObjectMap'); -Map<String, DartType> getObjectMemberMap(TypeProvider typeProvider) { - var map = _objectMap[typeProvider] as Map<String, DartType>; - if (map == null) { - map = <String, DartType>{}; - _objectMap[typeProvider] = map; - var objectType = typeProvider.objectType; - var element = objectType.element; - // Only record methods (including getters) with no parameters. As parameters are contravariant wrt - // type, using Object's version may be too strict. - // Add instance methods. - element.methods.where((method) => !method.isStatic).forEach((method) { - map[method.name] = method.type; - }); - // Add getters. - element.accessors - .where((member) => !member.isStatic && member.isGetter) - .forEach((member) { - map[member.name] = member.type.returnType; - }); - } - return map; -} - /// Searches all supertype, in order of most derived members, to see if any /// [match] a condition. If so, returns the first match, otherwise returns null. InterfaceType findSupertype(InterfaceType type, bool match(InterfaceType t)) {
diff --git a/lib/strong_mode.dart b/lib/strong_mode.dart index 99cef5c..45d17ab 100644 --- a/lib/strong_mode.dart +++ b/lib/strong_mode.dart
@@ -31,6 +31,7 @@ /// A type checker for Dart code that operates under stronger rules, and has /// the ability to do local type inference in some situations. +// TODO(jmesserly): remove this class. class StrongChecker { final AnalysisContext _context; final CodeChecker _checker; @@ -41,25 +42,28 @@ factory StrongChecker(AnalysisContext context, StrongModeOptions options) { // TODO(vsm): Remove this once analyzer_cli is completely switched to the // task model. - if (!AnalysisEngine - .instance.useTaskModel) enableDevCompilerInference(context, options); - var rules = new TypeRules(context.typeProvider, options: options); - var reporter = new _ErrorCollector(options.hints); - var checker = new CodeChecker(rules, reporter); - return new StrongChecker._(context, checker, reporter); + if (!AnalysisEngine.instance.useTaskModel) { + enableDevCompilerInference(context, options); + var rules = new TypeRules(context.typeProvider); + var reporter = new _ErrorCollector(options.hints); + var checker = new CodeChecker(rules, reporter); + return new StrongChecker._(context, checker, reporter); + } + return new StrongChecker._(context, null, null); } /// Computes and returns DDC errors for the [source]. AnalysisErrorInfo computeErrors(Source source) { var errors = new List<AnalysisError>(); - _reporter.errors = errors; + if (_checker != null) { + _reporter.errors = errors; - for (Source librarySource in _context.getLibrariesContaining(source)) { - var resolved = _context.resolveCompilationUnit2(source, librarySource); - _checker.visitCompilationUnit(resolved); + for (Source librarySource in _context.getLibrariesContaining(source)) { + var resolved = _context.resolveCompilationUnit2(source, librarySource); + _checker.visitCompilationUnit(resolved); + } + _reporter.errors = null; } - _reporter.errors = null; - return new AnalysisErrorInfoImpl(errors, _context.getLineInfo(source)); } }
diff --git a/pubspec.yaml b/pubspec.yaml index 7c9dd37..0f56a4c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml
@@ -41,3 +41,6 @@ # Similar to dartdevc, but runs the (single) entry point with iojs (requires # a very recent iojs next-nightly version). dartdevrun: devrun + +dependency_overrides: + analyzer: { path: ../dart/sdk/pkg/analyzer }
diff --git a/test/all_tests.dart b/test/all_tests.dart index bd97444..b7c60ca 100644 --- a/test/all_tests.dart +++ b/test/all_tests.dart
@@ -7,8 +7,6 @@ import 'package:test/test.dart'; -import 'checker/checker_test.dart' as checker_test; -import 'checker/inferred_type_test.dart' as inferred_type_test; import 'checker/self_host_test.dart' as self_host; import 'closure/closure_annotation_test.dart' as closure_annotation_test; import 'closure/closure_type_test.dart' as closure_type_test; @@ -19,8 +17,6 @@ void main() { group('end-to-end', e2e.main); - group('inferred types', inferred_type_test.main); - group('checker', checker_test.main); group('report', report_test.main); group('dependency_graph', dependency_graph_test.main); group('codegen', () => codegen_test.main([]));
diff --git a/test/testing.dart b/test/testing.dart index 6874ed3..faa90be 100644 --- a/test/testing.dart +++ b/test/testing.dart
@@ -28,17 +28,21 @@ import 'package:dev_compiler/src/utils.dart'; /// Shared analysis context used for compilation. -final realSdkContext = createAnalysisContextWithSources( - new StrongModeOptions(), - new SourceResolverOptions( - dartSdkPath: getSdkDir().path, - customUrlMappings: { - 'package:expect/expect.dart': _testCodegenPath('expect.dart'), - 'package:async_helper/async_helper.dart': - _testCodegenPath('async_helper.dart'), - 'package:unittest/unittest.dart': _testCodegenPath('unittest.dart'), - 'package:dom/dom.dart': _testCodegenPath('sunflower', 'dom.dart') - }))..analysisOptions.cacheSize = 512; +final AnalysisContext realSdkContext = () { + var context = createAnalysisContextWithSources( + new StrongModeOptions(), + new SourceResolverOptions( + dartSdkPath: getSdkDir().path, + customUrlMappings: { + 'package:expect/expect.dart': _testCodegenPath('expect.dart'), + 'package:async_helper/async_helper.dart': + _testCodegenPath('async_helper.dart'), + 'package:unittest/unittest.dart': _testCodegenPath('unittest.dart'), + 'package:dom/dom.dart': _testCodegenPath('sunflower', 'dom.dart') + })); + (context.analysisOptions as AnalysisOptionsImpl).cacheSize = 512; + return context; +}(); String _testCodegenPath(String p1, [String p2]) => path.join(testDirectory, 'codegen', p1, p2);