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..cb8b6f18e32bcf9265ded33a914a9b5b5405b436 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,40 @@ bool hasStrictArrow(Expression 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. |
@@ -103,47 +139,19 @@ 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) { |
- 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; |
- } |
- } |
- |
- try { |
- if (isGetter) { |
- assert(!isSetter); |
- // Look for getter or field. |
- baseMethod = type.getGetter(memberName); |
- } else if (isSetter) { |
- baseMethod = type.getSetter(memberName); |
- } else { |
- baseMethod = type.getMethod(memberName); |
- } |
- } catch (e) { |
- // TODO(sigmund): remove this try-catch block (see issue #48). |
- } |
- if (baseMethod == null || baseMethod.isStatic) return null; |
- return baseMethod.type; |
+FunctionType _getMemberType(InterfaceType type, ExecutableElement member) { |
+ if (member.isPrivate && type.element.library != member.library) { |
+ return null; |
} |
- return f; |
+ 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; |
} |
-typedef FunctionType _MemberTypeGetter(InterfaceType type); |
- |
/// Checks the body of functions and properties. |
class CodeChecker extends RecursiveAstVisitor { |
final StrongTypeSystemImpl rules; |
@@ -154,6 +162,7 @@ class CodeChecker extends RecursiveAstVisitor { |
bool _failure = false; |
bool _hasImplicitCasts; |
+ HashSet<ExecutableElement> _covariantPrivateMembers; |
CodeChecker(TypeProvider typeProvider, StrongTypeSystemImpl rules, |
AnalysisErrorListener reporter, this._options) |
@@ -201,7 +210,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)) { |
@@ -311,8 +320,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 +449,7 @@ class CodeChecker extends RecursiveAstVisitor { |
@override |
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) { |
- checkFunctionApplication(node); |
+ _checkFunctionApplication(node); |
node.visitChildren(this); |
} |
@@ -565,9 +576,13 @@ class CodeChecker extends RecursiveAstVisitor { |
// we call [checkFunctionApplication]. |
setIsDynamicInvoke(node.methodName, true); |
} else { |
- checkFunctionApplication(node); |
+ _checkImplicitCovarianceCast(node, target, element); |
+ _checkFunctionApplication(node); |
} |
- node.visitChildren(this); |
+ // 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 |
@@ -664,18 +679,16 @@ 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); |
} |
} |
} |
+ |
node.visitChildren(this); |
} |
@@ -729,9 +742,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; |
+ _checkImplicitCovarianceCast(node, target, element); |
+ if (element == null && !typeProvider.isObjectMember(field.name)) { |
_recordDynamicInvoke(node, target); |
} |
node.visitChildren(this); |
@@ -826,6 +841,134 @@ class CodeChecker extends RecursiveAstVisitor { |
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}) { |
@@ -1038,7 +1181,7 @@ class CodeChecker extends RecursiveAstVisitor { |
} |
_recordMessage(expr, errorCode, [from, to]); |
if (opAssign) { |
- setImplicitAssignmentCast(expr, to); |
+ setImplicitOperationCast(expr, to); |
} else { |
setImplicitCast(expr, to); |
} |
@@ -1249,6 +1392,264 @@ class _OverrideChecker { |
_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. |
@@ -1367,7 +1768,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 +1903,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 +1922,7 @@ class _OverrideChecker { |
]); |
} |
} |
+ |
if (!rules.isOverrideSubtypeOf(subType, baseType)) { |
ErrorCode errorCode; |
var parent = errorLocation?.parent; |