CastDiagnosticsUtil.kt: converted to Kotlin
This commit is contained in:
@@ -14,208 +14,182 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.types;
|
package org.jetbrains.kotlin.types
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicates
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.collect.Lists
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Maps
|
||||||
import com.google.common.collect.Maps;
|
import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap
|
||||||
import kotlin.collections.CollectionsKt;
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import kotlin.jvm.functions.Function1;
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||||
import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap;
|
import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind;
|
|
||||||
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
|
|
||||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor;
|
|
||||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker;
|
|
||||||
import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure;
|
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
|
||||||
|
|
||||||
import java.util.*;
|
object CastDiagnosticsUtil {
|
||||||
|
|
||||||
public class CastDiagnosticsUtil {
|
|
||||||
|
|
||||||
// As this method produces a warning, it must be _complete_ (not sound), i.e. every time it says "cast impossible",
|
// As this method produces a warning, it must be _complete_ (not sound), i.e. every time it says "cast impossible",
|
||||||
// it must be really impossible
|
// it must be really impossible
|
||||||
public static boolean isCastPossible(
|
@JvmStatic
|
||||||
@NotNull KotlinType lhsType,
|
fun isCastPossible(
|
||||||
@NotNull KotlinType rhsType,
|
lhsType: KotlinType,
|
||||||
@NotNull PlatformToKotlinClassMap platformToKotlinClassMap
|
rhsType: KotlinType,
|
||||||
) {
|
platformToKotlinClassMap: PlatformToKotlinClassMap): Boolean {
|
||||||
if (KotlinBuiltIns.isNullableNothing(lhsType) && !TypeUtils.isNullableType(rhsType)) return false;
|
if (KotlinBuiltIns.isNullableNothing(lhsType) && !TypeUtils.isNullableType(rhsType)) return false
|
||||||
if (isRelated(lhsType, rhsType, platformToKotlinClassMap)) return true;
|
if (isRelated(lhsType, rhsType, platformToKotlinClassMap)) return true
|
||||||
// This is an oversimplification (which does not render the method incomplete):
|
// This is an oversimplification (which does not render the method incomplete):
|
||||||
// we consider any type parameter capable of taking any value, which may be made more precise if we considered bounds
|
// we consider any type parameter capable of taking any value, which may be made more precise if we considered bounds
|
||||||
if (TypeUtils.isTypeParameter(lhsType) || TypeUtils.isTypeParameter(rhsType)) return true;
|
if (TypeUtils.isTypeParameter(lhsType) || TypeUtils.isTypeParameter(rhsType)) return true
|
||||||
if (isFinal(lhsType) || isFinal(rhsType)) return false;
|
if (isFinal(lhsType) || isFinal(rhsType)) return false
|
||||||
if (isTrait(lhsType) || isTrait(rhsType)) return true;
|
if (isTrait(lhsType) || isTrait(rhsType)) return true
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Two types are related, roughly, when one is a subtype or supertype of the other.
|
* Two types are related, roughly, when one is a subtype or supertype of the other.
|
||||||
* <p/>
|
*
|
||||||
|
*
|
||||||
* Note that some types have platform-specific counterparts, i.e. kotlin.String is mapped to java.lang.String,
|
* Note that some types have platform-specific counterparts, i.e. kotlin.String is mapped to java.lang.String,
|
||||||
* such types (and all their sub- and supertypes) are related too.
|
* such types (and all their sub- and supertypes) are related too.
|
||||||
* <p/>
|
*
|
||||||
|
*
|
||||||
* Due to limitations in PlatformToKotlinClassMap, we only consider mapping of platform classes to Kotlin classed
|
* Due to limitations in PlatformToKotlinClassMap, we only consider mapping of platform classes to Kotlin classed
|
||||||
* (i.e. java.lang.String -> kotlin.String) and ignore mappings that go the other way.
|
* (i.e. java.lang.String -> kotlin.String) and ignore mappings that go the other way.
|
||||||
*/
|
*/
|
||||||
private static boolean isRelated(@NotNull KotlinType a, @NotNull KotlinType b, @NotNull PlatformToKotlinClassMap platformToKotlinClassMap) {
|
private fun isRelated(a: KotlinType, b: KotlinType, platformToKotlinClassMap: PlatformToKotlinClassMap): Boolean {
|
||||||
List<KotlinType> aTypes = mapToPlatformIndependentTypes(TypeUtils.makeNotNullable(a), platformToKotlinClassMap);
|
val aTypes = mapToPlatformIndependentTypes(TypeUtils.makeNotNullable(a), platformToKotlinClassMap)
|
||||||
List<KotlinType> bTypes = mapToPlatformIndependentTypes(TypeUtils.makeNotNullable(b), platformToKotlinClassMap);
|
val bTypes = mapToPlatformIndependentTypes(TypeUtils.makeNotNullable(b), platformToKotlinClassMap)
|
||||||
|
|
||||||
for (KotlinType aType : aTypes) {
|
for (aType in aTypes) {
|
||||||
for (KotlinType bType : bTypes) {
|
for (bType in bTypes) {
|
||||||
if (KotlinTypeChecker.DEFAULT.isSubtypeOf(aType, bType)) return true;
|
if (KotlinTypeChecker.DEFAULT.isSubtypeOf(aType, bType)) return true
|
||||||
if (KotlinTypeChecker.DEFAULT.isSubtypeOf(bType, aType)) return true;
|
if (KotlinTypeChecker.DEFAULT.isSubtypeOf(bType, aType)) return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<KotlinType> mapToPlatformIndependentTypes(
|
private fun mapToPlatformIndependentTypes(
|
||||||
@NotNull KotlinType type,
|
type: KotlinType,
|
||||||
@NotNull PlatformToKotlinClassMap platformToKotlinClassMap
|
platformToKotlinClassMap: PlatformToKotlinClassMap): List<KotlinType> {
|
||||||
) {
|
val descriptor = type.constructor.declarationDescriptor
|
||||||
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
|
if (descriptor !is ClassDescriptor) return listOf(type)
|
||||||
if (!(descriptor instanceof ClassDescriptor)) return Collections.singletonList(type);
|
|
||||||
|
|
||||||
ClassDescriptor originalClass = (ClassDescriptor) descriptor;
|
val kotlinClasses = platformToKotlinClassMap.mapPlatformClass(descriptor)
|
||||||
Collection<ClassDescriptor> kotlinClasses = platformToKotlinClassMap.mapPlatformClass(originalClass);
|
if (kotlinClasses.isEmpty()) return listOf(type)
|
||||||
if (kotlinClasses.isEmpty()) return Collections.singletonList(type);
|
|
||||||
|
|
||||||
List<KotlinType> result = Lists.newArrayListWithCapacity(2);
|
val result = Lists.newArrayListWithCapacity<KotlinType>(2)
|
||||||
result.add(type);
|
result.add(type)
|
||||||
for (ClassDescriptor classDescriptor : kotlinClasses) {
|
for (classDescriptor in kotlinClasses) {
|
||||||
KotlinType kotlinType = TypeUtils.substituteProjectionsForParameters(classDescriptor, type.getArguments());
|
val kotlinType = TypeUtils.substituteProjectionsForParameters(classDescriptor, type.arguments)
|
||||||
result.add(kotlinType);
|
result.add(kotlinType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isFinal(@NotNull KotlinType type) {
|
private fun isFinal(type: KotlinType) = !TypeUtils.canHaveSubtypes(KotlinTypeChecker.DEFAULT, type)
|
||||||
return !TypeUtils.canHaveSubtypes(KotlinTypeChecker.DEFAULT, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isTrait(@NotNull KotlinType type) {
|
private fun isTrait(type: KotlinType) =
|
||||||
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
|
type.constructor.declarationDescriptor.let { it is ClassDescriptor && it.kind == ClassKind.INTERFACE }
|
||||||
return descriptor instanceof ClassDescriptor && ((ClassDescriptor) descriptor).getKind() == ClassKind.INTERFACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if cast from supertype to subtype is erased.
|
* Check if cast from supertype to subtype is erased.
|
||||||
* It is an error in "is" statement and warning in "as".
|
* It is an error in "is" statement and warning in "as".
|
||||||
*/
|
*/
|
||||||
public static boolean isCastErased(@NotNull KotlinType supertype, @NotNull KotlinType subtype, @NotNull KotlinTypeChecker typeChecker) {
|
@JvmStatic
|
||||||
|
fun isCastErased(supertype: KotlinType, subtype: KotlinType, typeChecker: KotlinTypeChecker): Boolean {
|
||||||
// cast between T and T? is always OK
|
// cast between T and T? is always OK
|
||||||
if (supertype.isMarkedNullable() || subtype.isMarkedNullable()) {
|
if (supertype.isMarkedNullable || subtype.isMarkedNullable) {
|
||||||
return isCastErased(TypeUtils.makeNotNullable(supertype), TypeUtils.makeNotNullable(subtype), typeChecker);
|
return isCastErased(TypeUtils.makeNotNullable(supertype), TypeUtils.makeNotNullable(subtype), typeChecker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it is a upcast, it's never erased
|
// if it is a upcast, it's never erased
|
||||||
if (typeChecker.isSubtypeOf(supertype, subtype)) return false;
|
if (typeChecker.isSubtypeOf(supertype, subtype)) return false
|
||||||
|
|
||||||
// downcasting to a non-reified type parameter is always erased
|
// downcasting to a non-reified type parameter is always erased
|
||||||
if (TypeUtils.isNonReifiedTypeParameter(subtype)) return true;
|
if (TypeUtils.isNonReifiedTypeParameter(subtype)) return true
|
||||||
|
|
||||||
// Check that we are actually casting to a generic type
|
// Check that we are actually casting to a generic type
|
||||||
// NOTE: this does not account for 'as Array<List<T>>'
|
// NOTE: this does not account for 'as Array<List<T>>'
|
||||||
if (allParametersReified(subtype)) return false;
|
if (allParametersReified(subtype)) return false
|
||||||
|
|
||||||
KotlinType staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype.getConstructor()).getResultingType();
|
val staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype.constructor).resultingType ?: return true
|
||||||
|
|
||||||
// If the substitution failed, it means that the result is an impossible type, e.g. something like Out<in Foo>
|
// If the substitution failed, it means that the result is an impossible type, e.g. something like Out<in Foo>
|
||||||
// In this case, we can't guarantee anything, so the cast is considered to be erased
|
// In this case, we can't guarantee anything, so the cast is considered to be erased
|
||||||
if (staticallyKnownSubtype == null) return true;
|
|
||||||
|
|
||||||
// If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead.
|
// If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead.
|
||||||
// If not, it's wrong to use it
|
// If not, it's wrong to use it
|
||||||
return !typeChecker.isSubtypeOf(staticallyKnownSubtype, subtype);
|
return !typeChecker.isSubtypeOf(staticallyKnownSubtype, subtype)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remember that we are trying to cast something of type {@code supertype} to {@code subtype}.
|
* Remember that we are trying to cast something of type `supertype` to `subtype`.
|
||||||
*
|
|
||||||
* Since at runtime we can only check the class (type constructor), the rest of the subtype should be known statically, from supertype.
|
* Since at runtime we can only check the class (type constructor), the rest of the subtype should be known statically, from supertype.
|
||||||
* This method reconstructs all static information that can be obtained from supertype.
|
* This method reconstructs all static information that can be obtained from supertype.
|
||||||
*
|
|
||||||
* Example 1:
|
* Example 1:
|
||||||
* supertype = Collection<String>
|
* supertype = Collection
|
||||||
* subtype = List<...>
|
* subtype = List<...>
|
||||||
* result = List<String>, all arguments are inferred
|
* result = List, all arguments are inferred
|
||||||
*
|
|
||||||
* Example 2:
|
* Example 2:
|
||||||
* supertype = Any
|
* supertype = Any
|
||||||
* subtype = List<...>
|
* subtype = List<...>
|
||||||
* result = List<*>, some arguments were not inferred, replaced with '*'
|
* result = List<*>, some arguments were not inferred, replaced with '*'
|
||||||
*/
|
*/
|
||||||
public static TypeReconstructionResult findStaticallyKnownSubtype(@NotNull KotlinType supertype, @NotNull TypeConstructor subtypeConstructor) {
|
@JvmStatic
|
||||||
assert !supertype.isMarkedNullable() : "This method only makes sense for non-nullable types";
|
fun findStaticallyKnownSubtype(supertype: KotlinType, subtypeConstructor: TypeConstructor): TypeReconstructionResult {
|
||||||
|
assert(!supertype.isMarkedNullable) { "This method only makes sense for non-nullable types" }
|
||||||
|
|
||||||
// Assume we are casting an expression of type Collection<Foo> to List<Bar>
|
// Assume we are casting an expression of type Collection<Foo> to List<Bar>
|
||||||
// First, let's make List<T>, where T is a type variable
|
// First, let's make List<T>, where T is a type variable
|
||||||
ClassifierDescriptor descriptor = subtypeConstructor.getDeclarationDescriptor();
|
val descriptor = subtypeConstructor.declarationDescriptor ?: error("Can't create default type for " + subtypeConstructor)
|
||||||
assert descriptor != null : "Can't create default type for " + subtypeConstructor;
|
val subtypeWithVariables = descriptor.defaultType
|
||||||
KotlinType subtypeWithVariables = descriptor.getDefaultType();
|
|
||||||
|
|
||||||
// Now, let's find a supertype of List<T> that is a Collection of something,
|
// Now, let's find a supertype of List<T> that is a Collection of something,
|
||||||
// in this case it will be Collection<T>
|
// in this case it will be Collection<T>
|
||||||
KotlinType supertypeWithVariables = TypeCheckingProcedure.findCorrespondingSupertype(subtypeWithVariables, supertype);
|
val supertypeWithVariables = TypeCheckingProcedure.findCorrespondingSupertype(subtypeWithVariables, supertype)
|
||||||
|
|
||||||
List<TypeParameterDescriptor> variables = subtypeWithVariables.getConstructor().getParameters();
|
val variables = subtypeWithVariables.constructor.parameters
|
||||||
Set<TypeConstructor> variableConstructors = CollectionsKt.toSet(
|
val variableConstructors = variables.map { descriptor -> descriptor.typeConstructor }.toSet()
|
||||||
CollectionsKt.map(variables, new Function1<TypeParameterDescriptor, TypeConstructor>() {
|
|
||||||
@Override
|
|
||||||
public TypeConstructor invoke(TypeParameterDescriptor descriptor) {
|
|
||||||
return descriptor.getTypeConstructor();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
Map<TypeConstructor, TypeProjection> substitution;
|
val substitution: MutableMap<TypeConstructor, TypeProjection>
|
||||||
if (supertypeWithVariables != null) {
|
if (supertypeWithVariables != null) {
|
||||||
// Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo
|
// Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo
|
||||||
TypeUnifier.UnificationResult solution = TypeUnifier.unify(
|
val solution = TypeUnifier.unify(
|
||||||
new TypeProjectionImpl(supertype), new TypeProjectionImpl(supertypeWithVariables),
|
TypeProjectionImpl(supertype), TypeProjectionImpl(supertypeWithVariables),
|
||||||
Predicates.in(variableConstructors));
|
Predicates.`in`(variableConstructors))
|
||||||
substitution = Maps.newHashMap(solution.getSubstitution());
|
substitution = Maps.newHashMap(solution.substitution)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// If there's no corresponding supertype, no variables are determined
|
// If there's no corresponding supertype, no variables are determined
|
||||||
// This may be OK, e.g. in case 'Any as List<*>'
|
// This may be OK, e.g. in case 'Any as List<*>'
|
||||||
substitution = Maps.newHashMapWithExpectedSize(variables.size());
|
substitution = Maps.newHashMapWithExpectedSize<TypeConstructor, TypeProjection>(variables.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If some of the parameters are not determined by unification, it means that these parameters are lost,
|
// If some of the parameters are not determined by unification, it means that these parameters are lost,
|
||||||
// let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
|
// let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
|
||||||
boolean allArgumentsInferred = true;
|
var allArgumentsInferred = true
|
||||||
for (TypeParameterDescriptor variable : variables) {
|
for (variable in variables) {
|
||||||
TypeProjection value = substitution.get(variable.getTypeConstructor());
|
val value = substitution[variable.typeConstructor]
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
substitution.put(
|
substitution.put(
|
||||||
variable.getTypeConstructor(),
|
variable.typeConstructor,
|
||||||
TypeUtils.makeStarProjection(variable)
|
TypeUtils.makeStarProjection(variable))
|
||||||
);
|
allArgumentsInferred = false
|
||||||
allArgumentsInferred = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we have values for all type parameters of List
|
// At this point we have values for all type parameters of List
|
||||||
// Let's make a type by substituting them: List<T> -> List<Foo>
|
// Let's make a type by substituting them: List<T> -> List<Foo>
|
||||||
KotlinType substituted = TypeSubstitutor.create(substitution).substitute(subtypeWithVariables, Variance.INVARIANT);
|
val substituted = TypeSubstitutor.create(substitution).substitute(subtypeWithVariables, Variance.INVARIANT)
|
||||||
|
|
||||||
return new TypeReconstructionResult(substituted, allArgumentsInferred);
|
return TypeReconstructionResult(substituted, allArgumentsInferred)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean allParametersReified(KotlinType subtype) {
|
private fun allParametersReified(subtype: KotlinType) = subtype.constructor.parameters.all { it.isReified }
|
||||||
for (TypeParameterDescriptor parameterDescriptor : subtype.getConstructor().getParameters()) {
|
|
||||||
if (!parameterDescriptor.isReified()) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CastDiagnosticsUtil() {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user