blob: 56155b1f1d4f188fdbf6e82300f0e36371f6f325 [file] [log] [blame]
/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.internal.type;
import com.google.common.annotations.VisibleForTesting;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.ConstructorElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.MethodElement;
import com.google.dart.engine.element.PropertyAccessorElement;
import com.google.dart.engine.element.TypeParameterElement;
import com.google.dart.engine.internal.element.ClassElementImpl;
import com.google.dart.engine.internal.element.ElementPair;
import com.google.dart.engine.internal.element.member.ConstructorMember;
import com.google.dart.engine.internal.element.member.MethodMember;
import com.google.dart.engine.internal.element.member.PropertyAccessorMember;
import com.google.dart.engine.internal.resolver.InheritanceManager;
import com.google.dart.engine.type.FunctionType;
import com.google.dart.engine.type.InterfaceType;
import com.google.dart.engine.type.Type;
import com.google.dart.engine.type.TypeParameterType;
import com.google.dart.engine.type.UnionType;
import com.google.dart.engine.utilities.general.ObjectUtilities;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Instances of the class {@code InterfaceTypeImpl} defines the behavior common to objects
* representing the type introduced by either a class or an interface, or a reference to such a
* type.
*
* @coverage dart.engine.type
*/
public class InterfaceTypeImpl extends TypeImpl implements InterfaceType {
/**
* This method computes the longest inheritance path from some passed {@link Type} to Object.
*
* @param type the {@link Type} to compute the longest inheritance path of from the passed
* {@link Type} to Object
* @return the computed longest inheritance path to Object
* @see InterfaceType#getLeastUpperBound(Type)
*/
@VisibleForTesting
public static int computeLongestInheritancePathToObject(InterfaceType type) {
return computeLongestInheritancePathToObject(type, 0, new HashSet<ClassElement>());
}
/**
* Returns the set of all superinterfaces of the passed {@link Type}.
*
* @param type the {@link Type} to compute the set of superinterfaces of
* @return the {@link Set} of superinterfaces of the passed {@link Type}
* @see #getLeastUpperBound(Type)
*/
@VisibleForTesting
public static Set<InterfaceType> computeSuperinterfaceSet(InterfaceType type) {
return computeSuperinterfaceSet(type, new HashSet<InterfaceType>());
}
/**
* This method computes the longest inheritance path from some passed {@link Type} to Object. This
* method calls itself recursively, callers should use the public method
* {@link #computeLongestInheritancePathToObject(Type)}.
*
* @param type the {@link Type} to compute the longest inheritance path of from the passed
* {@link Type} to Object
* @param depth a field used recursively
* @param visitedClasses the classes that have already been visited
* @return the computed longest inheritance path to Object
* @see #computeLongestInheritancePathToObject(Type)
* @see #getLeastUpperBound(Type)
*/
private static int computeLongestInheritancePathToObject(InterfaceType type, int depth,
HashSet<ClassElement> visitedClasses) {
ClassElement classElement = type.getElement();
// Object case
if (classElement.getSupertype() == null || visitedClasses.contains(classElement)) {
return depth;
}
int longestPath = 1;
try {
visitedClasses.add(classElement);
InterfaceType[] superinterfaces = classElement.getInterfaces();
int pathLength;
if (superinterfaces.length > 0) {
// loop through each of the superinterfaces recursively calling this method and keeping track
// of the longest path to return
for (InterfaceType superinterface : superinterfaces) {
pathLength = computeLongestInheritancePathToObject(
superinterface,
depth + 1,
visitedClasses);
if (pathLength > longestPath) {
longestPath = pathLength;
}
}
}
// finally, perform this same check on the super type
// TODO(brianwilkerson) Does this also need to add in the number of mixin classes?
InterfaceType supertype = classElement.getSupertype();
pathLength = computeLongestInheritancePathToObject(supertype, depth + 1, visitedClasses);
if (pathLength > longestPath) {
longestPath = pathLength;
}
} finally {
visitedClasses.remove(classElement);
}
return longestPath;
}
/**
* Returns the set of all superinterfaces of the passed {@link Type}. This is a recursive method,
* callers should call the public {@link #computeSuperinterfaceSet(Type)}.
*
* @param type the {@link Type} to compute the set of superinterfaces of
* @param set a {@link HashSet} used recursively by this method
* @return the {@link Set} of superinterfaces of the passed {@link Type}
* @see #computeSuperinterfaceSet(Type)
* @see #getLeastUpperBound(Type)
*/
private static Set<InterfaceType> computeSuperinterfaceSet(InterfaceType type,
HashSet<InterfaceType> set) {
Element element = type.getElement();
if (element != null) {
InterfaceType[] superinterfaces = type.getInterfaces();
for (InterfaceType superinterface : superinterfaces) {
if (set.add(superinterface)) {
computeSuperinterfaceSet(superinterface, set);
}
}
InterfaceType supertype = type.getSuperclass();
if (supertype != null) {
if (set.add(supertype)) {
computeSuperinterfaceSet(supertype, set);
}
}
}
return set;
}
/**
* Return the intersection of the given sets of types, where intersection is based on the equality
* of the types themselves.
*
* @param first the first set of types to be intersected
* @param second the second set of types to be intersected
* @return the intersection of the given sets of types
*/
private static InterfaceType[] intersection(Set<InterfaceType> first, Set<InterfaceType> second) {
Set<InterfaceType> result = new HashSet<InterfaceType>(first);
result.retainAll(second);
return result.toArray(new InterfaceType[result.size()]);
}
/**
* Return the "least upper bound" of the given types under the assumption that the types have the
* same element and differ only in terms of the type arguments. The resulting type is composed by
* comparing the corresponding type arguments, keeping those that are the same, and using
* 'dynamic' for those that are different.
*
* @param firstType the first type
* @param secondType the second type
* @return the "least upper bound" of the given types
*/
private static InterfaceType leastUpperBound(InterfaceType firstType, InterfaceType secondType) {
if (firstType.equals(secondType)) {
return firstType;
}
Type[] firstArguments = firstType.getTypeArguments();
Type[] secondArguments = secondType.getTypeArguments();
int argumentCount = firstArguments.length;
if (argumentCount == 0) {
return firstType;
}
Type[] lubArguments = new Type[argumentCount];
for (int i = 0; i < argumentCount; i++) {
//
// Ideally we would take the least upper bound of the two argument types, but this can cause
// an infinite recursion (such as when finding the least upper bound of String and num).
//
if (firstArguments[i].equals(secondArguments[i])) {
lubArguments[i] = firstArguments[i];
}
if (lubArguments[i] == null) {
lubArguments[i] = DynamicTypeImpl.getInstance();
}
}
InterfaceTypeImpl lub = new InterfaceTypeImpl(firstType.getElement());
lub.setTypeArguments(lubArguments);
return lub;
}
/**
* An array containing the actual types of the type arguments.
*/
private Type[] typeArguments = TypeImpl.EMPTY_ARRAY;
/**
* Initialize a newly created type to be declared by the given element.
*
* @param element the element representing the declaration of the type
*/
public InterfaceTypeImpl(ClassElement element) {
super(element, element.getDisplayName());
}
/**
* Initialize a newly created type to have the given name. This constructor should only be used in
* cases where there is no declaration of the type.
*
* @param name the name of the type
*/
private InterfaceTypeImpl(String name) {
super(null, name);
}
@Override
public boolean equals(Object object) {
return internalEquals(object, new HashSet<ElementPair>());
}
@Override
public PropertyAccessorElement[] getAccessors() {
PropertyAccessorElement[] accessors = getElement().getAccessors();
PropertyAccessorElement[] members = new PropertyAccessorElement[accessors.length];
for (int i = 0; i < accessors.length; i++) {
members[i] = PropertyAccessorMember.from(accessors[i], this);
}
return members;
}
@Override
public String getDisplayName() {
String name = getName();
Type[] typeArguments = getTypeArguments();
boolean allDynamic = true;
for (Type type : typeArguments) {
if (type != null && !type.isDynamic()) {
allDynamic = false;
break;
}
}
// If there is at least one non-dynamic type, then list them out
if (!allDynamic) {
StringBuilder builder = new StringBuilder();
builder.append(name);
builder.append("<");
for (int i = 0; i < typeArguments.length; i++) {
if (i != 0) {
builder.append(", ");
}
Type typeArg = typeArguments[i];
builder.append(typeArg.getDisplayName());
}
builder.append(">");
name = builder.toString();
}
return name;
}
@Override
public ClassElement getElement() {
return (ClassElement) super.getElement();
}
@Override
public PropertyAccessorElement getGetter(String getterName) {
return PropertyAccessorMember.from(
((ClassElementImpl) getElement()).getGetter(getterName),
this);
}
@Override
public InterfaceType[] getInterfaces() {
ClassElement classElement = getElement();
InterfaceType[] interfaces = classElement.getInterfaces();
TypeParameterElement[] typeParameters = classElement.getTypeParameters();
Type[] parameterTypes = classElement.getType().getTypeArguments();
if (typeParameters.length == 0) {
return interfaces;
}
int count = interfaces.length;
InterfaceType[] typedInterfaces = new InterfaceType[count];
for (int i = 0; i < count; i++) {
typedInterfaces[i] = interfaces[i].substitute(typeArguments, parameterTypes);
}
return typedInterfaces;
}
@Override
public Type getLeastUpperBound(Type type) {
// quick check for self
if (type == this) {
return this;
}
// dynamic
Type dynamicType = DynamicTypeImpl.getInstance();
if (this == dynamicType || type == dynamicType) {
return dynamicType;
}
// TODO (jwren) opportunity here for a better, faster algorithm if this turns out to be a bottle-neck
if (!(type instanceof InterfaceType)) {
return null;
}
// new names to match up with the spec
InterfaceType i = this;
InterfaceType j = (InterfaceType) type;
// compute set of supertypes
Set<InterfaceType> si = computeSuperinterfaceSet(i);
Set<InterfaceType> sj = computeSuperinterfaceSet(j);
// union si with i and sj with j
si.add(i);
sj.add(j);
// compute intersection, reference as set 's'
InterfaceType[] s = intersection(si, sj);
// for each element in Set s, compute the largest inheritance path to Object
int[] depths = new int[s.length];
int maxDepth = 0;
for (int n = 0; n < s.length; n++) {
depths[n] = computeLongestInheritancePathToObject(s[n]);
if (depths[n] > maxDepth) {
maxDepth = depths[n];
}
}
// ensure that the currently computed maxDepth is unique,
// otherwise, decrement and test for uniqueness again
for (; maxDepth >= 0; maxDepth--) {
int indexOfLeastUpperBound = -1;
int numberOfTypesAtMaxDepth = 0;
for (int m = 0; m < depths.length; m++) {
if (depths[m] == maxDepth) {
numberOfTypesAtMaxDepth++;
indexOfLeastUpperBound = m;
}
}
if (numberOfTypesAtMaxDepth == 1) {
return s[indexOfLeastUpperBound];
}
}
// illegal state, log and return null- Object at maxDepth == 0 should always return itself as
// the least upper bound.
// TODO (jwren) log the error state
return null;
}
@Override
public MethodElement getMethod(String methodName) {
return MethodMember.from(((ClassElementImpl) getElement()).getMethod(methodName), this);
}
@Override
public MethodElement[] getMethods() {
MethodElement[] methods = getElement().getMethods();
MethodElement[] members = new MethodElement[methods.length];
for (int i = 0; i < methods.length; i++) {
members[i] = MethodMember.from(methods[i], this);
}
return members;
}
@Override
public InterfaceType[] getMixins() {
ClassElement classElement = getElement();
InterfaceType[] mixins = classElement.getMixins();
TypeParameterElement[] typeParameters = classElement.getTypeParameters();
Type[] parameterTypes = classElement.getType().getTypeArguments();
if (typeParameters.length == 0) {
return mixins;
}
int count = mixins.length;
InterfaceType[] typedMixins = new InterfaceType[count];
for (int i = 0; i < count; i++) {
typedMixins[i] = mixins[i].substitute(typeArguments, parameterTypes);
}
return typedMixins;
}
@Override
public PropertyAccessorElement getSetter(String setterName) {
return PropertyAccessorMember.from(
((ClassElementImpl) getElement()).getSetter(setterName),
this);
}
@Override
public InterfaceType getSuperclass() {
ClassElement classElement = getElement();
InterfaceType supertype = classElement.getSupertype();
if (supertype == null) {
return null;
}
Type[] typeParameters = classElement.getType().getTypeArguments();
if (typeArguments.length == 0 || typeArguments.length != typeParameters.length) {
return supertype;
}
return supertype.substitute(typeArguments, typeParameters);
}
@Override
public Type[] getTypeArguments() {
return typeArguments;
}
@Override
public TypeParameterElement[] getTypeParameters() {
return getElement().getTypeParameters();
}
@Override
public int hashCode() {
ClassElement element = getElement();
if (element == null) {
return 0;
}
return element.hashCode();
}
@Override
public boolean isDartCoreFunction() {
ClassElement element = getElement();
if (element == null) {
return false;
}
return element.getName().equals("Function") && element.getLibrary().isDartCore();
}
@Override
public boolean isDirectSupertypeOf(InterfaceType type) {
InterfaceType i = this;
InterfaceType j = type;
ClassElement jElement = j.getElement();
InterfaceType supertype = jElement.getSupertype();
//
// If J has no direct supertype then it is Object, and Object has no direct supertypes.
//
if (supertype == null) {
return false;
}
//
// I is listed in the extends clause of J.
//
Type[] jArgs = j.getTypeArguments();
Type[] jVars = jElement.getType().getTypeArguments();
supertype = supertype.substitute(jArgs, jVars);
if (supertype.equals(i)) {
return true;
}
//
// I is listed in the implements clause of J.
//
for (InterfaceType interfaceType : jElement.getInterfaces()) {
interfaceType = interfaceType.substitute(jArgs, jVars);
if (interfaceType.equals(i)) {
return true;
}
}
//
// I is listed in the with clause of J.
//
for (InterfaceType mixinType : jElement.getMixins()) {
mixinType = mixinType.substitute(jArgs, jVars);
if (mixinType.equals(i)) {
return true;
}
}
//
// J is a mixin application of the mixin of I.
//
// TODO(brianwilkerson) Determine whether this needs to be implemented or whether it is covered
// by the case above.
return false;
}
@Override
public boolean isObject() {
return getElement().getSupertype() == null;
}
@Override
public ConstructorElement lookUpConstructor(String constructorName, LibraryElement library) {
// prepare base ConstructorElement
ConstructorElement constructorElement;
if (constructorName == null) {
constructorElement = getElement().getUnnamedConstructor();
} else {
constructorElement = getElement().getNamedConstructor(constructorName);
}
// not found or not accessible
if (constructorElement == null || !constructorElement.isAccessibleIn(library)) {
return null;
}
// return member
return ConstructorMember.from(constructorElement, this);
}
@Override
public PropertyAccessorElement lookUpGetter(String getterName, LibraryElement library) {
PropertyAccessorElement element = getGetter(getterName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
return lookUpGetterInSuperclass(getterName, library);
}
@Override
public PropertyAccessorElement lookUpGetterInSuperclass(String getterName, LibraryElement library) {
for (InterfaceType mixin : getMixins()) {
PropertyAccessorElement element = mixin.getGetter(getterName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
}
HashSet<ClassElement> visitedClasses = new HashSet<ClassElement>();
InterfaceType supertype = getSuperclass();
ClassElement supertypeElement = supertype == null ? null : supertype.getElement();
while (supertype != null && !visitedClasses.contains(supertypeElement)) {
visitedClasses.add(supertypeElement);
PropertyAccessorElement element = supertype.getGetter(getterName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
for (InterfaceType mixin : supertype.getMixins()) {
element = mixin.getGetter(getterName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
}
supertype = supertype.getSuperclass();
supertypeElement = supertype == null ? null : supertype.getElement();
}
return null;
}
@Override
public MethodElement lookUpMethod(String methodName, LibraryElement library) {
MethodElement element = getMethod(methodName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
return lookUpMethodInSuperclass(methodName, library);
}
@Override
public MethodElement lookUpMethodInSuperclass(String methodName, LibraryElement library) {
for (InterfaceType mixin : getMixins()) {
MethodElement element = mixin.getMethod(methodName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
}
HashSet<ClassElement> visitedClasses = new HashSet<ClassElement>();
InterfaceType supertype = getSuperclass();
ClassElement supertypeElement = supertype == null ? null : supertype.getElement();
while (supertype != null && !visitedClasses.contains(supertypeElement)) {
visitedClasses.add(supertypeElement);
MethodElement element = supertype.getMethod(methodName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
for (InterfaceType mixin : supertype.getMixins()) {
element = mixin.getMethod(methodName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
}
supertype = supertype.getSuperclass();
supertypeElement = supertype == null ? null : supertype.getElement();
}
return null;
}
@Override
public PropertyAccessorElement lookUpSetter(String setterName, LibraryElement library) {
PropertyAccessorElement element = getSetter(setterName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
return lookUpSetterInSuperclass(setterName, library);
}
@Override
public PropertyAccessorElement lookUpSetterInSuperclass(String setterName, LibraryElement library) {
for (InterfaceType mixin : getMixins()) {
PropertyAccessorElement element = mixin.getSetter(setterName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
}
HashSet<ClassElement> visitedClasses = new HashSet<ClassElement>();
InterfaceType supertype = getSuperclass();
ClassElement supertypeElement = supertype == null ? null : supertype.getElement();
while (supertype != null && !visitedClasses.contains(supertypeElement)) {
visitedClasses.add(supertypeElement);
PropertyAccessorElement element = supertype.getSetter(setterName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
for (InterfaceType mixin : supertype.getMixins()) {
element = mixin.getSetter(setterName);
if (element != null && element.isAccessibleIn(library)) {
return element;
}
}
supertype = supertype.getSuperclass();
supertypeElement = supertype == null ? null : supertype.getElement();
}
return null;
}
/**
* Set the actual types of the type arguments to those in the given array.
*
* @param typeArguments the actual types of the type arguments
*/
public void setTypeArguments(Type[] typeArguments) {
this.typeArguments = typeArguments;
}
@Override
public InterfaceTypeImpl substitute(Type[] argumentTypes) {
return substitute(argumentTypes, getTypeArguments());
}
@Override
public InterfaceTypeImpl substitute(Type[] argumentTypes, Type[] parameterTypes) {
if (argumentTypes.length != parameterTypes.length) {
throw new IllegalArgumentException("argumentTypes.length (" + argumentTypes.length
+ ") != parameterTypes.length (" + parameterTypes.length + ")");
}
if (argumentTypes.length == 0 || typeArguments.length == 0) {
return this;
}
Type[] newTypeArguments = substitute(typeArguments, argumentTypes, parameterTypes);
if (Arrays.equals(newTypeArguments, typeArguments)) {
return this;
}
InterfaceTypeImpl newType = new InterfaceTypeImpl(getElement());
newType.setTypeArguments(newTypeArguments);
return newType;
}
@Override
protected void appendTo(StringBuilder builder) {
builder.append(getName());
int argumentCount = typeArguments.length;
if (argumentCount > 0) {
builder.append("<");
for (int i = 0; i < argumentCount; i++) {
if (i > 0) {
builder.append(", ");
}
((TypeImpl) typeArguments[i]).appendTo(builder);
}
builder.append(">");
}
}
@Override
protected boolean internalEquals(Object object, Set<ElementPair> visitedElementPairs) {
if (!(object instanceof InterfaceTypeImpl)) {
return false;
}
InterfaceTypeImpl otherType = (InterfaceTypeImpl) object;
return ObjectUtilities.equals(getElement(), otherType.getElement())
&& TypeImpl.equalArrays(typeArguments, otherType.typeArguments, visitedElementPairs);
}
@Override
protected boolean internalIsMoreSpecificThan(Type type, boolean withDynamic,
Set<TypePair> visitedTypePairs) {
//
// S is dynamic.
// The test to determine whether S is dynamic is done here because dynamic is not an instance of
// InterfaceType.
//
if (type == DynamicTypeImpl.getInstance()) {
return true;
} else if ((type instanceof UnionType)) {
return ((UnionTypeImpl) type).internalUnionTypeIsLessSpecificThan(
this,
withDynamic,
visitedTypePairs);
} else if (!(type instanceof InterfaceType)) {
return false;
}
return isMoreSpecificThan(
(InterfaceType) type,
new HashSet<ClassElement>(),
withDynamic,
visitedTypePairs);
}
@Override
protected boolean internalIsSubtypeOf(Type type, Set<TypePair> visitedTypePairs) {
//
// T is a subtype of S, written T <: S, iff [bottom/dynamic]T << S
//
if (type.isDynamic()) {
return true;
} else if (type instanceof TypeParameterType) {
return false;
} else if (type instanceof UnionType) {
return ((UnionTypeImpl) type).internalUnionTypeIsSuperTypeOf(this, visitedTypePairs);
} else if (type instanceof FunctionType) {
// This implementation assumes transitivity
// for function type subtyping on the RHS, but a literal reading
// of the spec does not specify this. More precisely: if T <: F1 and F1 <: F2 and
// F1 and F2 are function types, then we assume T <: F2.
//
// From the Function Types section of the spec:
//
// If a type I includes an instance method named call(), and the type of call()
// is the function type F, then I is considered to be a subtype of F.
//
// However, the section on Interface Types says
//
// T is a subtype of S, written T <: S, iff [bottom/dynamic]T << S.
//
// after giving rules for << (pronounced "more specific than"). However, the "only if"
// direction of the "iff"
// in the definition of <: seems to be contradicted by the special case <: rule
// quoted from the Function Types section: I see no rule for << which tells us that
// I << F if I has call() at type F.
//
// After defining <: , the spec then
// emphasizes that unlike the relation <<, the relation <: is not transitive in general:
//
// Note that <: is not a partial order on types, it is only binary relation on types.
// This is because <: is not transitive. If it was, the subtype rule would have a cycle.
// For example: List <: List<String> and List<int> <: List, but List<int> is not a subtype
// of List<String>. Although <: is not a partial order on types, it does contain a partial
// order, namely <<. This means that, barring raw types, intuition about classical subtype
// rules does apply.
//
// There is no other occurrence of the word "raw" in relation to types in the spec that I can
// find, but presumably it's a reference to
//
// http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html
//
// so e.g. non-generic types are never raw. As pointed out by paulberry, it's not clear
// whether a type like T<int, dynamic> should be considered raw or not. On the one hand, it
// doesn't correspond to a "raw"-in-the-Java-sense occurrence of T, which would instead
// be T<dynamic, dynamic>; on the other hand, it's treated differently by <: and << when
// occurring on the left hand side.
ClassElement element = getElement();
InheritanceManager manager = new InheritanceManager(element.getLibrary());
FunctionType callType = manager.lookupMemberType(this, "call");
if (callType != null) {
// A more literal reading of the spec would give something like
//
// return callType.equals(type)
//
// here, but that causes 101 errors in the external tests
// (tools/test.py --mode release --compiler dartanalyzer --runtime none).
return callType.isSubtypeOf(type);
}
return false;
} else if (!(type instanceof InterfaceType)) {
return false;
} else if (this.equals(type)) {
return true;
}
return isSubtypeOf((InterfaceType) type, new HashSet<ClassElement>(), visitedTypePairs);
}
// TODO(jwren) Remove "visitedClasses" parameter, as the logic for "visitedTypePairs" should
// prevent a larger set of infinite loops
private boolean isMoreSpecificThan(InterfaceType s, HashSet<ClassElement> visitedClasses,
boolean withDynamic, Set<TypePair> visitedTypePairs) {
//
// A type T is more specific than a type S, written T << S, if one of the following conditions
// is met:
//
// Reflexivity: T is S.
//
if (this.equals(s)) {
return true;
}
//
// T is bottom. (This case is handled by the class BottomTypeImpl.)
//
// Direct supertype: S is a direct supertype of T.
//
if (s.isDirectSupertypeOf(this)) {
return true;
}
//
// Covariance: T is of the form I<T1, ..., Tn> and S is of the form I<S1, ..., Sn> and Ti << Si, 1 <= i <= n.
//
ClassElement tElement = getElement();
ClassElement sElement = s.getElement();
if (tElement.equals(sElement)) {
Type[] tArguments = getTypeArguments();
Type[] sArguments = s.getTypeArguments();
if (tArguments.length != sArguments.length) {
return false;
}
for (int i = 0; i < tArguments.length; i++) {
if (!((TypeImpl) tArguments[i]).isMoreSpecificThan(
sArguments[i],
withDynamic,
visitedTypePairs)) {
return false;
}
}
return true;
}
//
// Transitivity: T << U and U << S.
//
// First check for infinite loops
ClassElement element = getElement();
if (element == null || visitedClasses.contains(element)) {
return false;
}
visitedClasses.add(element);
// Iterate over all of the types U that are more specific than T because they are direct
// supertypes of T and return true if any of them are more specific than S.
InterfaceType supertype = getSuperclass();
if (supertype != null
&& ((InterfaceTypeImpl) supertype).isMoreSpecificThan(
s,
visitedClasses,
withDynamic,
visitedTypePairs)) {
return true;
}
for (InterfaceType interfaceType : getInterfaces()) {
if (((InterfaceTypeImpl) interfaceType).isMoreSpecificThan(
s,
visitedClasses,
withDynamic,
visitedTypePairs)) {
return true;
}
}
for (InterfaceType mixinType : getMixins()) {
if (((InterfaceTypeImpl) mixinType).isMoreSpecificThan(
s,
visitedClasses,
withDynamic,
visitedTypePairs)) {
return true;
}
}
return false;
}
private boolean isSubtypeOf(InterfaceType type, HashSet<ClassElement> visitedClasses,
Set<TypePair> visitedTypePairs) {
InterfaceType typeT = this;
InterfaceType typeS = type;
ClassElement elementT = getElement();
if (elementT == null || visitedClasses.contains(elementT)) {
return false;
}
visitedClasses.add(elementT);
if (typeT.equals(typeS)) {
return true;
} else if (ObjectUtilities.equals(elementT, typeS.getElement())) {
// For each of the type arguments return true if all type args from T is a subtype of all
// types from S.
Type[] typeTArgs = typeT.getTypeArguments();
Type[] typeSArgs = typeS.getTypeArguments();
if (typeTArgs.length != typeSArgs.length) {
// This case covers the case where two objects are being compared that have a different
// number of parameterized types.
return false;
}
for (int i = 0; i < typeTArgs.length; i++) {
// Recursively call isSubtypeOf the type arguments and return false if the T argument is not
// a subtype of the S argument.
if (!((TypeImpl) typeTArgs[i]).isSubtypeOf(typeSArgs[i], visitedTypePairs)) {
return false;
}
}
return true;
} else if (typeS.isDartCoreFunction() && elementT.getMethod("call") != null) {
return true;
}
InterfaceType supertype = getSuperclass();
// The type is Object, return false.
if (supertype != null
&& ((InterfaceTypeImpl) supertype).isSubtypeOf(typeS, visitedClasses, visitedTypePairs)) {
return true;
}
InterfaceType[] interfaceTypes = getInterfaces();
for (InterfaceType interfaceType : interfaceTypes) {
if (((InterfaceTypeImpl) interfaceType).isSubtypeOf(typeS, visitedClasses, visitedTypePairs)) {
return true;
}
}
InterfaceType[] mixinTypes = getMixins();
for (InterfaceType mixinType : mixinTypes) {
if (((InterfaceTypeImpl) mixinType).isSubtypeOf(typeS, visitedClasses, visitedTypePairs)) {
return true;
}
}
return false;
}
}