Index: pkg/analyzer/lib/src/task/strong/checker.dart |
diff --git a/pkg/analyzer/lib/src/task/strong/checker.dart b/pkg/analyzer/lib/src/task/strong/checker.dart |
index 9e5eee90c4cb3e4420fd9d920023c7b270c85c74..84e5eeed7b5a2527942c2b723057fa79ea0ff94a 100644 |
--- a/pkg/analyzer/lib/src/task/strong/checker.dart |
+++ b/pkg/analyzer/lib/src/task/strong/checker.dart |
@@ -6,6 +6,7 @@ |
// 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'; |
@@ -16,6 +17,7 @@ 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; |
@@ -51,6 +53,35 @@ bool hasStrictArrow(Expression expression) { |
return element is FunctionElement || element is MethodElement; |
} |
+InterfaceType _getCovariantUpperBound(TypeSystem rules, ClassElement iface) { |
+ var upperBound = rules.instantiateToBounds(iface.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 = iface.type.instantiate(newTypeArgs); |
+ } |
+ return upperBound; |
+} |
+ |
DartType _elementType(Element e) { |
if (e == null) { |
// Malformed code - just return dynamic. |
@@ -103,47 +134,37 @@ FieldElement _getMemberField( |
/// Looks up the declaration that matches [member] in [type] and returns it's |
/// declared type. |
-FunctionType _getMemberType(InterfaceType type, ExecutableElement member) => |
- _memberTypeGetter(member)(type); |
- |
-_MemberTypeGetter _memberTypeGetter(ExecutableElement member) { |
+FunctionType _getMemberType(InterfaceType type, ExecutableElement member) { |
String memberName = member.name; |
final isGetter = member is PropertyAccessorElement && member.isGetter; |
final isSetter = member is PropertyAccessorElement && member.isSetter; |
- FunctionType f(InterfaceType type) { |
- ExecutableElement baseMethod; |
- |
- if (member.isPrivate) { |
- var subtypeLibrary = member.library; |
- var baseLibrary = type.element.library; |
- if (baseLibrary != subtypeLibrary) { |
- return null; |
- } |
+ ExecutableElement baseMethod; |
+ if (member.isPrivate) { |
+ var subtypeLibrary = member.library; |
+ var baseLibrary = type.element.library; |
+ if (baseLibrary != subtypeLibrary) { |
+ return null; |
} |
+ } |
- try { |
- if (isGetter) { |
- assert(!isSetter); |
- // Look for getter or field. |
- baseMethod = type.getGetter(memberName); |
- } else if (isSetter) { |
- baseMethod = type.getSetter(memberName); |
- } else { |
- baseMethod = type.getMethod(memberName); |
- } |
- } catch (e) { |
- // TODO(sigmund): remove this try-catch block (see issue #48). |
+ try { |
+ if (isGetter) { |
+ assert(!isSetter); |
+ // Look for getter or field. |
+ baseMethod = type.getGetter(memberName); |
+ } else if (isSetter) { |
+ baseMethod = type.getSetter(memberName); |
+ } else { |
+ baseMethod = type.getMethod(memberName); |
} |
- if (baseMethod == null || baseMethod.isStatic) return null; |
- return baseMethod.type; |
+ } catch (e) { |
+ // TODO(sigmund): remove this try-catch block (see issue #48). |
Leaf
2017/06/28 18:12:29
Looks like this can be removed now?
https://githu
Jennifer Messerly
2017/07/05 20:11:21
Done.
|
} |
- |
- return f; |
+ if (baseMethod == null || baseMethod.isStatic) return null; |
+ return baseMethod.type; |
} |
-typedef FunctionType _MemberTypeGetter(InterfaceType type); |
- |
/// Checks the body of functions and properties. |
class CodeChecker extends RecursiveAstVisitor { |
final StrongTypeSystemImpl rules; |
@@ -154,6 +175,8 @@ class CodeChecker extends RecursiveAstVisitor { |
bool _failure = false; |
bool _hasImplicitCasts; |
+ ClassElement _currentClass; |
+ HashSet<ExecutableElement> _covariantPrivateMembers; |
CodeChecker(TypeProvider typeProvider, StrongTypeSystemImpl rules, |
AnalysisErrorListener reporter, this._options) |
@@ -201,7 +224,7 @@ class CodeChecker extends RecursiveAstVisitor { |
void checkBoolean(Expression expr) => |
checkAssignment(expr, typeProvider.boolType); |
- void checkFunctionApplication(InvocationExpression node) { |
+ void _checkFunctionApplication(InvocationExpression node) { |
var ft = _getTypeAsCaller(node); |
if (_isDynamicCall(node, ft)) { |
@@ -209,6 +232,7 @@ class CodeChecker extends RecursiveAstVisitor { |
// gotten an analyzer error, so no need to issue another error. |
_recordDynamicInvoke(node, node.function); |
} else { |
+ _checkFunctionApplicationForCovariance(node); |
checkArgumentList(node.argumentList, ft); |
} |
} |
@@ -293,7 +317,10 @@ class CodeChecker extends RecursiveAstVisitor { |
@override |
void visitClassDeclaration(ClassDeclaration node) { |
_overrideChecker.check(node); |
+ var savedClass = _currentClass; |
vsm
2017/06/26 22:14:20
Can these ever be nested? If not, maybe
assert(_
Jennifer Messerly
2017/07/05 20:11:20
Done.
|
+ _currentClass = node.element; |
super.visitClassDeclaration(node); |
+ _currentClass = savedClass; |
} |
@override |
@@ -311,8 +338,10 @@ class CodeChecker extends RecursiveAstVisitor { |
@override |
void visitCompilationUnit(CompilationUnit node) { |
_hasImplicitCasts = false; |
+ _covariantPrivateMembers = new HashSet(); |
node.visitChildren(this); |
setHasImplicitCasts(node, _hasImplicitCasts); |
+ setCovariantPrivateMembers(node, _covariantPrivateMembers); |
} |
@override |
@@ -438,7 +467,7 @@ class CodeChecker extends RecursiveAstVisitor { |
@override |
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
- checkFunctionApplication(node); |
+ _checkFunctionApplication(node); |
node.visitChildren(this); |
} |
@@ -565,7 +594,8 @@ class CodeChecker extends RecursiveAstVisitor { |
// we call [checkFunctionApplication]. |
setIsDynamicInvoke(node.methodName, true); |
} else { |
- checkFunctionApplication(node); |
+ _checkFunctionApplication(node); |
+ _trackPrivateMemberCovariance(target, node.methodName.staticElement); |
} |
node.visitChildren(this); |
} |
@@ -616,6 +646,11 @@ class CodeChecker extends RecursiveAstVisitor { |
} |
@override |
+ void visitSimpleIdentifier(SimpleIdentifier node) { |
+ _trackPrivateMemberCovariance(null, node.staticElement); |
+ } |
+ |
+ @override |
void visitSuperConstructorInvocation(SuperConstructorInvocation node) { |
var element = node.staticElement; |
if (element != null) { |
@@ -664,18 +699,24 @@ class CodeChecker extends RecursiveAstVisitor { |
@override |
void visitVariableDeclarationList(VariableDeclarationList node) { |
TypeAnnotation 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 { |
- for (VariableDeclaration variable in node.variables) { |
- var initializer = variable.initializer; |
- if (initializer != null) { |
+ |
+ for (VariableDeclaration variable in node.variables) { |
+ var initializer = variable.initializer; |
+ if (initializer != null) { |
+ if (type != null) { |
checkAssignment(initializer, type.type); |
+ } else { |
+ // Most checks are not needed when the type is var, however, we still |
+ // need to check for unsafe function types from the initialize due |
+ // to covariance. |
+ var toType = _getDefiniteType(initializer); |
+ if (toType is FunctionType) { |
+ _checkImplicitCastForCovariance(initializer, toType); |
+ } |
} |
} |
} |
+ |
node.visitChildren(this); |
} |
@@ -729,9 +770,11 @@ class CodeChecker extends RecursiveAstVisitor { |
} |
} |
- void _checkFieldAccess(AstNode node, AstNode target, SimpleIdentifier field) { |
- if (field.staticElement == null && |
- !typeProvider.isObjectMember(field.name)) { |
+ void _checkFieldAccess( |
+ AstNode node, Expression target, SimpleIdentifier field) { |
+ var element = field.staticElement; |
+ _trackPrivateMemberCovariance(target, element); |
+ if (element == null && !typeProvider.isObjectMember(field.name)) { |
_recordDynamicInvoke(node, target); |
} |
node.visitChildren(this); |
@@ -750,6 +793,8 @@ class CodeChecker extends RecursiveAstVisitor { |
if (_needsImplicitCast(expr, to, from: from) == true) { |
_recordImplicitCast(expr, to, from: from, opAssign: opAssign); |
+ } else if (to is FunctionType) { |
+ _checkImplicitCastForCovariance(expr, to); |
} |
} |
@@ -826,6 +871,204 @@ class CodeChecker extends RecursiveAstVisitor { |
DartType _getDefiniteType(Expression expr) => |
getDefiniteType(expr, rules, typeProvider); |
+ /// This gets the return type that is safe to rely on, given that we |
Leaf
2017/06/28 18:12:29
What does "This gets the return type" mean here?
Jennifer Messerly
2017/07/05 20:11:20
I've improved this comment, let me know if it help
|
+ /// might be making a call on an (unsafe) covariant interface type. |
+ /// |
+ /// For example: |
+ /// |
+ /// typedef F<T>(T t); |
+ /// class C<T> { |
+ /// F<T> f; |
+ /// C(this.f); |
+ /// } |
+ /// main() { |
+ /// C<Object> c = new C<int>((int x) => x + 42)); |
+ /// F<Object> f = c.f; // need an implicit cast here. |
+ /// f('hello'); |
+ /// } |
+ /// |
+ void _checkImplicitCastForCovariance(Expression node, FunctionType to, |
Leaf
2017/06/28 18:12:29
Some high level comments here.
My original thin
Jennifer Messerly
2017/07/05 20:11:20
Did a big refactor to simplify this. We always add
|
+ {DartType target}) { |
+ while (node is NamedExpression) { |
+ node = (node as NamedExpression).expression; |
+ } |
+ while (node is ParenthesizedExpression) { |
+ node = (node as ParenthesizedExpression).expression; |
+ } |
+ |
+ Element member; |
+ bool isTearOff = false; |
+ bool isMethodInvokeTarget = false; |
+ if (node is PropertyAccess) { |
+ target = node.realTarget.staticType; |
+ member = node.propertyName.staticElement; |
+ isTearOff = member is MethodElement; |
+ if (target == null) return; |
+ } else if (node is PrefixedIdentifier) { |
+ target = node.prefix.staticType; |
+ member = node.staticElement; |
+ isTearOff = member is MethodElement; |
+ if (target == null) return; |
+ } else if (node is SimpleIdentifier) { |
+ member = node.staticElement; |
+ var parent = node.parent; |
+ isMethodInvokeTarget = |
+ parent is MethodInvocation && parent.methodName == node; |
+ isTearOff = member is MethodElement; |
+ } else if (node is MethodInvocation) { |
+ target = node.realTarget?.staticType; |
+ member = node.methodName.staticElement; |
+ } else if (node is FunctionExpressionInvocation) { |
+ member = node.staticElement; |
+ } |
+ |
+ // 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 |
Leaf
2017/06/28 18:12:30
This comment confused me, until I read the code be
Jennifer Messerly
2017/07/05 20:11:20
(this is gone in the new code)
|
+ // covariance checks for their parameters inside the method. |
+ if (member is ExecutableElement && _isInstanceMember(member)) { |
+ if (target == null) { |
+ if (!_inCurrentClass(member)) return; |
+ // Use implicit `this` as the target. |
+ target = _currentClass?.type; |
+ } |
+ |
+ if (target is InterfaceType && target.typeArguments.isNotEmpty) { |
Leaf
2017/06/28 18:12:29
Not sure that this is sufficient? Consider:
clas
Jennifer Messerly
2017/07/05 20:11:20
in this case, `c` has type `C<Object>` and so it w
|
+ // 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>`). |
Leaf
2017/06/28 18:12:29
Clever... :)
Jennifer Messerly
2017/07/05 20:11:20
Acknowledged.
|
+ // |
+ // That way we can tell if any lower `T` will work or not. |
+ var classType = target.element.type; |
+ var classLowerBound = classType.instantiate(new List.filled( |
+ classType.typeParameters.length, typeProvider.nullType)); |
+ var memberLowerBound = _lookUpMember(classLowerBound, member).type; |
+ |
+ // If this is a tear-off, we can ignore the parameters (they're already |
+ // checked) but we still need to check the return type. |
+ var expectedType = isTearOff ? to.returnType : to; |
+ if (!rules.isSubtypeOf(memberLowerBound.returnType, expectedType)) { |
+ // We can't put an `as` expression inside of a MethodInvocation node, |
+ // so we use the special cast annotation to indicate this. |
+ if (isMethodInvokeTarget) { |
+ setImplicitOperationCast(node, to); |
+ } else { |
+ setImplicitCast(node, to); |
+ } |
+ _hasImplicitCasts = true; |
+ } |
+ } |
+ } |
+ } |
+ |
+ /// Similar to [_checkImplicitCastForCovariance] but make sure the function |
+ /// type we're applying arguments to is valid. |
+ /// |
+ /// For example: |
+ /// |
+ /// typedef F<T>(T t); |
+ /// class C<T> { |
+ /// F<T> f; |
+ /// C(this.f); |
+ /// } |
+ /// main() { |
+ /// C<Object> c = new C<int>((int x) => x + 42)); |
+ /// Object obj = c.f; // no cast. |
+ /// c.f('hello'); // implicit cast |
+ /// } |
+ void _checkFunctionApplicationForCovariance(InvocationExpression node) { |
+ DartType target; |
+ Element member; |
+ if (node is MethodInvocation) { |
+ target = node.realTarget?.staticType; |
+ member = node.methodName.staticElement; |
+ } else if (node is FunctionExpressionInvocation) { |
+ member = node.staticElement; |
+ } |
+ // Caller side checks will take care of methods. |
+ if (member is MethodElement) return; |
+ |
+ var type = _getDefiniteType(node.function); |
+ if (type is FunctionType) { |
+ _checkImplicitCastForCovariance(node.function, type, target: target); |
+ } |
+ } |
+ |
+ /// We can eliminate covariance checks on private members if they are only |
+ /// accessed through something with a known generic type, such as `this`. |
+ /// |
+ /// For these expressions, we will know the generic parameters exactly: |
+ /// |
+ /// - this |
+ /// - super |
+ /// - non-factory instance creation |
+ /// |
+ /// 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>(); |
+ /// } |
+ /// |
+ void _trackPrivateMemberCovariance(Expression target, Element e) { |
+ if (e is ExecutableElement && e.isPrivate && _isInstanceMember(e)) { |
+ if (target == null) return; // implicit this or invalid code. |
+ if (target is ThisExpression || target is SuperExpression) return; |
+ if (target is InstanceCreationExpression && |
+ target.staticElement?.isFactory == false) { |
+ return; |
+ } |
+ if (e is PropertyAccessorElement && e.isGetter) return; |
+ |
+ var type = target.staticType; |
+ if (type is InterfaceType && type.typeArguments.isNotEmpty) { |
+ _covariantPrivateMembers |
+ .add(e is Member ? (e as Member).baseElement : e); |
+ } |
+ } |
+ } |
+ |
+ bool _isInstanceMember(ExecutableElement e) => |
+ !e.isStatic && |
+ (e is MethodElement || |
+ e is PropertyAccessorElement && e.variable is FieldElement); |
+ |
+ bool _inCurrentClass(ExecutableElement e) { |
+ if (_currentClass == null) return false; |
+ var match = _lookUpMember(_currentClass.type, e); |
+ return e.enclosingElement == match.enclosingElement; |
+ } |
+ |
+ 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}) { |
@@ -1038,7 +1281,7 @@ class CodeChecker extends RecursiveAstVisitor { |
} |
_recordMessage(expr, errorCode, [from, to]); |
if (opAssign) { |
- setImplicitAssignmentCast(expr, to); |
+ setImplicitOperationCast(expr, to); |
} else { |
setImplicitCast(expr, to); |
} |
@@ -1249,6 +1492,133 @@ class _OverrideChecker { |
_checkSuperOverrides(node, element); |
_checkMixinApplicationOverrides(node, element); |
_checkAllInterfaceOverrides(node, element); |
+ _checkForCovariantGenerics(node, element); |
+ } |
+ |
+ void _checkForCovariantGenerics(Declaration node, ClassElement element) { |
Leaf
2017/06/28 18:12:29
These functions really need some documentation at
Jennifer Messerly
2017/07/05 20:11:21
Done.
|
+ // Find all generic interfaces that are implemented, if any. |
+ // |
+ // Because we're going to instantiate these to their bounds, we don't have |
+ // to track type parameters. |
+ var allCovariant = _findCovariantSupertypes(element.type); |
+ if (allCovariant.isEmpty) return; |
+ |
+ // For each most-derived concrete member of the class, (which may be from a |
+ // superclasses or mixin), check it against all generic superinterfaces that |
+ // it was not already checked against. |
+ var seenConcreteMembers = new HashSet<String>(); |
+ var members = _getConcreteMembers(element.type, seenConcreteMembers); |
+ |
+ // For members on this class, check them against all interfaces. |
+ var checks = _findCovariantChecks(members, allCovariant); |
+ setClassCovariantParameters(node, checks); |
+ |
+ checks = _findSuperclassCovariantChecks( |
+ element, allCovariant, seenConcreteMembers); |
+ setSuperclassCovariantParameters(node, checks); |
+ } |
+ |
+ Set<Element> _findSuperclassCovariantChecks(ClassElement element, |
Leaf
2017/06/28 18:12:30
Ok, I need some in person guidance on this code, I
Jennifer Messerly
2017/07/05 20:11:20
I've added a lot of comments -- do they help?
|
+ 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 = _findCovariantSupertypes(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. |
+ 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; |
+ } |
+ |
+ 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 upperBound = _getCovariantUpperBound(rules, iface); |
+ |
+ for (var m in members) { |
+ var superMemberType = _getMemberType(upperBound, m); |
+ if (superMemberType == null) continue; |
+ |
+ void addCheck(Element e) { |
+ covariantChecks.add(e is Member ? e.baseElement : e); |
+ } |
+ |
+ rules.visitCovariantParameters( |
+ m.type, superMemberType, addCheck, addCheck); |
+ } |
+ } |
+ return covariantChecks; |
+ } |
+ |
+ static Set<ClassElement> _findCovariantSupertypes(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. |
@@ -1367,7 +1737,7 @@ class _OverrideChecker { |
/// 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( |
+ void _checkIndividualOverridesFromType( |
InterfaceType subType, |
InterfaceType baseType, |
AstNode errorLocation, |
@@ -1502,7 +1872,6 @@ class _OverrideChecker { |
assert(!element.isStatic); |
FunctionType subType = _elementType(element); |
- // TODO(vsm): Test for generic |
FunctionType baseType = _getMemberType(type, element); |
if (baseType == null) return false; |
@@ -1522,6 +1891,7 @@ class _OverrideChecker { |
]); |
} |
} |
+ |
if (!rules.isOverrideSubtypeOf(subType, baseType)) { |
ErrorCode errorCode; |
var parent = errorLocation?.parent; |