Chromium Code Reviews| 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..212fd09ca6f7bc35309b4ae01822ca1ee9da8cb8 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,131 @@ 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)) { |
| + if (member.isPrivate && |
| + (member is! PropertyAccessorElement || member.isSetter)) { |
| + _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 a known type arguments, such as `this` `super` or a non-factory `new`. |
|
vsm
2017/07/06 15:43:02
'has a known' -> 'has known'
Jennifer Messerly
2017/07/06 21:31:04
Done.
|
| + /// |
| + /// 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 +1178,7 @@ class CodeChecker extends RecursiveAstVisitor { |
| } |
| _recordMessage(expr, errorCode, [from, to]); |
| if (opAssign) { |
| - setImplicitAssignmentCast(expr, to); |
| + setImplicitOperationCast(expr, to); |
| } else { |
| setImplicitCast(expr, to); |
| } |
| @@ -1249,6 +1389,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 +1765,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 +1900,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 +1919,7 @@ class _OverrideChecker { |
| ]); |
| } |
| } |
| + |
| if (!rules.isOverrideSubtypeOf(subType, baseType)) { |
| ErrorCode errorCode; |
| var parent = errorLocation?.parent; |