Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(396)

Unified Diff: pkg/analyzer/lib/src/task/strong/checker.dart

Issue 2954523002: fix #27259, implement covariance checking for strong mode and DDC (Closed)
Patch Set: merged and fix an analysis error Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;
« no previous file with comments | « pkg/analyzer/lib/src/task/strong/ast_properties.dart ('k') | pkg/analyzer/test/generated/strong_mode_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698