| /* |
| * 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; |
| } |
| } |