CastDiagnosticsUtil.kt: converted to Kotlin

This commit is contained in:
Mikhail Glukhikh
2016-06-07 18:58:49 +03:00
parent f3f7bff376
commit 99acbf16b2
@@ -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() {}
} }