/** * Check if cast from supertype to subtype is erased. It is an error in "is" statement and warning * in "as". */ public static boolean isCastErased( @NotNull KotlinType supertype, @NotNull KotlinType subtype, @NotNull KotlinTypeChecker typeChecker) { // cast between T and T? is always OK if (supertype.isMarkedNullable() || subtype.isMarkedNullable()) { return isCastErased( TypeUtils.makeNotNullable(supertype), TypeUtils.makeNotNullable(subtype), typeChecker); } // if it is a upcast, it's never erased if (typeChecker.isSubtypeOf(supertype, subtype)) return false; // downcasting to a non-reified type parameter is always erased if (TypeUtils.isNonReifiedTypeParemeter(subtype)) return true; // Check that we are actually casting to a generic type // NOTE: this does not account for 'as Array<List<T>>' if (allParametersReified(subtype)) return false; KotlinType staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype.getConstructor()).getResultingType(); // 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 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 not, it's wrong to use it return !typeChecker.isSubtypeOf(staticallyKnownSubtype, subtype); }
/** * Remember that we are trying to cast something of type {@code supertype} to {@code subtype}. * * <p>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. * * <p>Example 1: supertype = Collection<String> subtype = List<...> result = List<String>, all * arguments are inferred * * <p>Example 2: supertype = Any subtype = List<...> result = List<*>, some arguments were not * inferred, replaced with '*' */ public static TypeReconstructionResult findStaticallyKnownSubtype( @NotNull KotlinType supertype, @NotNull TypeConstructor subtypeConstructor) { 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> // First, let's make List<T>, where T is a type variable ClassifierDescriptor descriptor = subtypeConstructor.getDeclarationDescriptor(); assert descriptor != null : "Can't create default type for " + subtypeConstructor; KotlinType subtypeWithVariables = descriptor.getDefaultType(); // Now, let's find a supertype of List<T> that is a Collection of something, // in this case it will be Collection<T> KotlinType supertypeWithVariables = TypeCheckingProcedure.findCorrespondingSupertype(subtypeWithVariables, supertype); final List<TypeParameterDescriptor> variables = subtypeWithVariables.getConstructor().getParameters(); Map<TypeConstructor, TypeProjection> substitution; if (supertypeWithVariables != null) { // Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo TypeUnifier.UnificationResult solution = TypeUnifier.unify( new TypeProjectionImpl(supertype), new TypeProjectionImpl(supertypeWithVariables), new Predicate<TypeConstructor>() { @Override public boolean apply(TypeConstructor typeConstructor) { ClassifierDescriptor descriptor = typeConstructor.getDeclarationDescriptor(); return descriptor instanceof TypeParameterDescriptor && variables.contains(descriptor); } }); substitution = Maps.newHashMap(solution.getSubstitution()); } else { // If there's no corresponding supertype, no variables are determined // This may be OK, e.g. in case 'Any as List<*>' substitution = Maps.newHashMapWithExpectedSize(variables.size()); } // 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<*> boolean allArgumentsInferred = true; for (TypeParameterDescriptor variable : variables) { TypeProjection value = substitution.get(variable.getTypeConstructor()); if (value == null) { substitution.put(variable.getTypeConstructor(), TypeUtils.makeStarProjection(variable)); allArgumentsInferred = false; } } // At this point we have values for all type parameters of List // Let's make a type by substituting them: List<T> -> List<Foo> KotlinType substituted = TypeSubstitutor.create(substitution).substitute(subtypeWithVariables, Variance.INVARIANT); return new TypeReconstructionResult(substituted, allArgumentsInferred); }
/** * Differs from `isNullableType` only by treating type parameters: acceptsNullable(T) <=> T has * nullable lower bound Semantics should be the same as `isSubtype(Nothing?, T)` * * @return true if `null` can be assigned to storage of this type */ public static boolean acceptsNullable(@NotNull KotlinType type) { if (type.isMarkedNullable()) { return true; } if (FlexibleTypesKt.isFlexible(type) && acceptsNullable(FlexibleTypesKt.flexibility(type).getUpperBound())) { return true; } return false; }
@Nullable public static KotlinType createSubstitutedSupertype( @NotNull KotlinType subType, @NotNull KotlinType superType, @NotNull TypeSubstitutor substitutor) { KotlinType substitutedType = substitutor.substitute(superType, Variance.INVARIANT); if (substitutedType != null) { return makeNullableIfNeeded(substitutedType, subType.isMarkedNullable()); } return null; }
public static boolean hasNullableSuperType(@NotNull KotlinType type) { if (type.getConstructor().getDeclarationDescriptor() instanceof ClassDescriptor) { // A class/trait cannot have a nullable supertype return false; } for (KotlinType supertype : getImmediateSupertypes(type)) { if (supertype.isMarkedNullable()) return true; if (hasNullableSuperType(supertype)) return true; } return false; }
/** * A work-around of the generic nullability problem in the type checker Semantics should be the * same as `!isSubtype(T, Any)` * * @return true if a value of this type can be null */ public static boolean isNullableType(@NotNull KotlinType type) { if (type.isMarkedNullable()) { return true; } if (FlexibleTypesKt.isFlexible(type) && isNullableType(FlexibleTypesKt.flexibility(type).getUpperBound())) { return true; } if (isTypeParameter(type)) { return hasNullableSuperType(type); } return false; }
@NotNull public static KotlinType makeNullableAsSpecified(@NotNull KotlinType type, boolean nullable) { Flexibility flexibility = type.getCapability(Flexibility.class); if (flexibility != null) { return flexibility.makeNullableAsSpecified(nullable); } // Wrapping serves two purposes here // 1. It's requires less memory than copying with a changed nullability flag: a copy has many // fields, while a wrapper has only one // 2. It preserves laziness of types // Unwrap to avoid long delegation call chains if (type instanceof AbstractTypeWithKnownNullability) { return makeNullableAsSpecified(((AbstractTypeWithKnownNullability) type).delegate, nullable); } // checking to preserve laziness if (!(type instanceof LazyType) && type.isMarkedNullable() == nullable) { return type; } return nullable ? new NullableType(type) : new NotNullType(type); }
private static boolean isNotNullConstructedFromGivenClass( @NotNull KotlinType type, @NotNull FqNameUnsafe fqName) { return !type.isMarkedNullable() && isConstructedFromGivenClass(type, fqName); }
public static boolean isPrimitiveType(@NotNull KotlinType type) { ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor(); return !type.isMarkedNullable() && descriptor instanceof ClassDescriptor && isPrimitiveClass((ClassDescriptor) descriptor); }
public static boolean isNullableAny(@NotNull KotlinType type) { return isAnyOrNullableAny(type) && type.isMarkedNullable(); }
public static boolean isNullableNothing(@NotNull KotlinType type) { return isNothingOrNullableNothing(type) && type.isMarkedNullable(); }
public static boolean canHaveSubtypes(KotlinTypeChecker typeChecker, @NotNull KotlinType type) { if (type.isMarkedNullable()) { return true; } if (!type.getConstructor().isFinal()) { return true; } List<TypeParameterDescriptor> parameters = type.getConstructor().getParameters(); List<TypeProjection> arguments = type.getArguments(); for (int i = 0, parametersSize = parameters.size(); i < parametersSize; i++) { TypeParameterDescriptor parameterDescriptor = parameters.get(i); TypeProjection typeProjection = arguments.get(i); if (typeProjection.isStarProjection()) return true; Variance projectionKind = typeProjection.getProjectionKind(); KotlinType argument = typeProjection.getType(); switch (parameterDescriptor.getVariance()) { case INVARIANT: switch (projectionKind) { case INVARIANT: if (lowerThanBound(typeChecker, argument, parameterDescriptor) || canHaveSubtypes(typeChecker, argument)) { return true; } break; case IN_VARIANCE: if (lowerThanBound(typeChecker, argument, parameterDescriptor)) { return true; } break; case OUT_VARIANCE: if (canHaveSubtypes(typeChecker, argument)) { return true; } break; } break; case IN_VARIANCE: if (projectionKind != Variance.OUT_VARIANCE) { if (lowerThanBound(typeChecker, argument, parameterDescriptor)) { return true; } } else { if (canHaveSubtypes(typeChecker, argument)) { return true; } } break; case OUT_VARIANCE: if (projectionKind != Variance.IN_VARIANCE) { if (canHaveSubtypes(typeChecker, argument)) { return true; } } else { if (lowerThanBound(typeChecker, argument, parameterDescriptor)) { return true; } } break; } } return false; }