| // 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 'dart:collection'; |
| import 'package:analyzer/analyzer.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/ast/standard_resolution_map.dart'; |
| import 'package:analyzer/dart/ast/token.dart' show TokenType; |
| import 'package:analyzer/dart/ast/token.dart'; |
| import 'package:analyzer/dart/ast/visitor.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/source/error_processor.dart' show ErrorProcessor; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/member.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/error/codes.dart' show StrongModeCode; |
| import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; |
| import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
| import 'package:analyzer/src/generated/type_system.dart'; |
| import 'package:analyzer/src/summary/idl.dart'; |
| |
| import 'ast_properties.dart'; |
| |
| /// Given an [expression] and a corresponding [typeSystem] and [typeProvider], |
| /// gets the known static type of the expression. |
| /// |
| /// Normally when we ask for an expression's type, we get the type of the |
| /// storage slot that would contain it. For function types, this is necessarily |
| /// a "fuzzy arrow" that treats `dynamic` as bottom. However, if we're |
| /// interested in the expression's own type, it can often be a "strict arrow" |
| /// because we know it evaluates to a specific, concrete function, and we can |
| /// treat "dynamic" as top for that case, which is more permissive. |
| DartType getDefiniteType( |
| Expression expression, TypeSystem typeSystem, TypeProvider typeProvider) { |
| DartType type = expression.staticType ?? DynamicTypeImpl.instance; |
| if (typeSystem is StrongTypeSystemImpl && |
| type is FunctionType && |
| hasStrictArrow(expression)) { |
| // Remove fuzzy arrow if possible. |
| return typeSystem.functionTypeToConcreteType(type); |
| } |
| return type; |
| } |
| |
| bool hasStrictArrow(Expression expression) { |
| var element = _getKnownElement(expression); |
| return element is FunctionElement || element is MethodElement; |
| } |
| |
| /// Given a generic class [element] find its covariant upper bound, using |
| /// the type system [rules]. |
| /// |
| /// Unlike [TypeSystem.instantiateToBounds], this will change `dynamic` into |
| /// `Object` to work around an issue with fuzzy arrows. |
| InterfaceType _getCovariantUpperBound(TypeSystem rules, ClassElement element) { |
| var upperBound = rules.instantiateToBounds(element.type) as InterfaceType; |
| var typeArgs = upperBound.typeArguments; |
| // TODO(jmesserly): remove this. It is a workaround for fuzzy arrows. |
| // To prevent extra checks due to fuzzy arrows, we need to instantiate with |
| // `Object` rather than `dynamic`. Consider a case like: |
| // |
| // class C<T> { |
| // void forEach(f(T t)) {} |
| // } |
| // |
| // If we try `(dynamic) ~> void <: (T) ~> void` with fuzzy arrows, we will |
| // treat `dynamic` as `bottom` and get `(bottom) -> void <: (T) -> void` |
| // which indicates that a check is required on the parameter `f`. This check |
| // is not sufficient when `T` is `dynamic`, however, because calling a |
| // function with a fuzzy arrow type is not safe and requires a dynamic call. |
| // See: https://github.com/dart-lang/sdk/issues/29295 |
| // |
| // For all other values of T, the check is unnecessary: it is sound to pass |
| // a function that accepts any Object. |
| if (typeArgs.any((t) => t.isDynamic)) { |
| var newTypeArgs = typeArgs |
| .map((t) => t.isDynamic ? rules.typeProvider.objectType : t) |
| .toList(); |
| upperBound = element.type.instantiate(newTypeArgs); |
| } |
| return upperBound; |
| } |
| |
| DartType _elementType(Element e) { |
| if (e == null) { |
| // Malformed code - just return dynamic. |
| return DynamicTypeImpl.instance; |
| } |
| return (e as dynamic).type; |
| } |
| |
| Element _getKnownElement(Expression expression) { |
| if (expression is ParenthesizedExpression) { |
| return _getKnownElement(expression.expression); |
| } else if (expression is NamedExpression) { |
| return _getKnownElement(expression.expression); |
| } else if (expression is FunctionExpression) { |
| return expression.element; |
| } else if (expression is PropertyAccess) { |
| return expression.propertyName.staticElement; |
| } else if (expression is Identifier) { |
| return expression.staticElement; |
| } |
| return null; |
| } |
| |
| /// Return the field on type corresponding to member, or null if none |
| /// exists or the "field" is actually a getter/setter. |
| FieldElement _getMemberField( |
| InterfaceType type, PropertyAccessorElement member) { |
| String memberName = member.name; |
| FieldElement field; |
| if (member.isGetter) { |
| // The subclass member is an explicit getter or a field |
| // - lookup the getter on the superclass. |
| var getter = type.getGetter(memberName); |
| if (getter == null || getter.isStatic) return null; |
| field = getter.variable; |
| } else if (!member.isSynthetic) { |
| // The subclass member is an explicit setter |
| // - lookup the setter on the superclass. |
| // Note: an implicit (synthetic) setter would have already been flagged on |
| // the getter above. |
| var setter = type.getSetter(memberName); |
| if (setter == null || setter.isStatic) return null; |
| field = setter.variable; |
| } else { |
| return null; |
| } |
| if (field.isSynthetic) return null; |
| return field; |
| } |
| |
| /// Looks up the declaration that matches [member] in [type] and returns it's |
| /// declared type. |
| FunctionType _getMemberType(InterfaceType type, ExecutableElement member) { |
| if (member.isPrivate && type.element.library != member.library) { |
| return null; |
| } |
| |
| var name = member.name; |
| var baseMember = member is PropertyAccessorElement |
| ? (member.isGetter ? type.getGetter(name) : type.getSetter(name)) |
| : type.getMethod(name); |
| if (baseMember == null || baseMember.isStatic) return null; |
| return baseMember.type; |
| } |
| |
| /// Checks the body of functions and properties. |
| class CodeChecker extends RecursiveAstVisitor { |
| final StrongTypeSystemImpl rules; |
| final TypeProvider typeProvider; |
| final AnalysisErrorListener reporter; |
| final AnalysisOptionsImpl _options; |
| _OverrideChecker _overrideChecker; |
| |
| bool _failure = false; |
| bool _hasImplicitCasts; |
| HashSet<ExecutableElement> _covariantPrivateMembers; |
| |
| CodeChecker(TypeProvider typeProvider, StrongTypeSystemImpl rules, |
| AnalysisErrorListener reporter, this._options) |
| : typeProvider = typeProvider, |
| rules = rules, |
| reporter = reporter { |
| _overrideChecker = new _OverrideChecker(this); |
| } |
| |
| bool get failure => _failure; |
| |
| void checkArgument(Expression arg, DartType expectedType) { |
| // Preserve named argument structure, so their immediate parent is the |
| // method invocation. |
| Expression baseExpression = arg is NamedExpression ? arg.expression : arg; |
| checkAssignment(baseExpression, expectedType); |
| } |
| |
| 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) { |
| // We found an argument mismatch, the analyzer will report this too, |
| // so no need to insert an error for this here. |
| continue; |
| } |
| checkArgument(arg, _elementType(element)); |
| } |
| } |
| |
| void checkAssignment(Expression expr, DartType type) { |
| if (expr is ParenthesizedExpression) { |
| checkAssignment(expr.expression, type); |
| } else { |
| _checkImplicitCast(expr, 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, typeProvider.boolType); |
| |
| void _checkFunctionApplication(InvocationExpression node) { |
| var ft = _getTypeAsCaller(node); |
| |
| if (_isDynamicCall(node, ft)) { |
| // 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, node.function); |
| } else { |
| checkArgumentList(node.argumentList, ft); |
| } |
| } |
| |
| DartType getType(TypeAnnotation type) { |
| return type?.type ?? DynamicTypeImpl.instance; |
| } |
| |
| void reset() { |
| _failure = false; |
| } |
| |
| @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 visitAssignmentExpression(AssignmentExpression node) { |
| Token operator = node.operator; |
| TokenType operatorType = operator.type; |
| if (operatorType == TokenType.EQ || |
| operatorType == TokenType.QUESTION_QUESTION_EQ) { |
| DartType staticType = _getDefiniteType(node.leftHandSide); |
| checkAssignment(node.rightHandSide, staticType); |
| } else if (operatorType == TokenType.AMPERSAND_AMPERSAND_EQ || |
| operatorType == TokenType.BAR_BAR_EQ) { |
| checkAssignment(node.leftHandSide, typeProvider.boolType); |
| checkAssignment(node.rightHandSide, typeProvider.boolType); |
| } else { |
| _checkCompoundAssignment(node); |
| } |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitBinaryExpression(BinaryExpression node) { |
| var op = node.operator; |
| if (op.isUserDefinableOperator) { |
| var element = node.staticElement; |
| if (element == null) { |
| // 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 { |
| // 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 visitClassDeclaration(ClassDeclaration node) { |
| _overrideChecker.check(node); |
| super.visitClassDeclaration(node); |
| } |
| |
| @override |
| void visitClassTypeAlias(ClassTypeAlias node) { |
| _overrideChecker.check(node); |
| super.visitClassTypeAlias(node); |
| } |
| |
| @override |
| void visitComment(Comment node) { |
| // skip, no need to do typechecking inside comments (they may contain |
| // comment references which would require resolution). |
| } |
| |
| @override |
| void visitCompilationUnit(CompilationUnit node) { |
| _hasImplicitCasts = false; |
| _covariantPrivateMembers = new HashSet(); |
| node.visitChildren(this); |
| setHasImplicitCasts(node, _hasImplicitCasts); |
| setCovariantPrivateMembers(node, _covariantPrivateMembers); |
| } |
| |
| @override |
| void visitConditionalExpression(ConditionalExpression node) { |
| checkBoolean(node.condition); |
| 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(node, StrongModeCode.INVALID_SUPER_INVOCATION, [node]); |
| } |
| } |
| } |
| |
| // Check invocations |
| @override |
| void visitConstructorFieldInitializer(ConstructorFieldInitializer node) { |
| var field = node.fieldName; |
| var element = field.staticElement; |
| DartType staticType = _elementType(element); |
| checkAssignment(node.expression, staticType); |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitDefaultFormalParameter(DefaultFormalParameter node) { |
| // Check that defaults have the proper subtype. |
| var parameter = node.parameter; |
| var parameterType = _elementType(parameter.element); |
| assert(parameterType != null); |
| var defaultValue = node.defaultValue; |
| if (defaultValue != null) { |
| checkAssignment(defaultValue, parameterType); |
| } |
| |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitDoStatement(DoStatement node) { |
| checkBoolean(node.condition); |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitExpressionFunctionBody(ExpressionFunctionBody node) { |
| _checkReturnOrYield(node.expression, node); |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitFieldFormalParameter(FieldFormalParameter node) { |
| var element = node.element; |
| var typeName = node.type; |
| if (typeName != null) { |
| var type = _elementType(element); |
| var fieldElement = |
| node.identifier.staticElement as FieldFormalParameterElement; |
| var fieldType = _elementType(fieldElement.field); |
| if (!rules.isSubtypeOf(type, fieldType)) { |
| _recordMessage(node, StrongModeCode.INVALID_PARAMETER_DECLARATION, |
| [node, fieldType]); |
| } |
| } |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitForEachStatement(ForEachStatement node) { |
| var loopVariable = node.identifier ?? node.loopVariable?.identifier; |
| |
| // Safely handle malformed statements. |
| if (loopVariable != null) { |
| // Find the element type of the sequence. |
| var sequenceInterface = node.awaitKeyword != null |
| ? typeProvider.streamType |
| : typeProvider.iterableType; |
| var iterableType = _getDefiniteType(node.iterable); |
| var elementType = |
| rules.mostSpecificTypeArgument(iterableType, sequenceInterface); |
| |
| // If the sequence is not an Iterable (or Stream for await for) but is a |
| // supertype of it, do an implicit downcast to Iterable<dynamic>. Then |
| // we'll do a separate cast of the dynamic element to the variable's type. |
| if (elementType == null) { |
| var sequenceType = |
| sequenceInterface.instantiate([DynamicTypeImpl.instance]); |
| |
| if (rules.isSubtypeOf(sequenceType, iterableType)) { |
| _recordImplicitCast(node.iterable, sequenceType, from: iterableType); |
| elementType = DynamicTypeImpl.instance; |
| } |
| } |
| |
| // If the sequence doesn't implement the interface at all, [ErrorVerifier] |
| // will report the error, so ignore it here. |
| if (elementType != null) { |
| // Insert a cast from the sequence's element type to the loop variable's |
| // if needed. |
| _checkImplicitCast(loopVariable, _getDefiniteType(loopVariable), |
| from: elementType); |
| } |
| } |
| |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitForStatement(ForStatement node) { |
| if (node.condition != null) { |
| checkBoolean(node.condition); |
| } |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
| _checkFunctionApplication(node); |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitIfStatement(IfStatement node) { |
| checkBoolean(node.condition); |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitIndexExpression(IndexExpression node) { |
| var target = node.realTarget; |
| var element = node.staticElement; |
| if (element == null) { |
| _recordDynamicInvoke(node, target); |
| } else 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); |
| } |
| |
| @override |
| void visitInstanceCreationExpression(InstanceCreationExpression node) { |
| var arguments = node.argumentList; |
| var element = node.staticElement; |
| if (element != null) { |
| var type = _elementType(node.staticElement); |
| checkArgumentList(arguments, type); |
| } |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitIsExpression(IsExpression node) { |
| _checkRuntimeTypeCheck(node, node.type); |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitListLiteral(ListLiteral node) { |
| DartType type = DynamicTypeImpl.instance; |
| if (node.typeArguments != null) { |
| NodeList<TypeAnnotation> targs = node.typeArguments.arguments; |
| if (targs.length > 0) { |
| type = targs[0].type; |
| } |
| } else { |
| DartType staticType = node.staticType; |
| if (staticType is InterfaceType) { |
| List<DartType> targs = staticType.typeArguments; |
| if (targs != null && targs.length > 0) { |
| type = targs[0]; |
| } |
| } |
| } |
| NodeList<Expression> elements = node.elements; |
| for (int i = 0; i < elements.length; i++) { |
| checkArgument(elements[i], type); |
| } |
| super.visitListLiteral(node); |
| } |
| |
| @override |
| void visitMapLiteral(MapLiteral node) { |
| DartType ktype = DynamicTypeImpl.instance; |
| DartType vtype = DynamicTypeImpl.instance; |
| if (node.typeArguments != null) { |
| NodeList<TypeAnnotation> targs = node.typeArguments.arguments; |
| if (targs.length > 0) { |
| ktype = targs[0].type; |
| } |
| if (targs.length > 1) { |
| vtype = targs[1].type; |
| } |
| } else { |
| DartType staticType = node.staticType; |
| if (staticType is InterfaceType) { |
| List<DartType> targs = staticType.typeArguments; |
| if (targs != null) { |
| if (targs.length > 0) { |
| ktype = targs[0]; |
| } |
| if (targs.length > 1) { |
| vtype = targs[1]; |
| } |
| } |
| } |
| } |
| NodeList<MapLiteralEntry> entries = node.entries; |
| for (int i = 0; i < entries.length; i++) { |
| MapLiteralEntry entry = entries[i]; |
| checkArgument(entry.key, ktype); |
| checkArgument(entry.value, vtype); |
| } |
| super.visitMapLiteral(node); |
| } |
| |
| @override |
| visitMethodInvocation(MethodInvocation node) { |
| var target = node.realTarget; |
| var element = node.methodName.staticElement; |
| if (element == null && !typeProvider.isObjectMethod(node.methodName.name)) { |
| _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]. |
| setIsDynamicInvoke(node.methodName, true); |
| } else { |
| _checkImplicitCovarianceCast(node, target, element); |
| _checkFunctionApplication(node); |
| } |
| // Don't visit methodName, we already checked things related to the call. |
| node.target?.accept(this); |
| node.typeArguments?.accept(this); |
| node.argumentList?.accept(this); |
| } |
| |
| @override |
| void visitPostfixExpression(PostfixExpression node) { |
| _checkUnary(node.operand, node.operator, node.staticElement); |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitPrefixedIdentifier(PrefixedIdentifier node) { |
| _checkFieldAccess(node, node.prefix, node.identifier); |
| } |
| |
| @override |
| void visitPrefixExpression(PrefixExpression node) { |
| if (node.operator.type == TokenType.BANG) { |
| checkBoolean(node.operand); |
| } else { |
| _checkUnary(node.operand, node.operator, node.staticElement); |
| } |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitPropertyAccess(PropertyAccess node) { |
| _checkFieldAccess(node, node.realTarget, node.propertyName); |
| } |
| |
| @override |
| void visitRedirectingConstructorInvocation( |
| RedirectingConstructorInvocation node) { |
| var type = resolutionMap.staticElementForConstructorReference(node)?.type; |
| // TODO(leafp): There's a TODO in visitRedirectingConstructorInvocation |
| // in the element_resolver to handle the case that the element is null |
| // and emit an error. In the meantime, just be defensive here. |
| if (type != null) { |
| checkArgumentList(node.argumentList, type); |
| } |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitReturnStatement(ReturnStatement node) { |
| _checkReturnOrYield(node.expression, node); |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
| var element = node.staticElement; |
| if (element != null) { |
| var type = resolutionMap.staticElementForConstructorReference(node).type; |
| checkArgumentList(node.argumentList, type); |
| } |
| 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 |
| Object visitVariableDeclaration(VariableDeclaration node) { |
| VariableElement variableElement = node == null |
| ? null |
| : resolutionMap.elementDeclaredByVariableDeclaration(node); |
| if (!node.isConst && |
| !node.isFinal && |
| node.initializer == null && |
| rules.isNonNullableType(variableElement?.type)) { |
| _recordMessage( |
| node, |
| StaticTypeWarningCode.NON_NULLABLE_FIELD_NOT_INITIALIZED, |
| [node.name, variableElement?.type]); |
| } |
| AstNode parent = node.parent; |
| if (variableElement != null && |
| parent is VariableDeclarationList && |
| parent.type == null && |
| node.initializer != null) { |
| if (variableElement.kind == ElementKind.TOP_LEVEL_VARIABLE || |
| variableElement.kind == ElementKind.FIELD) { |
| _validateTopLevelInitializer(variableElement.name, node.initializer); |
| } |
| } |
| return super.visitVariableDeclaration(node); |
| } |
| |
| @override |
| void visitVariableDeclarationList(VariableDeclarationList node) { |
| TypeAnnotation type = node.type; |
| |
| for (VariableDeclaration variable in node.variables) { |
| var initializer = variable.initializer; |
| if (initializer != null) { |
| if (type != null) { |
| checkAssignment(initializer, type.type); |
| } |
| } |
| } |
| |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitWhileStatement(WhileStatement node) { |
| checkBoolean(node.condition); |
| node.visitChildren(this); |
| } |
| |
| @override |
| void visitYieldStatement(YieldStatement node) { |
| _checkReturnOrYield(node.expression, node, yieldStar: node.star != null); |
| node.visitChildren(this); |
| } |
| |
| void _checkCompoundAssignment(AssignmentExpression expr) { |
| var op = expr.operator.type; |
| assert(op.isAssignmentOperator && op != TokenType.EQ); |
| var methodElement = resolutionMap.staticElementForMethodReference(expr); |
| 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); |
| |
| // Refine the return type. |
| var rhsType = _getDefiniteType(expr.rightHandSide); |
| var lhsType = _getDefiniteType(expr.leftHandSide); |
| var returnType = rules.refineBinaryExpressionType( |
| lhsType, op, rhsType, functionType.returnType); |
| |
| // Check the argument for an implicit cast. |
| _checkImplicitCast(expr.rightHandSide, paramTypes[0], from: rhsType); |
| |
| // Check the return type for an implicit cast. |
| // |
| // If needed, mark the assignment to indicate a down cast when we assign |
| // back to it. So these two implicit casts are equivalent: |
| // |
| // y = /*implicit cast*/(y + 42); |
| // /*implicit assignment cast*/y += 42; |
| // |
| _checkImplicitCast(expr.leftHandSide, lhsType, |
| from: returnType, opAssign: true); |
| } |
| } |
| |
| void _checkFieldAccess( |
| AstNode node, Expression target, SimpleIdentifier field) { |
| var element = field.staticElement; |
| _checkImplicitCovarianceCast(node, target, element); |
| if (element == null && !typeProvider.isObjectMember(field.name)) { |
| _recordDynamicInvoke(node, target); |
| } |
| node.visitChildren(this); |
| } |
| |
| /// Checks if an implicit cast of [expr] from [from] type to [to] type is |
| /// needed, and if so records it. |
| /// |
| /// If [from] is omitted, uses the static type of [expr]. |
| /// |
| /// If [expr] does not require an implicit cast because it is not related to |
| /// [to] or is already a subtype of it, does nothing. |
| void _checkImplicitCast(Expression expr, DartType to, |
| {DartType from, bool opAssign: false}) { |
| from ??= _getDefiniteType(expr); |
| |
| if (_needsImplicitCast(expr, to, from: from) == true) { |
| _recordImplicitCast(expr, to, from: from, opAssign: opAssign); |
| } |
| } |
| |
| /// Checks if the assignment is valid with respect to non-nullable types. |
| /// Returns `false` if a nullable expression is assigned to a variable of |
| /// non-nullable type and `true` otherwise. |
| bool _checkNonNullAssignment( |
| Expression expression, DartType to, DartType from) { |
| if (rules.isNonNullableType(to) && rules.isNullableType(from)) { |
| _recordMessage( |
| expression, StaticTypeWarningCode.INVALID_ASSIGNMENT, [from, to]); |
| return false; |
| } |
| return true; |
| } |
| |
| void _checkReturnOrYield(Expression expression, AstNode node, |
| {bool yieldStar: false}) { |
| FunctionBody body = node.getAncestor((n) => n is FunctionBody); |
| var type = _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); |
| } |
| |
| void _checkRuntimeTypeCheck(AstNode node, TypeAnnotation annotation) { |
| var type = getType(annotation); |
| if (!rules.isGroundType(type)) { |
| _recordMessage(node, StrongModeCode.NON_GROUND_TYPE_CHECK_INFO, [type]); |
| } |
| } |
| |
| void _checkUnary(Expression operand, Token op, MethodElement element) { |
| bool isIncrementAssign = |
| op.type == TokenType.PLUS_PLUS || op.type == TokenType.MINUS_MINUS; |
| if (op.isUserDefinableOperator || isIncrementAssign) { |
| if (element == null) { |
| _recordDynamicInvoke(operand.parent, operand); |
| } else if (isIncrementAssign) { |
| // 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 (in ErrorVerifier). |
| // |
| // However, we also need to check the return type. |
| |
| // Refine the return type. |
| var functionType = element.type; |
| var rhsType = typeProvider.intType; |
| var lhsType = _getDefiniteType(operand); |
| var returnType = rules.refineBinaryExpressionType( |
| lhsType, TokenType.PLUS, rhsType, functionType.returnType); |
| |
| // Skip the argument check - `int` cannot be downcast. |
| // |
| // Check the return type for an implicit cast. |
| // |
| // If needed, mark the assignment to indicate a down cast when we assign |
| // back to it. So these two implicit casts are equivalent: |
| // |
| // y = /*implicit cast*/(y + 1); |
| // /*implicit assignment cast*/y++; |
| // |
| _checkImplicitCast(operand, lhsType, from: returnType, opAssign: true); |
| } |
| } |
| } |
| |
| DartType _getDefiniteType(Expression expr) => |
| getDefiniteType(expr, rules, typeProvider); |
| |
| /// If we're calling into [member] through the [target], we may need to |
| /// insert a caller side check for soundness on the result of the expression |
| /// [node]. |
| /// |
| /// This happens when [target] is an unsafe covariant interface, and [member] |
| /// could return a type that is not a subtype of the expected static type |
| /// given target's type. For example: |
| /// |
| /// typedef F<T>(T t); |
| /// class C<T> { |
| /// F<T> f; |
| /// C(this.f); |
| /// } |
| /// test1() { |
| /// C<Object> c = new C<int>((int x) => x + 42)); |
| /// F<Object> f = c.f; // need an implicit cast here. |
| /// f('hello'); |
| /// } |
| /// |
| /// Here target is `c`, the target type is `C<Object>`, the member is |
| /// `get f() -> F<T>`, and the expression node is `c.f`. When we call `c.f` |
| /// the expected static result is `F<Object>`. However `c.f` actually returns |
| /// `F<int>`, which is not a subtype of `F<Object>`. So this method will add |
| /// an implicit cast `(c.f as F<Object>)` to guard against this case. |
| /// |
| /// Note that it is possible for the cast to succeed, for example: |
| /// `new C<int>((Object x) => '$x'))`. It is safe to pass any object to that |
| /// function, including an `int`. |
| void _checkImplicitCovarianceCast( |
| Expression node, Expression target, Element member) { |
| // If we're calling an instance method or getter, then we |
| // want to check the result type. |
| // |
| // We intentionally ignore method tear-offs, because those methods have |
| // covariance checks for their parameters inside the method. |
| var targetType = target?.staticType; |
| if (member is ExecutableElement && |
| _isInstanceMember(member) && |
| targetType is InterfaceType && |
| targetType.typeArguments.isNotEmpty && |
| !_targetHasKnownGenericTypeArguments(target)) { |
| // Track private setters/method calls. We can sometimes eliminate the |
| // parameter check in code generation, if it was never needed. |
| // This member will need a check, however, because we are calling through |
| // an unsafe target. |
| if (member.isPrivate && member.parameters.isNotEmpty) { |
| _covariantPrivateMembers |
| .add(member is ExecutableMember ? member.baseElement : member); |
| } |
| |
| // Get the lower bound of the declared return type (e.g. `F<Null>`) and |
| // see if it can be assigned to the expected type (e.g. `F<Object>`). |
| // |
| // That way we can tell if any lower `T` will work or not. |
| var classType = targetType.element.type; |
| var classLowerBound = classType.instantiate(new List.filled( |
| classType.typeParameters.length, typeProvider.nullType)); |
| var memberLowerBound = _lookUpMember(classLowerBound, member).type; |
| var expectedType = member.returnType; |
| |
| if (!rules.isSubtypeOf(memberLowerBound.returnType, expectedType)) { |
| if (node is MethodInvocation && member is! MethodElement) { |
| // If `o.m` is not a method, we need to cast `o.m` before the call: |
| // `(o.m as expectedType)(args)`. |
| // This cannot be represented by an `as` node without changing the |
| // Dart AST structure, so we record it as a special cast. |
| setImplicitOperationCast(node, expectedType); |
| } else { |
| setImplicitCast(node, expectedType); |
| } |
| _hasImplicitCasts = true; |
| } |
| } |
| } |
| |
| /// Returns true if we can safely skip the covariance checks because [target] |
| /// has known type arguments, such as `this` `super` or a non-factory `new`. |
| /// |
| /// For example: |
| /// |
| /// class C<T> { |
| /// T _t; |
| /// } |
| /// class D<T> extends C<T> { |
| /// method<S extends T>(T t, C<T> c) { |
| /// // implicit cast: t as T; |
| /// // implicit cast: c as C<T>; |
| /// |
| /// // These do not need further checks. The type parameter `T` for |
| /// // `this` must be the same as our `T` |
| /// this._t = t; |
| /// super._t = t; |
| /// new C<T>()._t = t; // non-factory |
| /// |
| /// // This needs further checks. The type of `c` could be `C<S>` for |
| /// // some `S <: T`. |
| /// c._t = t; |
| /// // factory statically returns `C<T>`, dynamically returns `C<S>`. |
| /// new F<T, S>()._t = t; |
| /// } |
| /// } |
| /// class F<T, S extends T> extends C<T> { |
| /// factory F() => new C<S>(); |
| /// } |
| /// |
| bool _targetHasKnownGenericTypeArguments(Expression target) { |
| return target == null || // implicit this |
| target is ThisExpression || |
| target is SuperExpression || |
| target is InstanceCreationExpression && |
| target.staticElement?.isFactory == false; |
| } |
| |
| bool _isInstanceMember(ExecutableElement e) => |
| !e.isStatic && |
| (e is MethodElement || |
| e is PropertyAccessorElement && e.variable is FieldElement); |
| |
| ExecutableElement _lookUpMember(InterfaceType type, ExecutableElement e) { |
| var name = e.name; |
| var library = e.library; |
| return e is PropertyAccessorElement |
| ? (e.isGetter |
| ? type.lookUpInheritedGetter(name, library: library) |
| : type.lookUpInheritedSetter(name, library: library)) |
| : type.lookUpInheritedMethod(name, library: library); |
| } |
| |
| /// 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 = |
| (parent as FunctionExpression).staticType ?? DynamicTypeImpl.instance; |
| } |
| |
| var type = functionType.returnType; |
| |
| InterfaceType expectedType = null; |
| if (body.isAsynchronous) { |
| if (body.isGenerator) { |
| // Stream<T> -> T |
| expectedType = typeProvider.streamType; |
| } else { |
| // Future<T> -> FutureOr<T> |
| var typeArg = (type.element == typeProvider.futureType.element) |
| ? (type as InterfaceType).typeArguments[0] |
| : typeProvider.dynamicType; |
| return typeProvider.futureOrType.instantiate([typeArg]); |
| } |
| } else { |
| if (body.isGenerator) { |
| // Iterable<T> -> T |
| expectedType = typeProvider.iterableType; |
| } else { |
| // T -> T |
| return type; |
| } |
| } |
| if (yieldStar) { |
| if (type.isDynamic) { |
| // Ensure it's at least a Stream / Iterable. |
| return expectedType.instantiate([typeProvider.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; |
| } |
| } |
| |
| /// 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(InvocationExpression node) { |
| DartType type = node.staticInvokeType; |
| if (type is FunctionType) { |
| return type; |
| } else if (type is InterfaceType) { |
| return rules.getCallMethodDefiniteType(type); |
| } |
| return null; |
| } |
| |
| /// Returns `true` if the expression is a dynamic function call or method |
| /// invocation. |
| bool _isDynamicCall(InvocationExpression call, FunctionType ft) { |
| // 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 (hasStrictArrow(call.function)) { |
| return false; |
| } |
| return rules.anyParameterType(ft, (pt) => pt.isDynamic); |
| } |
| |
| /// Returns true if we need an implicit cast of [expr] from [from] type to |
| /// [to] type, returns false if no cast is needed, and returns null if the |
| /// types are statically incompatible. |
| /// |
| /// If [from] is omitted, uses the static type of [expr] |
| bool _needsImplicitCast(Expression expr, DartType to, {DartType from}) { |
| from ??= _getDefiniteType(expr); |
| |
| if (!_checkNonNullAssignment(expr, to, from)) return false; |
| |
| // We can use anything as void. |
| if (to.isVoid) return false; |
| |
| // fromT <: toT, no coercion needed. |
| if (rules.isSubtypeOf(from, to)) return false; |
| |
| // Down cast or legal sideways cast, coercion needed. |
| if (rules.isAssignableTo(from, to)) return true; |
| |
| // Special case for FutureOr to handle returned values from async functions. |
| // In this case, we're more permissive than assignability. |
| if (to.element == typeProvider.futureOrType.element) { |
| var to1 = (to as InterfaceType).typeArguments[0]; |
| var to2 = typeProvider.futureType.instantiate([to1]); |
| return _needsImplicitCast(expr, to1, from: from) == true || |
| _needsImplicitCast(expr, to2, from: from) == true; |
| } |
| |
| // Anything else is an illegal sideways cast. |
| // However, these will have been reported already in error_verifier, so we |
| // don't need to report them again. |
| return null; |
| } |
| |
| void _recordDynamicInvoke(AstNode node, Expression target) { |
| _recordMessage(node, StrongModeCode.DYNAMIC_INVOKE, [node]); |
| // 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. |
| if (target != null) setIsDynamicInvoke(target, true); |
| } |
| |
| /// Records an implicit cast for the [expr] from [from] to [to]. |
| /// |
| /// This will emit the appropriate error/warning/hint message as well as mark |
| /// the AST node. |
| void _recordImplicitCast(Expression expr, DartType to, |
| {DartType from, bool opAssign: false}) { |
| // Inference "casts": |
| if (expr is Literal) { |
| // fromT should be an exact type - this will almost certainly fail at |
| // runtime. |
| if (expr is ListLiteral) { |
| _recordMessage( |
| expr, StrongModeCode.INVALID_CAST_LITERAL_LIST, [from, to]); |
| } else if (expr is MapLiteral) { |
| _recordMessage( |
| expr, StrongModeCode.INVALID_CAST_LITERAL_MAP, [from, to]); |
| } else { |
| _recordMessage( |
| expr, StrongModeCode.INVALID_CAST_LITERAL, [expr, from, to]); |
| } |
| return; |
| } |
| |
| if (expr is FunctionExpression) { |
| _recordMessage( |
| expr, StrongModeCode.INVALID_CAST_FUNCTION_EXPR, [from, to]); |
| return; |
| } |
| |
| if (expr is InstanceCreationExpression) { |
| ConstructorElement e = expr.staticElement; |
| if (e == null || !e.isFactory) { |
| // fromT should be an exact type - this will almost certainly fail at |
| // runtime. |
| _recordMessage(expr, StrongModeCode.INVALID_CAST_NEW_EXPR, [from, to]); |
| return; |
| } |
| } |
| |
| Element e = _getKnownElement(expr); |
| if (e is FunctionElement || e is MethodElement && e.isStatic) { |
| _recordMessage( |
| expr, |
| e is MethodElement |
| ? StrongModeCode.INVALID_CAST_METHOD |
| : StrongModeCode.INVALID_CAST_FUNCTION, |
| [e.name, from, to]); |
| return; |
| } |
| |
| // Composite cast: these are more likely to fail. |
| bool downCastComposite = false; |
| if (!rules.isGroundType(to)) { |
| // This cast is (probably) due to our different treatment of dynamic. |
| // It may be more likely to fail at runtime. |
| if (from is InterfaceType) { |
| // For class types, we'd like to allow non-generic down casts, e.g., |
| // Iterable<T> to List<T>. The intuition here is that raw (generic) |
| // casts are problematic, and we should complain about those. |
| var typeArgs = from.typeArguments; |
| downCastComposite = |
| typeArgs.isEmpty || typeArgs.any((t) => t.isDynamic); |
| } else { |
| downCastComposite = !from.isDynamic; |
| } |
| } |
| |
| var parent = expr.parent; |
| ErrorCode errorCode; |
| if (downCastComposite) { |
| errorCode = StrongModeCode.DOWN_CAST_COMPOSITE; |
| } else if (from.isDynamic) { |
| errorCode = StrongModeCode.DYNAMIC_CAST; |
| } else if (parent is VariableDeclaration && parent.initializer == expr) { |
| errorCode = StrongModeCode.ASSIGNMENT_CAST; |
| } else { |
| errorCode = opAssign |
| ? StrongModeCode.DOWN_CAST_IMPLICIT_ASSIGN |
| : StrongModeCode.DOWN_CAST_IMPLICIT; |
| } |
| _recordMessage(expr, errorCode, [from, to]); |
| if (opAssign) { |
| setImplicitOperationCast(expr, to); |
| } else { |
| setImplicitCast(expr, to); |
| } |
| _hasImplicitCasts = true; |
| } |
| |
| void _recordMessage(AstNode node, ErrorCode errorCode, List arguments) { |
| // Compute the right severity taking the analysis options into account. |
| // We construct a dummy error to make the common case where we end up |
| // ignoring the strong mode message cheaper. |
| var processor = ErrorProcessor.getProcessor(_options, |
| new AnalysisError.forValues(null, -1, 0, errorCode, null, null)); |
| var severity = |
| (processor != null) ? processor.severity : errorCode.errorSeverity; |
| |
| if (severity == ErrorSeverity.ERROR) { |
| _failure = true; |
| } |
| if (errorCode.type == ErrorType.HINT && |
| errorCode.name.startsWith('STRONG_MODE_TOP_LEVEL_')) { |
| severity = ErrorSeverity.ERROR; |
| } |
| if (severity != ErrorSeverity.INFO || _options.strongModeHints) { |
| int begin = node is AnnotatedNode |
| ? node.firstTokenAfterCommentAndMetadata.offset |
| : node.offset; |
| int length = node.end - begin; |
| var source = resolutionMap |
| .elementDeclaredByCompilationUnit(node.root as CompilationUnit) |
| .source; |
| var error = |
| new AnalysisError(source, begin, length, errorCode, arguments); |
| reporter.onError(error); |
| } |
| } |
| |
| void _validateTopLevelInitializer(String name, Expression n) { |
| void validateHasType(PropertyAccessorElement e) { |
| if (e.hasImplicitReturnType) { |
| var variable = e.variable as VariableElementImpl; |
| TopLevelInferenceError error = variable.typeInferenceError; |
| if (error != null) { |
| if (error.kind == TopLevelInferenceErrorKind.dependencyCycle) { |
| _recordMessage( |
| n, StrongModeCode.TOP_LEVEL_CYCLE, [name, error.arguments]); |
| } else { |
| _recordMessage( |
| n, StrongModeCode.TOP_LEVEL_IDENTIFIER_NO_TYPE, [name, e.name]); |
| } |
| } |
| } |
| } |
| |
| void validateIdentifierElement(AstNode n, Element e) { |
| if (e == null) { |
| return; |
| } |
| |
| Element enclosing = e.enclosingElement; |
| if (enclosing is CompilationUnitElement) { |
| if (e is PropertyAccessorElement) { |
| validateHasType(e); |
| } |
| } else if (enclosing is ClassElement) { |
| if (e is PropertyAccessorElement) { |
| if (e.isStatic) { |
| validateHasType(e); |
| } else { |
| _recordMessage( |
| n, StrongModeCode.TOP_LEVEL_INSTANCE_GETTER, [name, e.name]); |
| } |
| } |
| } |
| } |
| |
| if (n == null || |
| n is NullLiteral || |
| n is BooleanLiteral || |
| n is DoubleLiteral || |
| n is IntegerLiteral || |
| n is StringLiteral || |
| n is SymbolLiteral || |
| n is IndexExpression) { |
| // Nothing to validate. |
| } else if (n is AwaitExpression) { |
| _validateTopLevelInitializer(name, n.expression); |
| } else if (n is ThrowExpression) { |
| // Nothing to validate. |
| } else if (n is ParenthesizedExpression) { |
| _validateTopLevelInitializer(name, n.expression); |
| } else if (n is ConditionalExpression) { |
| _validateTopLevelInitializer(name, n.thenExpression); |
| _validateTopLevelInitializer(name, n.elseExpression); |
| } else if (n is BinaryExpression) { |
| TokenType operator = n.operator.type; |
| if (operator == TokenType.AMPERSAND_AMPERSAND || |
| operator == TokenType.BAR_BAR || |
| operator == TokenType.EQ_EQ || |
| operator == TokenType.BANG_EQ) { |
| // These operators give 'bool', no need to validate operands. |
| } else if (operator == TokenType.QUESTION_QUESTION) { |
| _recordMessage(n, StrongModeCode.TOP_LEVEL_UNSUPPORTED, |
| [name, n.runtimeType.toString()]); |
| } else { |
| _validateTopLevelInitializer(name, n.leftOperand); |
| } |
| } else if (n is PrefixExpression) { |
| TokenType operator = n.operator.type; |
| if (operator == TokenType.BANG) { |
| // This operator gives 'bool', no need to validate operands. |
| } else { |
| _validateTopLevelInitializer(name, n.operand); |
| } |
| } else if (n is PostfixExpression) { |
| _validateTopLevelInitializer(name, n.operand); |
| } else if (n is ListLiteral) { |
| if (n.typeArguments == null) { |
| for (Expression element in n.elements) { |
| _validateTopLevelInitializer(name, element); |
| } |
| } |
| } else if (n is MapLiteral) { |
| if (n.typeArguments == null) { |
| for (MapLiteralEntry entry in n.entries) { |
| _validateTopLevelInitializer(name, entry.key); |
| _validateTopLevelInitializer(name, entry.value); |
| } |
| } |
| } else if (n is FunctionExpression) { |
| for (FormalParameter p in n.parameters.parameters) { |
| if (p is DefaultFormalParameter) { |
| p = (p as DefaultFormalParameter).parameter; |
| } |
| if (p is SimpleFormalParameter) { |
| if (p.type == null) { |
| _recordMessage( |
| p, |
| StrongModeCode.TOP_LEVEL_FUNCTION_LITERAL_PARAMETER, |
| [name, p.element?.name]); |
| } |
| } |
| } |
| |
| FunctionBody body = n.body; |
| if (body is ExpressionFunctionBody) { |
| _validateTopLevelInitializer(name, body.expression); |
| } else { |
| _recordMessage(n, StrongModeCode.TOP_LEVEL_FUNCTION_LITERAL_BLOCK, []); |
| } |
| } else if (n is InstanceCreationExpression) { |
| ConstructorElement constructor = n.staticElement; |
| ClassElement clazz = constructor?.enclosingElement; |
| if (clazz != null && clazz.typeParameters.isNotEmpty) { |
| TypeName type = n.constructorName.type; |
| if (type.typeArguments == null) { |
| _recordMessage(type, StrongModeCode.TOP_LEVEL_TYPE_ARGUMENTS, |
| [name, clazz.name]); |
| } |
| } |
| } else if (n is AsExpression) { |
| // Nothing to validate. |
| } else if (n is IsExpression) { |
| // Nothing to validate. |
| } else if (n is Identifier) { |
| validateIdentifierElement(n, n.staticElement); |
| } else if (n is PropertyAccess) { |
| Element element = n.propertyName.staticElement; |
| validateIdentifierElement(n.propertyName, element); |
| } else if (n is FunctionExpressionInvocation) { |
| _validateTopLevelInitializer(name, n.function); |
| // TODO(scheglov) type arguments |
| } else if (n is MethodInvocation) { |
| _validateTopLevelInitializer(name, n.target); |
| SimpleIdentifier methodName = n.methodName; |
| Element element = methodName.staticElement; |
| if (element is ExecutableElement && element.typeParameters.isNotEmpty) { |
| if (n.typeArguments == null) { |
| _recordMessage(methodName, StrongModeCode.TOP_LEVEL_TYPE_ARGUMENTS, |
| [name, methodName.name]); |
| } |
| } |
| } else if (n is CascadeExpression) { |
| _validateTopLevelInitializer(name, n.target); |
| } else { |
| _recordMessage(n, StrongModeCode.TOP_LEVEL_UNSUPPORTED, |
| [name, n.runtimeType.toString()]); |
| } |
| } |
| } |
| |
| /// Checks for overriding declarations of fields and methods. This is used to |
| /// check overrides between classes and superclasses, interfaces, and mixin |
| /// applications. |
| class _OverrideChecker { |
| final StrongTypeSystemImpl rules; |
| final CodeChecker _checker; |
| |
| _OverrideChecker(CodeChecker checker) |
| : _checker = checker, |
| rules = checker.rules; |
| |
| void check(Declaration node) { |
| var element = |
| resolutionMap.elementDeclaredByDeclaration(node) as ClassElement; |
| if (element.type.isObject) { |
| return; |
| } |
| _checkSuperOverrides(node, element); |
| _checkMixinApplicationOverrides(node, element); |
| _checkAllInterfaceOverrides(node, element); |
| _checkForCovariantGenerics(node, element); |
| } |
| |
| /// Finds implicit casts that we need on parameters and type formals to |
| /// ensure soundness of covariant generics, and records them on the [node]. |
| /// |
| /// The parameter checks can be retrived using [getClassCovariantParameters] |
| /// and [getSuperclassCovariantParameters]. |
| /// |
| /// For each member of this class and non-overridden inherited member, we |
| /// check to see if any generic super interface permits an unsound call to the |
| /// concrete member. For example: |
| /// |
| /// class C<T> { |
| /// add(T t) {} // C<Object>.add is unsafe, need a check on `t` |
| /// } |
| /// class D extends C<int> { |
| /// add(int t) {} // C<Object>.add is unsafe, need a check on `t` |
| /// } |
| /// class E extends C<int> { |
| /// add(Object t) {} // no check needed, C<Object>.add is safe |
| /// } |
| /// |
| void _checkForCovariantGenerics(Declaration node, ClassElement element) { |
| // Find all generic interfaces that could be used to call into members of |
| // this class. This will help us identify which parameters need checks |
| // for soundness. |
| var allCovariant = _findAllGenericInterfaces(element.type); |
| if (allCovariant.isEmpty) return; |
| |
| var seenConcreteMembers = new HashSet<String>(); |
| var members = _getConcreteMembers(element.type, seenConcreteMembers); |
| |
| // For members on this class, check them against all generic interfaces. |
| var checks = _findCovariantChecks(members, allCovariant); |
| // Store those checks on the class declaration. |
| setClassCovariantParameters(node, checks); |
| |
| // For members of the superclass, we may need to add checks because this |
| // class adds a new unsafe interface. Collect those checks. |
| checks = _findSuperclassCovariantChecks( |
| element, allCovariant, seenConcreteMembers); |
| // Store the checks on the class declaration, it will need to ensure the |
| // inherited members are appropriately guarded to ensure soundness. |
| setSuperclassCovariantParameters(node, checks); |
| } |
| |
| /// For each member of this class and non-overridden inherited member, we |
| /// check to see if any generic super interface permits an unsound call to the |
| /// concrete member. For example: |
| /// |
| /// We must check non-overridden inherited members because this class could |
| /// contain a new interface that permits unsound access to that member. In |
| /// those cases, the class is expected to insert stub that checks the type |
| /// before calling `super`. For example: |
| /// |
| /// class C<T> { |
| /// add(T t) {} |
| /// } |
| /// class D { |
| /// add(int t) {} |
| /// } |
| /// class E extends D implements C<int> { |
| /// // C<Object>.add is unsafe, and D.m is marked for a check. |
| /// // |
| /// // one way to implement this is to generate a stub method: |
| /// // add(t) => super.add(t as int); |
| /// } |
| /// |
| Set<Element> _findSuperclassCovariantChecks(ClassElement element, |
| Set<ClassElement> allCovariant, HashSet<String> seenConcreteMembers) { |
| var visited = new HashSet<ClassElement>()..add(element); |
| var superChecks = new Set<Element>(); |
| var existingChecks = new HashSet<Element>(); |
| |
| void visitImmediateSuper(InterfaceType type) { |
| // For members of mixins/supertypes, check them against new interfaces, |
| // and also record any existing checks they already had. |
| var oldCovariant = _findAllGenericInterfaces(type); |
| var newCovariant = allCovariant.difference(oldCovariant); |
| if (newCovariant.isEmpty) return; |
| |
| void visitSuper(InterfaceType type) { |
| var element = type.element; |
| if (visited.add(element)) { |
| var members = _getConcreteMembers(type, seenConcreteMembers); |
| _findCovariantChecks(members, newCovariant, superChecks); |
| _findCovariantChecks(members, oldCovariant, existingChecks); |
| element.mixins.reversed.forEach(visitSuper); |
| var s = element.supertype; |
| if (s != null) visitSuper(s); |
| } |
| } |
| |
| visitSuper(type); |
| } |
| |
| element.mixins.reversed.forEach(visitImmediateSuper); |
| var s = element.supertype; |
| if (s != null) visitImmediateSuper(s); |
| |
| superChecks.removeAll(existingChecks); |
| return superChecks; |
| } |
| |
| /// Gets all concrete instance members declared on this type, skipping already |
| /// [seenConcreteMembers] and adding any found ones to it. |
| /// |
| /// By tracking the set of seen members, we can visit superclasses and mixins |
| /// and ultimately collect every most-derived member exposed by a given type. |
| static List<ExecutableElement> _getConcreteMembers( |
| InterfaceType type, HashSet<String> seenConcreteMembers) { |
| var members = <ExecutableElement>[]; |
| for (var declaredMembers in [type.accessors, type.methods]) { |
| for (var member in declaredMembers) { |
| // We only visit each most derived concrete member. |
| // To avoid visiting an overridden superclass member, we skip members |
| // we've seen, and visit starting from the class, then mixins in |
| // reverse order, then superclasses. |
| if (!member.isStatic && |
| !member.isAbstract && |
| seenConcreteMembers.add(member.name)) { |
| members.add(member); |
| } |
| } |
| } |
| return members; |
| } |
| |
| /// Find all covariance checks on parameters/type parameters needed for |
| /// soundness given a set of concrete [members] and a set of unsafe generic |
| /// [covariantInterfaces] that may allow those members to be called in an |
| /// unsound way. |
| /// |
| /// See [_findCovariantChecksForMember] for more information and an exmaple. |
| Set<Element> _findCovariantChecks(Iterable<ExecutableElement> members, |
| Iterable<ClassElement> covariantInterfaces, |
| [Set<Element> covariantChecks]) { |
| covariantChecks ??= new Set(); |
| if (members.isEmpty) return covariantChecks; |
| |
| for (var iface in covariantInterfaces) { |
| var unsafeSupertype = _getCovariantUpperBound(rules, iface); |
| for (var m in members) { |
| _findCovariantChecksForMember(m, unsafeSupertype, covariantChecks); |
| } |
| } |
| return covariantChecks; |
| } |
| |
| /// Given a [member] and a covariant [unsafeSupertype], determine if any |
| /// type formals or parameters of this member need a check because of the |
| /// unsoundness in the unsafe covariant supertype. |
| /// |
| /// For example: |
| /// |
| /// class C<T> { |
| /// m(T t) {} |
| /// g<S extends T>() => <S>[]; |
| /// } |
| /// class D extends C<num> { |
| /// m(num n) {} |
| /// g<R extends num>() => <R>[]; |
| /// } |
| /// main() { |
| /// C<Object> c = new C<int>(); |
| /// c.m('hi'); // must throw for soundness |
| /// c.g<String>(); // must throw for soundness |
| /// |
| /// c = new D(); |
| /// c.m('hi'); // must throw for soundness |
| /// c.g<String>(); // must throw for soundness |
| /// } |
| /// |
| /// We've already found `C<Object>` is a potentially unsafe covariant generic |
| /// supertpe, and we call this method to see if any members need a check |
| /// because of `C<Object>`. |
| /// |
| /// In this example, we will call this method with: |
| /// - `C<T>.m` and `C<Object>`, finding that `t` needs a check. |
| /// - `C<T>.g` and `C<Object>`, finding that `S` needs a check. |
| /// - `D.m` and `C<Object>`, finding that `n` needs a check. |
| /// - `D.g` and `C<Object>`, finding that `R` needs a check. |
| /// |
| /// Given `C<T>.m` and `C<Object>`, we search for covariance checks like this |
| /// (`*` short for `dynamic`): |
| /// - get the type of `C<Object>.m`: `(Object) -> *` |
| /// - get the type of `C<T>.m`: `(T) -> *` |
| /// - perform a subtype check `(T) -> * <: (Object) -> *`, |
| /// and record any parameters/type formals that violate soundess. |
| /// - that checks `Object <: T`, which is false, thus we need a check on |
| /// parameter `t` of `C<T>.m` |
| /// |
| /// Another example is `D.g` and `C<Object>`: |
| /// - get the type of `C<Object>.m`: `<S extends Object>() -> *` |
| /// - get the type of `D.g`: `<R extends num>() -> *` |
| /// - perform a subtype check |
| /// `<S extends Object>() -> * <: <R extends num>() -> *`, |
| /// and record any parameters/type formals that violate soundess. |
| /// - that checks the type formal bound of `S` and `R` asserting |
| /// `Object <: num`, which is false, thus we need a check on type formal `R` |
| /// of `D.g`. |
| void _findCovariantChecksForMember(ExecutableElement member, |
| InterfaceType unsafeSupertype, Set<Element> covariantChecks) { |
| var f2 = _getMemberType(unsafeSupertype, member); |
| if (f2 == null) return; |
| var f1 = member.type; |
| |
| // Find parameter or type formal checks that we need to ensure `f2 <: f1`. |
| // |
| // The static type system allows this subtyping, but it is not sound without |
| // these runtime checks. |
| void addCheck(Element e) { |
| covariantChecks.add(e is Member ? e.baseElement : e); |
| } |
| |
| var fresh = FunctionTypeImpl.relateTypeFormals(f1, f2, (b2, b1, p2, p1) { |
| if (!rules.isSubtypeOf(b2, b1)) addCheck(p1); |
| return true; |
| }); |
| if (fresh != null) { |
| f1 = f1.instantiate(fresh); |
| f2 = f2.instantiate(fresh); |
| } |
| FunctionTypeImpl.relateParameters(f1.parameters, f2.parameters, (p1, p2) { |
| if (!rules.isOverrideSubtypeOfParameter(p1, p2)) addCheck(p1); |
| return true; |
| }); |
| } |
| |
| /// Find all generic interfaces that are implemented by [type], including |
| /// [type] itself if it is generic. |
| /// |
| /// This represents the complete set of unsafe covariant interfaces that could |
| /// be used to call members of [type]. |
| /// |
| /// Because we're going to instantiate these to their upper bound, we don't |
| /// have to track type parameters. |
| static Set<ClassElement> _findAllGenericInterfaces(InterfaceType type) { |
| var visited = new HashSet<ClassElement>(); |
| var genericSupertypes = new Set<ClassElement>(); |
| |
| void visitTypeAndSupertypes(InterfaceType type) { |
| var element = type.element; |
| if (visited.add(element)) { |
| if (element.typeParameters.isNotEmpty) { |
| genericSupertypes.add(element); |
| } |
| var supertype = element.supertype; |
| if (supertype != null) visitTypeAndSupertypes(supertype); |
| element.mixins.forEach(visitTypeAndSupertypes); |
| element.interfaces.forEach(visitTypeAndSupertypes); |
| } |
| } |
| |
| visitTypeAndSupertypes(type); |
| |
| return genericSupertypes; |
| } |
| |
| /// 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(Declaration node, ClassElement element) { |
| 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 = element.type; |
| type.interfaces.forEach((i) => find(i, localInterfaces)); |
| _checkInterfacesOverrides(type, localInterfaces, seen, |
| includeParents: true, classNode: node); |
| |
| // 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. |
| // We detect superclass circularities using the "tortoise and hare" |
| // algorithm. |
| var superInterfaces = new Set<InterfaceType>(); |
| var parent = type.superclass; |
| var hare = type.superclass?.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) { |
| if (identical(parent, hare)) break; |
| parent.interfaces.forEach((i) => find(i, superInterfaces)); |
| parent = parent.superclass; |
| hare = hare?.superclass?.superclass; |
| } |
| _checkInterfacesOverrides(type, superInterfaces, seen, |
| includeParents: false, classNode: node); |
| } |
| |
| /// Check that individual methods and fields in [node] correctly override |
| /// the declarations in [baseType]. |
| /// |
| /// The [errorLocation] node indicates where errors are reported, see |
| /// [_checkSingleOverride] for more details. |
| _checkIndividualOverridesFromClass(Declaration node, InterfaceType baseType, |
| Set<String> seen, bool isSubclass) { |
| for (var member in _classMembers(node)) { |
| 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.name, member, isSubclass); |
| if (!variable.isFinal && |
| !variable.isConst && |
| _checkSingleOverride( |
| setter, baseType, variable.name, member, isSubclass)) { |
| found = true; |
| } |
| if (found) { |
| seen.add(name); |
| } |
| } |
| } else if (member is MethodDeclaration) { |
| if (member.isStatic) { |
| continue; |
| } |
| var method = resolutionMap.elementDeclaredByMethodDeclaration(member); |
| if (seen.contains(method.name)) { |
| continue; |
| } |
| if (_checkSingleOverride( |
| method, baseType, member.name, member, isSubclass)) { |
| seen.add(method.name); |
| } |
| } else { |
| assert(member is ConstructorDeclaration); |
| } |
| } |
| } |
| |
| /// 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. |
| void _checkIndividualOverridesFromType( |
| InterfaceType subType, |
| InterfaceType baseType, |
| AstNode errorLocation, |
| Set<String> seen, |
| bool isSubclass) { |
| void checkHelper(ExecutableElement e) { |
| if (e.isStatic) return; |
| if (seen.contains(e.name)) return; |
| if (_checkSingleOverride(e, baseType, null, errorLocation, isSubclass)) { |
| seen.add(e.name); |
| } |
| } |
| |
| subType.methods.forEach(checkHelper); |
| subType.accessors.forEach(checkHelper); |
| } |
| |
| /// 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( |
| InterfaceType type, Iterable<InterfaceType> interfaces, Set<String> seen, |
| {Set<InterfaceType> visited, |
| bool includeParents: true, |
| AstNode errorLocation, |
| Declaration classNode}) { |
| 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 (classNode != null) { |
| _checkIndividualOverridesFromClass( |
| classNode, interfaceType, seen, false); |
| } else { |
| _checkIndividualOverridesFromType( |
| type, interfaceType, errorLocation, seen, false); |
| } |
| } |
| |
| // Check overrides from its mixins |
| for (int i = 0; i < type.mixins.length; i++) { |
| var loc = errorLocation ?? _withClause(classNode).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), false); |
| } |
| } |
| |
| // Check overrides from its superclasses |
| if (includeParents) { |
| var parent = type.superclass; |
| if (parent.isObject) { |
| return; |
| } |
| var loc = errorLocation ?? _extendsErrorLocation(classNode); |
| // 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 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(Declaration node, ClassElement element) { |
| var type = 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 = _withClause(node).mixinTypes[i]; |
| for (int j = i - 1; j >= 0; j--) { |
| _checkIndividualOverridesFromType( |
| current, mixins[j], errorLocation, seen, true); |
| } |
| _checkIndividualOverridesFromType( |
| current, parent, errorLocation, seen, true); |
| } |
| } |
| |
| /// 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, bool isSubclass) { |
| assert(!element.isStatic); |
| |
| FunctionType subType = _elementType(element); |
| FunctionType baseType = _getMemberType(type, element); |
| if (baseType == null) return false; |
| |
| if (isSubclass && element is PropertyAccessorElement) { |
| // Disallow any overriding if the base class defines this member |
| // as a field. We effectively treat fields as final / non-virtual, |
| // unless they are explicitly marked as @virtual |
| var field = _getMemberField(type, element); |
| if (field != null && !field.isVirtual) { |
| _checker._recordMessage( |
| errorLocation, StrongModeCode.INVALID_FIELD_OVERRIDE, [ |
| element.enclosingElement.name, |
| element.name, |
| subType, |
| type, |
| baseType |
| ]); |
| } |
| } |
| |
| if (!rules.isOverrideSubtypeOf(subType, baseType)) { |
| ErrorCode errorCode; |
| var parent = errorLocation?.parent; |
| if (errorLocation is ExtendsClause || |
| parent is ClassTypeAlias && parent.superclass == errorLocation) { |
| errorCode = StrongModeCode.INVALID_METHOD_OVERRIDE_FROM_BASE; |
| } else if (parent is WithClause) { |
| errorCode = StrongModeCode.INVALID_METHOD_OVERRIDE_FROM_MIXIN; |
| } else { |
| errorCode = StrongModeCode.INVALID_METHOD_OVERRIDE; |
| } |
| |
| _checker._recordMessage(errorLocation, errorCode, [ |
| element.enclosingElement.name, |
| element.name, |
| subType, |
| type, |
| baseType |
| ]); |
| } |
| |
| // If we have any covariant parameters and we're comparing against a |
| // superclass, we check all superclasses instead of stopping the search. |
| bool hasCovariant = element.parameters.any((p) => p.isCovariant); |
| bool keepSearching = hasCovariant && isSubclass; |
| return !keepSearching; |
| } |
| |
| /// 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(Declaration node, ClassElement element) { |
| var seen = new Set<String>(); |
| var current = element.type; |
| var visited = new Set<InterfaceType>(); |
| do { |
| visited.add(current); |
| current.mixins.reversed.forEach( |
| (m) => _checkIndividualOverridesFromClass(node, m, seen, true)); |
| _checkIndividualOverridesFromClass(node, current.superclass, seen, true); |
| current = current.superclass; |
| } while (!current.isObject && !visited.contains(current)); |
| } |
| |
| /// If node is a [ClassDeclaration] returns its members, otherwise if node is |
| /// a [ClassTypeAlias] this returns an empty list. |
| Iterable<ClassMember> _classMembers(Declaration node) { |
| return node is ClassDeclaration ? node.members : []; |
| } |
| |
| /// If node is a [ClassDeclaration] returns its members, otherwise if node is |
| /// a [ClassTypeAlias] this returns an empty list. |
| AstNode _extendsErrorLocation(Declaration node) { |
| return node is ClassDeclaration |
| ? node.extendsClause |
| : (node as ClassTypeAlias).superclass; |
| } |
| |
| /// If node is a [ClassDeclaration] returns its members, otherwise if node is |
| /// a [ClassTypeAlias] this returns an empty list. |
| WithClause _withClause(Declaration node) { |
| return node is ClassDeclaration |
| ? node.withClause |
| : (node as ClassTypeAlias).withClause; |
| } |
| } |