blob: ac3b69d65d010238f842176e3859c46c8515748d [file] [log] [blame]
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.gwt.dev.javac.typemodel;
import com.google.gwt.core.ext.typeinfo.JType;
import java.util.Set;
/**
* A helper class to check assignability of types.
*/
class AssignabilityChecker {
public boolean isAssignable(JClassType from, JClassType to) {
from = convertToRawIfGeneric(from);
to = convertToRawIfGeneric(to);
if (to == from) {
return true;
}
if (to.isWildcard() != null) {
return isAssignableToWildcardType(from, to.isWildcard());
}
if (from.isTypeParameter() != null) {
return isAssignableFromAny(from.isTypeParameter().getBounds(), to);
}
if (from.isWildcard() != null) {
return isAssignableFromAny(from.isWildcard().getUpperBounds(), to);
}
if (from.isArray() != null) {
return isAssignableFromGenericArrayType(from.isArray(), to);
}
if (to.isParameterized() != null) {
return isAssignableToParameterizedType(from, to.isParameterized());
}
if (to.isTypeParameter() != null) {
// type inference is not supported (yet)
return isAssignableFromAll(from, to.isTypeParameter().getBounds());
}
if (to.isArray() != null) {
return false;
}
// Only remaining cases for 'to' are being real-class or raw type
assert to instanceof JRealClassType || to instanceof JRawType;
return isAssignableFromRaw(from, to);
}
private boolean isAssignableFromAny(JClassType[] fromTypes, JClassType to) {
for (JClassType from : fromTypes) {
if (isAssignable(from, to)) {
return true;
}
}
return false;
}
private boolean isAssignableToWildcardType(JClassType from, JWildcardType to) {
// if "to" is <? extends Foo>, "from" can be:
// Foo, SubFoo, <? extends Foo>, <? extends SubFoo>, <T extends Foo> or <T extends SubFoo>.
// if "to" is <? super Foo>, "from" can be:
// Foo, SuperFoo, <? super Foo> or <? super SuperFoo>.
return isAssignable(from, supertypeBound(to)) && isAssignableBySubtypeBound(from, to);
}
private boolean isAssignableBySubtypeBound(JClassType from, JWildcardType to) {
JClassType toSubtypeBound = subtypeBound(to);
if (toSubtypeBound == null) {
return true;
}
JClassType fromSubtypeBound = subtypeBound(from);
if (fromSubtypeBound == null) {
return false;
}
return isAssignable(toSubtypeBound, fromSubtypeBound);
}
private boolean isAssignableToParameterizedType(JClassType from, JParameterizedType to) {
// If "to" is "List<? extends CharSequence>" and "from" is StringArrayList,
// First step is to figure out StringArrayList "is-a" List<E> and <E> is String.
JMaybeParameterizedType parentOfFrom = asParamterizationOf(from, to);
if (parentOfFrom == null) {
return false;
}
if (parentOfFrom.isRawType() != null) {
return true;
}
// If it is not raw, then it should be parameterized
JParameterizedType parameterizedParentOfFrom = parentOfFrom.isParameterized();
assert parameterizedParentOfFrom != null;
JClassType[] fromTypeArgs = parameterizedParentOfFrom.getTypeArgs();
JClassType[] toTypeArgs = to.getTypeArgs();
for (int i = 0; i < fromTypeArgs.length; i++) {
if (!matchTypeArgument(fromTypeArgs[i], toTypeArgs[i])) {
return false;
}
}
return true;
}
private boolean matchTypeArgument(JClassType from, JClassType to) {
if (from == to) {
return true;
}
if (to.isWildcard() != null) {
return isAssignableToWildcardType(from, to.isWildcard());
}
return false;
}
private boolean isAssignableFromAll(JClassType from, JClassType[] toTypes) {
for (JClassType to : toTypes) {
if (!isAssignable(from, to)) {
return false;
}
}
return true;
}
private boolean isAssignableFromGenericArrayType(JArrayType from, JClassType to) {
if (to.isArray() != null) {
JType fromComponentType = from.getComponentType();
JType toComponentType = to.isArray().getComponentType();
if (toComponentType.isPrimitive() != null || fromComponentType.isPrimitive() != null) {
// Only scenario for this to be assignable is; this two being equal, but we wouldn't have
// reached here in that case
return false;
}
return isAssignable((JClassType) fromComponentType, (JClassType) toComponentType);
}
return isJavaLangObject(to);
}
private boolean isAssignableFromRaw(JClassType from, JClassType to) {
if (isJavaLangObject(to)) {
return true;
}
Set<JClassType> fromSuperTypeHierarchy = from.getFlattenedSupertypeHierarchy();
// Shortcut: 'to' is one of the parents.
if (fromSuperTypeHierarchy.contains(to)) {
return true;
}
// Fallback to checking erased types if it is raw.
if (to.isRawType() != null) {
for (JClassType fromSuper : fromSuperTypeHierarchy) {
if (fromSuper.getErasedType() == to.getErasedType()) {
return true;
}
}
}
return false;
}
private static boolean isJavaLangObject(JClassType type) {
return type == type.getOracle().getJavaLangObject();
}
private static JClassType convertToRawIfGeneric(JClassType from) {
return from.isGenericType() != null ? from.isGenericType().getRawType() : from;
}
private static JClassType supertypeBound(JWildcardType type) {
// If type is <? super ? super Foo>, this will return Foo.
// (Even if you cannot write such code in java, the type can resolve to that)
JClassType upperBound = type.getUpperBound();
return upperBound.isWildcard() != null ? supertypeBound(upperBound.isWildcard()) : upperBound;
}
private static JClassType subtypeBound(JWildcardType type) {
// If type is <? extends ? extends Foo>, this will return Foo.
// (Even if you cannot write such code in java, the type can resolve to that)
JClassType[] lowerBounds = type.getLowerBounds();
return lowerBounds.length == 1 ? subtypeBound(lowerBounds[0]) : null;
}
private static JClassType subtypeBound(JClassType type) {
return type.isWildcard() != null ? subtypeBound(type.isWildcard()) : type;
}
private static JMaybeParameterizedType asParamterizationOf(JClassType from,
JParameterizedType to) {
for (JClassType parent : from.getFlattenedSupertypeHierarchy()) {
JMaybeParameterizedType maybeParameterized = parent.isMaybeParameterizedType();
if (maybeParameterized != null && maybeParameterized.getBaseType() == to.getBaseType()) {
return maybeParameterized;
}
}
return null;
}
}