blob: 8e83daf9a3bd62d52752853f8f0741ede31b9d9e [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler.type;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.dart.compiler.ast.DartNewExpression;
import com.google.dart.compiler.ast.DartNode;
import com.google.dart.compiler.ast.DartPropertyAccess;
import com.google.dart.compiler.ast.DartTypeNode;
import com.google.dart.compiler.resolver.ClassElement;
import com.google.dart.compiler.resolver.CoreTypeProvider;
import com.google.dart.compiler.resolver.Element;
import com.google.dart.compiler.resolver.FunctionAliasElement;
import com.google.dart.compiler.resolver.ResolutionErrorListener;
import com.google.dart.compiler.resolver.TypeVariableElement;
import com.google.dart.compiler.resolver.VariableElement;
import com.google.dart.compiler.type.InterfaceType.Member;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Utility class for types.
*/
public class Types {
private static Map<Type, Map<TypeQuality, Type>> inferredTypes = new MapMaker().weakKeys().makeMap();
private final CoreTypeProvider typeProvider;
private Types(CoreTypeProvider typeProvider) { // Prevent subclassing.
this.typeProvider = typeProvider;
}
public Type leastUpperBound(Type t, Type s) {
if (isSubtype(t, s)) {
return s;
} else if (isSubtype(s, t)) {
return t;
} else {
List<InterfaceType> tTypes = getSuperTypes(t);
List<InterfaceType> sTypes = getSuperTypes(s);
for (InterfaceType tType : tTypes) {
if (sTypes.contains(tType)) {
return tType;
}
}
return typeProvider.getObjectType();
}
}
public Type intersection(Type t, Type s) {
return intersection(ImmutableList.of(t, s));
}
public static TypeQuality getIntersectionQuality(Type t, Type s) {
return getIntersectionQuality(ImmutableList.of(t, s));
}
public static TypeQuality getIntersectionQuality(List<Type> types) {
Type singleType = null;
for (Type type : types) {
if (singleType == null) {
singleType = type;
} else if (singleType != type) {
return TypeQuality.INFERRED;
}
}
return TypeQuality.INFERRED_EXACT;
}
public Type intersection(List<Type> types) {
// exclude 'dynamic' type
{
List<Type> newTypes = Lists.newArrayList();
for (Type type : types) {
if (TypeKind.of(type) != TypeKind.DYNAMIC) {
newTypes.add(type);
}
}
types = newTypes;
}
// no types, so Dynamic
if (types.isEmpty()) {
return typeProvider.getDynamicType();
}
// prepare all super types
List<List<InterfaceType>> superTypesLists = Lists.newArrayList();
List<Map<InterfaceType, InterfaceType>> superTypesMaps = Lists.newArrayList();
for (Type type : types) {
List<InterfaceType> superTypes = getSuperTypes(type);
superTypesLists.add(superTypes);
Map<InterfaceType, InterfaceType> superTypesMap = Maps.newHashMap();
for (InterfaceType superType : superTypes) {
superTypesMap.put(superType.asRawType(), superType);
}
superTypesMaps.add(superTypesMap);
}
// find intersection of super types
LinkedList<InterfaceType> interTypes = Lists.newLinkedList();
if (superTypesLists.size() > 0) {
for (InterfaceType superType : superTypesLists.get(0)) {
boolean inAll = true;
for (Map<InterfaceType, InterfaceType> otherTypesMap : superTypesMaps) {
InterfaceType superTypeRaw = superType.asRawType();
InterfaceType otherType = otherTypesMap.get(superTypeRaw);
// no such raw type, exclude from intersection
if (otherType == null) {
inAll = false;
break;
}
// if not raw, choose type arguments
if (!superType.getArguments().isEmpty()) {
InterfaceType t0 = superType;
InterfaceType t1 = otherType;
// if two-way sub-type, then has Dynamic(s), choose with least number
if (isSubtype(t0, t1) && isSubtype(t1, t0)) {
int dynamics0 = getDynamicArgumentsCount(t0);
int dynamics1 = getDynamicArgumentsCount(t1);
if (dynamics0 < dynamics1) {
superType = t0;
} else {
superType = t1;
}
continue;
}
// use super-type of t0 and t1
if (isSubtype(t0, t1)) {
superType = t1;
}
if (isSubtype(t1, t0)) {
superType = t0;
}
}
}
if (inAll && !interTypes.contains(superType)) {
interTypes.add(superType);
}
}
}
// try to remove sub-types already covered by existing types
for (Iterator<InterfaceType> i = interTypes.descendingIterator(); i.hasNext();) {
InterfaceType subType = i.next();
boolean hasSuperType = false;
for (InterfaceType superType : interTypes) {
if (superType != subType && isSubtype(superType, subType)) {
hasSuperType = true;
break;
}
}
if (hasSuperType) {
i.remove();
}
}
// use single type
if (interTypes.size() == 0) {
return typeProvider.getObjectType();
}
if (interTypes.size() == 1) {
return interTypes.get(0);
}
// create union
return unionTypes(interTypes);
}
/**
* @return the {@link InterfaceType} which is union of given ones.
*/
public InterfaceType unionTypes(List<InterfaceType> types) {
return new InterfaceTypeUnionImplementation(types);
}
/**
* @return the number of <code>Dynamic</code> type arguments in given {@link InterfaceType}.
*/
private static int getDynamicArgumentsCount(InterfaceType t) {
return Iterables.size(Iterables.filter(t.getArguments(), new Predicate<Type>() {
public boolean apply(Type arg) {
return TypeKind.of(arg) == TypeKind.DYNAMIC;
}
}));
}
/**
* @return list of the super-types (if class type given) or super-interfaces (if interface type
* given) from most specific to least specific.
*/
private static List<InterfaceType> getSuperTypes(Type type) {
List<InterfaceType> types = Lists.newArrayList();
if (type instanceof InterfaceType) {
InterfaceType interfaceType = (InterfaceType) type;
types.add(interfaceType);
for (InterfaceType intf : interfaceType.getElement().getInterfaces()) {
intf = asSupertype(interfaceType, intf);
types.addAll(getSuperTypes(intf));
}
for (InterfaceType mixin : interfaceType.getElement().getMixins()) {
mixin = asSupertype(interfaceType, mixin);
types.add(mixin);
}
if (!interfaceType.getElement().isInterface()) {
InterfaceType superClass = interfaceType.getElement().getSupertype();
superClass= asSupertype(interfaceType, superClass);
types.addAll(getSuperTypes(superClass));
}
}
return types;
}
/**
* Return an interface type representing the given interface, function or
* variable type.
* @return An interface type or null if the argument is neither an interface
* function or variable type.
*/
public InterfaceType getInterfaceType(Type type) {
switch (TypeKind.of(type)) {
case VARIABLE: {
TypeVariableElement element = ((TypeVariable) type).getTypeVariableElement();
if (element.getBound() == null) {
return typeProvider.getObjectType();
} else {
return getInterfaceType(element.getBound());
}
}
case FUNCTION:
case FUNCTION_ALIAS:
return typeProvider.getFunctionType();
case INTERFACE:
return (InterfaceType) type;
case DYNAMIC:
case NONE:
case VOID:
default:
return null;
}
}
/**
* Returns true if t is a subtype of s.
*/
public boolean isSubtype(Type t, Type s) {
if (t.getKind().equals(TypeKind.DYNAMIC)) {
return true;
}
switch (s.getKind()) {
case DYNAMIC:
return true;
case INTERFACE:
return isSubtypeOfInterface(t, (InterfaceType) s);
case FUNCTION_ALIAS:
return isSubtypeOfAlias(t, (FunctionAliasType) s);
case FUNCTION:
switch (t.getKind()) {
case FUNCTION_ALIAS:
return isSubtypeOfFunction(asFunctionType((FunctionAliasType) t), (FunctionType) s);
case FUNCTION:
return isSubtypeOfFunction((FunctionType) t, (FunctionType) s);
default:
return false;
}
case VARIABLE:
return isSubtypeOfTypeVariable(t, (TypeVariable) s);
case VOID:
return t.equals(s);
default:
throw new AssertionError(s.getKind());
}
}
static FunctionType asFunctionType(FunctionAliasType alias) {
FunctionAliasElement element = alias.getElement();
FunctionType type =
(FunctionType) element.getFunctionType().subst(alias.getArguments(),
element.getTypeParameters());
return type;
}
private boolean isSubtypeOfAlias(Type t, FunctionAliasType s) {
if (isSubtypeOfInterface(t, s)) {
return true;
}
if (t.getKind() == TypeKind.VARIABLE) {
Type bound = ((TypeVariable) t).getTypeVariableElement().getBound();
if (bound != null) {
return isSubtype(bound, s);
}
return true;
}
if (t.getKind() == TypeKind.FUNCTION_ALIAS) {
return isSubtypeOfFunction(asFunctionType((FunctionAliasType) t), asFunctionType(s));
}
// class has method call()
if (t.getKind() == TypeKind.INTERFACE) {
InterfaceType ti = (InterfaceType) t;
Member callMember = ti.lookupMember("call");
return callMember != null && isSubtype(callMember.getType(), asFunctionType(s));
}
return false;
}
private boolean isSubtypeOfTypeVariable(Type t, TypeVariable sv) {
// May be same type variable.
if (sv.equals(t)) {
return true;
}
// May be "T extends S".
if (t.getKind() == TypeKind.VARIABLE) {
TypeVariable tv = (TypeVariable) t;
Type tBound = tv.getTypeVariableElement().getBound();
if (tBound != null && tBound.getKind() == TypeKind.VARIABLE) {
// Prevent cycle.
if (tBound.equals(t)) {
return false;
}
// Check bound.
return isSubtype(tBound, sv);
}
}
// May be concrete InterfaceType.
if (t.getKind() == TypeKind.INTERFACE) {
InterfaceType ti = (InterfaceType) t;
Type sBound = sv.getTypeVariableElement().getBound();
if (sBound == null) {
return true;
}
// Prevent cycle.
if (sBound.equals(sv)) {
return false;
}
if (sBound.getKind() == TypeKind.INTERFACE) {
return isSubtype(ti, sBound);
}
}
// no
return false;
}
private boolean isSubtypeOfInterface(Type t, InterfaceType s) {
// Special handling for union.
if (t instanceof InterfaceTypeUnion) {
InterfaceTypeUnion tUnion = (InterfaceTypeUnion) t;
for (InterfaceType unionPart : tUnion.getTypes()) {
if (isSubtype(unionPart, s)) {
return true;
}
}
return false;
}
// class "t" implements call() and "s" is Function
if (TypeKind.of(t) == TypeKind.INTERFACE && typeProvider != null
&& s == typeProvider.getFunctionType()) {
InterfaceType ti = (InterfaceType) t;
if (ti.lookupMember("call") != null) {
return true;
}
}
// Try to cast "t" to "s".
final Type sup = asInstanceOf(t, s.getElement());
if (TypeKind.of(sup) == TypeKind.INTERFACE) {
InterfaceType ti = (InterfaceType) sup;
assert ti.getElement().equals(s.getElement());
if (ti.isRaw() || s.isRaw()) {
return true;
}
// Type arguments are covariant.
return areSubtypes(ti.getArguments().iterator(), s.getArguments().iterator());
}
return false;
}
/**
* Implement the Dart function subtype rule. Unlike the classic arrow rule (return type is
* covariant, and parameter types are contravariant), in Dart they must just be assignable.
*/
private boolean isSubtypeOfFunction(FunctionType t, FunctionType s) {
if (s.getKind() == TypeKind.DYNAMIC || t.getKind() == TypeKind.DYNAMIC) {
return true;
}
// Classic: return type is covariant; Dart: assignable.
if (!isAssignable(t.getReturnType(), s.getReturnType())) {
// A function that returns a value can be used as a function where you ignore the value.
if (!s.getReturnType().equals(typeProvider.getVoidType())) {
return false;
}
}
Type tRest = t.getRest();
Type sRest = s.getRest();
if ((tRest == null) != (sRest == null)) {
return false;
}
if (tRest != null) {
// Classic: parameter types are contravariant; Dart: assignable.
if (!isAssignable(sRest, tRest)) {
return false;
}
}
{
Map<String, Type> sOpti = s.getOptionalParameterTypes();
Map<String, Type> tOpti = t.getOptionalParameterTypes();
if (tOpti.size() < sOpti.size()) {
return false;
}
Iterator<Entry<String, Type>> tList = tOpti.entrySet().iterator();
Iterator<Entry<String, Type>> sList = sOpti.entrySet().iterator();
while (sList.hasNext()) {
if (!tList.hasNext()) {
return false;
}
Entry<String, Type> sEntry = sList.next();
Entry<String, Type> tEntry = tList.next();
if (!isAssignable(tEntry.getValue(), sEntry.getValue())) {
return false;
}
}
}
Map<String, Type> tNamed = t.getNamedParameterTypes();
Map<String, Type> sNamed = s.getNamedParameterTypes();
if (tNamed.isEmpty() && !sNamed.isEmpty()) {
return false;
}
// T's named parameters must be in the same order and assignable to S's but
// maybe a superset.
if (!sNamed.isEmpty()) {
LinkedHashMap<String,Type> tMap = (LinkedHashMap<String, Type>)(tNamed);
LinkedHashMap<String,Type> sMap = (LinkedHashMap<String, Type>)(sNamed);
if (!tMap.keySet().containsAll(sMap.keySet())) {
return false;
}
for (Entry<String, Type> entry : sMap.entrySet()) {
String name = entry.getKey();
Type sType = sMap.get(name);
Type tType = tMap.get(name);
if (!isAssignable(tType, sType)) {
return false;
}
}
// Iterator<Entry<String, Type>> tList = tMap.entrySet().iterator();
// Iterator<Entry<String, Type>> sList = sMap.entrySet().iterator();
// // t named parameters must start with the named parameters of s
// while (sList.hasNext()) {
// if (!tList.hasNext()) {
// return false;
// }
// Entry<String, Type> sEntry = sList.next();
// Entry<String, Type> tEntry = tList.next();
// if (!sEntry.getKey().equals(tEntry.getKey())) {
// return false;
// }
// // Classic: parameter types are contravariant; Dart: assignable.
// if (!isAssignable(tEntry.getValue(), sEntry.getValue())) {
// return false;
// }
// }
}
// Classic: parameter types are contravariant; Dart: assignable.
return areAssignable(s.getParameterTypes().iterator(), t.getParameterTypes().iterator());
}
private boolean areSubtypes(Iterator<? extends Type> t, Iterator<? extends Type> s) {
while (t.hasNext() && s.hasNext()) {
if (!isSubtype(t.next(), s.next())) {
return false;
}
}
// O(1) check to assert t and s are of same size.
return t.hasNext() == s.hasNext();
}
private boolean areAssignable(Iterator<? extends Type> t, Iterator<? extends Type> s) {
while (t.hasNext() && s.hasNext()) {
if (!isAssignable(t.next(), s.next())) {
return false;
}
}
// O(1) check to assert t and s are of same size.
return t.hasNext() == s.hasNext();
}
/**
* Returns true if s is assignable to t.
*/
public boolean isAssignable(Type t, Type s) {
t.getClass(); // Quick null check.
s.getClass(); // Quick null check.
return isSubtype(t, s) || isSubtype(s, t);
}
/**
* Translates the given type into an instantiation of the given
* element. This is done by walking the supertype hierarchy and
* substituting in the appropriate type arguments.
*
* <p>For example, if {@code GrowableArray<T>} is a subtype of
* {@code Array<T>}, then
* {@code asInstanceOf("GrowableArray<String>", "Array")} would
* return {@code Array<String>}
*
* @return null if t is not a subtype of element
*/
@VisibleForTesting
public InterfaceType asInstanceOf(Type t, ClassElement element) {
return checkedAsInstanceOf(t, element, new HashSet<TypeVariable>(), new HashSet<Type>());
}
private InterfaceType checkedAsInstanceOf(Type t, ClassElement element,
Set<TypeVariable> variablesReferenced, Set<Type> checkedTypes) {
// check for recursion
if (checkedTypes.contains(t)) {
return null;
}
checkedTypes.add(t);
// check current Type
switch (TypeKind.of(t)) {
case FUNCTION_ALIAS:
case INTERFACE: {
if (t.getElement().equals(element)) {
return (InterfaceType) t;
}
InterfaceType ti = (InterfaceType) t;
ClassElement tElement = ti.getElement();
InterfaceType supertype = tElement.getSupertype();
// super type
if (supertype != null) {
InterfaceType result = checkedAsInstanceOf(asSupertype(ti, supertype), element,
variablesReferenced, checkedTypes);
if (result != null) {
return result;
}
}
// interfaces
for (InterfaceType intf : tElement.getInterfaces()) {
InterfaceType result = checkedAsInstanceOf(asSupertype(ti, intf), element,
variablesReferenced, checkedTypes);
if (result != null) {
return result;
}
}
// mixins
for (InterfaceType mixin : tElement.getMixins()) {
if (mixin.getElement().equals(element)) {
return asSupertype(ti, mixin);
}
}
// no
return null;
}
case FUNCTION: {
Element e = t.getElement();
switch (e.getKind()) {
case CLASS:
// e should be the interface Function in the core library. See the
// documentation comment on FunctionType.
InterfaceType ti = (InterfaceType) e.getType();
return checkedAsInstanceOf(ti, element, variablesReferenced, checkedTypes);
default:
return null;
}
}
case VARIABLE: {
TypeVariable v = (TypeVariable) t;
Type bound = v.getTypeVariableElement().getBound();
// Check for previously encountered variables to avoid getting stuck in an infinite loop.
if (variablesReferenced.contains(v)) {
if (bound instanceof InterfaceType) {
return (InterfaceType) bound;
}
return typeProvider.getObjectType();
}
variablesReferenced.add(v);
return checkedAsInstanceOf(bound, element, variablesReferenced, checkedTypes);
}
default:
return null;
}
}
private static InterfaceType asSupertype(InterfaceType type, InterfaceType supertype) {
if (supertype == null) {
return null;
}
if (type.isRaw()) {
return supertype.asRawType();
}
List<Type> arguments = type.getArguments();
List<Type> parameters = type.getElement().getTypeParameters();
return supertype.subst(arguments, parameters);
}
static void printTypesOn(StringBuilder sb, List<Type> types,
String start, String end) {
sb.append(start);
boolean first = true;
for (Type argument : types) {
if (!first) {
sb.append(", ");
}
sb.append(argument);
first = false;
}
sb.append(end);
}
public static List<Type> subst(List<Type> types,
List<Type> arguments, List<Type> parameters) {
ArrayList<Type> result = new ArrayList<Type>(types.size());
for (Type type : types) {
result.add(type.subst(arguments, parameters));
}
return result;
}
public static FunctionType makeFunctionType(ResolutionErrorListener listener,
ClassElement element,
List<VariableElement> parameters,
Type returnType) {
List<Type> parameterTypes = new ArrayList<Type>(parameters.size());
Map<String, Type> optionalParameterTypes = null;
Map<String, Type> namedParameterTypes = null;
Type restParameter = null;
for (VariableElement parameter : parameters) {
Type type = parameter.getType();
// TODO(scheglov) one we will make optional parameter not named,
// check isOptional() before isNamed()
if (parameter.isNamed()) {
if (namedParameterTypes == null) {
namedParameterTypes = new LinkedHashMap<String, Type>();
}
namedParameterTypes.put(parameter.getName(), type);
} else if (parameter.isOptional()) {
if (optionalParameterTypes == null) {
optionalParameterTypes = new LinkedHashMap<String, Type>();
}
optionalParameterTypes.put(parameter.getName(), type);
} else {
parameterTypes.add(type);
}
}
return FunctionTypeImplementation.of(element, parameterTypes, optionalParameterTypes,
namedParameterTypes, restParameter, returnType);
}
public static Types getInstance(CoreTypeProvider typeProvider) {
return new Types(typeProvider);
}
public static InterfaceType interfaceType(ClassElement element, List<Type> arguments) {
return new InterfaceTypeImplementation(element, arguments);
}
public static FunctionAliasType functionAliasType(FunctionAliasElement element,
List<TypeVariable> typeVariables) {
return new FunctionAliasTypeImplementation(element,
Collections.<Type>unmodifiableList(typeVariables));
}
public static TypeVariable typeVariable(TypeVariableElement element) {
return new TypeVariableImplementation(element);
}
public static DynamicType newDynamicType() {
return new DynamicTypeImplementation();
}
public static InterfaceType ensureInterface(Type type) {
TypeKind kind = TypeKind.of(type);
switch (kind) {
case INTERFACE:
return (InterfaceType) type;
case NONE:
case DYNAMIC:
return null;
default:
throw new AssertionError("unexpected kind " + kind);
}
}
public static Type newVoidType() {
return new VoidType();
}
/**
* Returns the type node corresponding to the instantiated class or interface.
*/
public static DartTypeNode constructorTypeNode(DartNewExpression node) {
DartNode constructor = node.getConstructor();
if (constructor instanceof DartPropertyAccess) {
return (DartTypeNode) ((DartPropertyAccess) constructor).getQualifier();
} else {
return (DartTypeNode) constructor;
}
}
/**
* Returns the interface type being instantiated by the given node.
*/
public static InterfaceType constructorType(DartNewExpression node) {
DartTypeNode typeNode = constructorTypeNode(node);
return (InterfaceType) typeNode.getType();
}
/**
* @return the wrapper of the given {@link Type} with {@link TypeQuality#INFERRED}.
*/
public static Type makeInferred(Type type) {
return makeInferred(type, TypeQuality.INFERRED);
}
/**
* @return the wrapper of the given {@link Type} with {@link TypeQuality#INFERRED_EXACT}.
*/
public static Type makeInferredExact(Type type) {
return makeInferred(type, TypeQuality.INFERRED_EXACT);
}
/**
* @return the wrapper of the given {@link Type} with given {@link TypeQuality}.
*/
public static Type makeInferred(Type type, TypeQuality quality) {
if (type == null) {
return null;
}
if (type.getQuality().ordinal() > quality.ordinal()) {
return type;
}
if (quality == TypeQuality.EXACT) {
return type;
}
Set<Class<?>> interfaceSet = getAllImplementedInterfaces(type.getClass());
if (!interfaceSet.isEmpty()) {
Class<?>[] interfaces = interfaceSet.toArray(new Class[interfaceSet.size()]);
return makeInferred(type, interfaces, quality);
}
return type;
}
private static Type makeInferred(final Type type, Class<?>[] interfaces, final TypeQuality quality) {
Map<TypeQuality, Type> inferredMap = inferredTypes.get(type);
if (inferredMap == null) {
inferredMap = new MapMaker().weakValues().makeMap();
inferredTypes.put(type, inferredMap);
}
Type inferred = inferredMap.get(quality);
if (inferred == null) {
inferred = (Type) Proxy.newProxyInstance(type.getClass().getClassLoader(),
interfaces, new InvocationHandler() {
@Override
@SuppressWarnings("unchecked")
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (args == null && method.getName().equals("getQuality")) {
return quality;
}
if (type instanceof FunctionType) {
if (args == null && method.getName().equals("getParameterTypes")) {
List<Type> originalTypes = (List<Type>) method.invoke(type, args);
return Lists.transform(originalTypes, new Function<Type, Type>() {
public Type apply(Type input) {
return makeInferred(input, quality);
}
});
}
}
return method.invoke(type, args);
}
});
inferredMap.put(quality, inferred);
}
return inferred;
}
/**
* @return all interfaces implemented by given {@link Class}.
*/
private static Set<Class<?>> getAllImplementedInterfaces(Class<?> c) {
Set<Class<?>> result = Sets.newHashSet();
for (Class<?> intf : c.getInterfaces()) {
result.add(intf);
result.addAll(getAllImplementedInterfaces(intf));
}
return result;
}
}