Change default rules for declaration-site wildcards

Mostly this commit is about skipping wildcards that are redundant in some sense.
The motivation is that they looks `long` in Java code.

There are basically two important parts: return types and value parameters.

1. For return types default behaviour is skipping all declaration-site wildcards.
The intuition behind this rule is simple: return types are basically used in subtype position
(as an argument for another call), and here everything works well in case of 'out'-variance.
For example we have 'Out<Out<T>>>' as subtype both for 'Out<Out<T>>>' and 'Out<? extends Out<? extends T>>>',
so values of such type is more flexible in contrast to `Out<? extends Out<? extends T>>>` that could be used only
for the second case.

But we have choosen to treat `in`-variance in a different way: argument itself
should be rendered without wildcard while nested arguments are rendered by the rules
described further (see second part).

For example: 'In<Out<OpenClass>>' will have generic signature 'In<Out<? extends OpenClass>>'.
If we omit all wildcards here, then value of type 'In<Out<OpenClass>>'
will be impossible to use as argument for function expecting 'In<? super Out<? extends Derived>>'
where Derived <: OpenClass (you can check it manually :]).

And this exception should not be very inconvinient because in-variance is rather rare.

2. For value parameters we decided to skip wildcards if it doesn't make obtained signature weaker
in a sense of set of acceptable arguments.

More precisely:
    a. We write wildcard for 'Out<T>' iff T ``can have subtypes ignoring nullability''
    b. We write wildcard for 'In<T>' iff T is not equal to it's class upper bound (ignoring nullability again)

Definition of ``can have subtypes ignoring nullability'' is straightforward and you can see it in commit.

 #KT-9801 Fixed
 #KT-9890 Fixed
This commit is contained in:
Denis Zharkov
2015-11-23 19:45:48 +03:00
parent 1731cb8b40
commit 406e31f54a
29 changed files with 620 additions and 60 deletions
@@ -325,12 +325,12 @@ public class JetTypeMapper {
//noinspection ConstantConditions
return mapType(descriptor.getReturnType(), sw, TypeMappingMode.GENERIC_TYPE);
}
else if (DescriptorUtils.isAnnotationClass(descriptor.getContainingDeclaration())) {
//noinspection ConstantConditions
return mapType(descriptor.getReturnType(), sw, TypeMappingMode.VALUE_FOR_ANNOTATION);
}
else {
return mapType(returnType, sw, TypeMappingMode.DEFAULT, Variance.OUT_VARIANCE);
TypeMappingMode mappingMode = TypeMappingMode.getOptimalModeForReturnType(
returnType,
/* isAnnotationMethod = */ DescriptorUtils.isAnnotationClass(descriptor.getContainingDeclaration()));
return mapType(returnType, sw, mappingMode);
}
}
@@ -379,23 +379,17 @@ public class JetTypeMapper {
return mapType(descriptor.getDefaultType());
}
@NotNull
private Type mapType(@NotNull KotlinType jetType, @Nullable BothSignatureWriter signatureVisitor, @NotNull TypeMappingMode mode) {
return mapType(jetType, signatureVisitor, mode, Variance.INVARIANT);
}
@NotNull
private Type mapType(
@NotNull KotlinType jetType,
@Nullable BothSignatureWriter signatureVisitor,
@NotNull TypeMappingMode mode,
@NotNull Variance howThisTypeIsUsed
@NotNull TypeMappingMode mode
) {
Type builtinType = mapBuiltinType(jetType);
if (builtinType != null) {
Type asmType = mode.getNeedPrimitiveBoxing() ? boxType(builtinType) : builtinType;
writeGenericType(jetType, asmType, signatureVisitor, howThisTypeIsUsed, mode);
writeGenericType(jetType, asmType, signatureVisitor, mode);
return asmType;
}
@@ -440,7 +434,7 @@ public class JetTypeMapper {
arrayElementType = boxType(mapType(memberType, mode));
if (signatureVisitor != null) {
signatureVisitor.writeArrayType();
mapType(memberType, signatureVisitor, mode.toGenericArgumentMode());
mapType(memberType, signatureVisitor, mode.toGenericArgumentMode(memberProjection.getProjectionKind()));
signatureVisitor.writeArrayEnd();
}
}
@@ -452,7 +446,7 @@ public class JetTypeMapper {
Type asmType = mode.isForAnnotationParameter() && KotlinBuiltIns.isKClass((ClassDescriptor) descriptor) ?
AsmTypes.JAVA_CLASS_TYPE :
computeAsmType((ClassDescriptor) descriptor.getOriginal());
writeGenericType(jetType, asmType, signatureVisitor, howThisTypeIsUsed, mode);
writeGenericType(jetType, asmType, signatureVisitor, mode);
return asmType;
}
@@ -573,7 +567,6 @@ public class JetTypeMapper {
@NotNull KotlinType type,
@NotNull Type asmType,
@Nullable BothSignatureWriter signatureVisitor,
@NotNull Variance howThisTypeIsUsed,
@NotNull TypeMappingMode mode
) {
if (signatureVisitor != null) {
@@ -600,17 +593,14 @@ public class JetTypeMapper {
writeGenericArguments(
signatureVisitor,
outermostInnerType.getArguments(), outermostClass.getDeclaredTypeParameters(),
howThisTypeIsUsed, mode);
outermostInnerType.getArguments(), outermostClass.getDeclaredTypeParameters(), mode);
for (PossiblyInnerType innerPart : innerTypesAsList.subList(1, innerTypesAsList.size())) {
ClassDescriptor classDescriptor = innerPart.getClassDescriptor();
signatureVisitor.writeInnerClass(getJvmShortName(classDescriptor));
writeGenericArguments(
signatureVisitor, innerPart.getArguments(),
classDescriptor.getDeclaredTypeParameters(),
howThisTypeIsUsed, mode
);
classDescriptor.getDeclaredTypeParameters(), mode);
}
signatureVisitor.writeClassEnd();
@@ -631,7 +621,6 @@ public class JetTypeMapper {
@NotNull BothSignatureWriter signatureVisitor,
@NotNull List<? extends TypeProjection> arguments,
@NotNull List<? extends TypeParameterDescriptor> parameters,
@NotNull Variance howThisTypeIsUsed,
@NotNull TypeMappingMode mode
) {
for (Pair<? extends TypeParameterDescriptor, ? extends TypeProjection> item : CollectionsKt.zip(parameters, arguments)) {
@@ -642,11 +631,13 @@ public class JetTypeMapper {
signatureVisitor.writeUnboundedWildcard();
}
else {
Variance projectionKind =
getEffectiveVariance(parameter.getVariance(), argument.getProjectionKind(), howThisTypeIsUsed, mode);
Variance projectionKind = getVarianceForWildcard(parameter, argument, mode);
signatureVisitor.writeTypeArgument(projectionKind);
mapType(argument.getType(), signatureVisitor, mode.toGenericArgumentMode());
mapType(argument.getType(), signatureVisitor,
mode.toGenericArgumentMode(
TypeMappingUtil.getEffectiveVariance(parameter.getVariance(), argument.getProjectionKind())));
signatureVisitor.writeTypeArgumentEnd();
}
}
@@ -670,21 +661,34 @@ public class JetTypeMapper {
});
}
private static Variance getEffectiveVariance(
Variance parameterVariance,
Variance projectionKind,
Variance howThisTypeIsUsed,
TypeMappingMode mode
@NotNull
private static Variance getVarianceForWildcard(
@NotNull TypeParameterDescriptor parameter,
@NotNull TypeProjection projection,
@NotNull TypeMappingMode mode
) {
if (!mode.getWriteDeclarationSiteProjections() && projectionKind == Variance.INVARIANT) return Variance.INVARIANT;
Variance projectionKind = projection.getProjectionKind();
// Return type must not contain wildcards
if (howThisTypeIsUsed == Variance.OUT_VARIANCE) return projectionKind;
if (mode.getSkipDeclarationSiteWildcards() && projectionKind == Variance.INVARIANT) {
return Variance.INVARIANT;
}
Variance parameterVariance = parameter.getVariance();
if (parameterVariance == Variance.INVARIANT) {
return projectionKind;
}
if (projectionKind == Variance.INVARIANT) {
if (mode.getSkipDeclarationSiteWildcardsIfPossible() && !projection.isStarProjection()) {
if (parameterVariance == Variance.OUT_VARIANCE && TypeMappingUtil.isMostPreciseCovariantArgument(projection.getType())){
return Variance.INVARIANT;
}
if (parameterVariance == Variance.IN_VARIANCE
&& TypeMappingUtil.isMostPreciseContravariantArgument(projection.getType(), parameter)) {
return Variance.INVARIANT;
}
}
return parameterVariance;
}
if (parameterVariance == projectionKind) {
@@ -1187,7 +1191,7 @@ public class JetTypeMapper {
private void writeParameter(@NotNull BothSignatureWriter sw, @NotNull JvmMethodParameterKind kind, @NotNull KotlinType type) {
sw.writeParameterType(kind);
mapType(type, sw, TypeMappingMode.DEFAULT);
mapType(type, sw, TypeMappingMode.getOptimalModeForValueParameter(type));
sw.writeParameterTypeEnd();
}
@@ -16,11 +16,17 @@
package org.jetbrains.kotlin.codegen.state
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Variance
internal class TypeMappingMode private constructor(
val needPrimitiveBoxing: Boolean = false,
val isForAnnotationParameter: Boolean = false,
val writeDeclarationSiteProjections: Boolean = true,
val genericArgumentMode: TypeMappingMode? = null
// Here DeclarationSiteWildcards means wildcard generated because of declaration-site variance
val skipDeclarationSiteWildcards: Boolean = false,
val skipDeclarationSiteWildcardsIfPossible: Boolean = false,
private val genericArgumentMode: TypeMappingMode? = null,
private val genericContravariantArgumentMode: TypeMappingMode? = genericArgumentMode
) {
companion object {
/**
@@ -35,13 +41,12 @@ internal class TypeMappingMode private constructor(
@JvmField
val DEFAULT = TypeMappingMode(genericArgumentMode = GENERIC_TYPE)
/**
* kotlin.Int is mapped to Ljava/lang/Integer;
* No projections allowed in immediate arguments
*/
@JvmField
val SUPER_TYPE = TypeMappingMode(needPrimitiveBoxing = true, writeDeclarationSiteProjections = false, genericArgumentMode = GENERIC_TYPE)
val SUPER_TYPE = TypeMappingMode(needPrimitiveBoxing = true, skipDeclarationSiteWildcards = true, genericArgumentMode = GENERIC_TYPE)
/**
* kotlin.reflect.KClass mapped to java.lang.Class
@@ -50,9 +55,49 @@ internal class TypeMappingMode private constructor(
@JvmField
val VALUE_FOR_ANNOTATION = TypeMappingMode(
isForAnnotationParameter = true,
genericArgumentMode = TypeMappingMode(isForAnnotationParameter = true, needPrimitiveBoxing = true))
genericArgumentMode = TypeMappingMode(isForAnnotationParameter = true, needPrimitiveBoxing = true, genericArgumentMode = GENERIC_TYPE))
@JvmStatic
fun getOptimalModeForValueParameter(
type: KotlinType
) = getOptimalModeForSignaturePart(type, isForAnnotationParameter = false, canBeUsedInSupertypePosition = true)
@JvmStatic
fun getOptimalModeForReturnType(
type: KotlinType,
isAnnotationMethod: Boolean
) = getOptimalModeForSignaturePart(type, isForAnnotationParameter = isAnnotationMethod, canBeUsedInSupertypePosition = false)
private fun getOptimalModeForSignaturePart(
type: KotlinType,
isForAnnotationParameter: Boolean,
canBeUsedInSupertypePosition: Boolean
): TypeMappingMode {
if (type.arguments.isEmpty()) return DEFAULT
val contravariantArgumentMode =
if (!canBeUsedInSupertypePosition)
TypeMappingMode(
needPrimitiveBoxing = true,
isForAnnotationParameter = isForAnnotationParameter,
skipDeclarationSiteWildcards = false,
skipDeclarationSiteWildcardsIfPossible = true)
else
null
return TypeMappingMode(
needPrimitiveBoxing = true,
isForAnnotationParameter = isForAnnotationParameter,
skipDeclarationSiteWildcards = !canBeUsedInSupertypePosition,
skipDeclarationSiteWildcardsIfPossible = true,
genericContravariantArgumentMode = contravariantArgumentMode)
}
}
fun toGenericArgumentMode(): TypeMappingMode = genericArgumentMode ?: this
fun toGenericArgumentMode(effectiveVariance: Variance): TypeMappingMode =
when (effectiveVariance) {
Variance.IN_VARIANCE -> genericContravariantArgumentMode ?: this
else -> genericArgumentMode ?: this
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* 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.
*/
@file:JvmName("TypeMappingUtil")
package org.jetbrains.kotlin.codegen.state
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Variance
fun KotlinType.isMostPreciseContravariantArgument(parameter: TypeParameterDescriptor): Boolean =
// TODO: probably class upper bound should be used
KotlinBuiltIns.isAnyOrNullableAny(this)
fun KotlinType.isMostPreciseCovariantArgument(): Boolean = !canHaveSubtypesIgnoringNullability()
private fun KotlinType.canHaveSubtypesIgnoringNullability(): Boolean {
val constructor = constructor
val descriptor = constructor.declarationDescriptor
when (descriptor) {
is TypeParameterDescriptor -> return true
is ClassDescriptor -> if (descriptor.modality.isOverridable) return true
}
for ((parameter, argument) in constructor.parameters.zip(arguments)) {
if (argument.isStarProjection) return true
val projectionKind = argument.projectionKind
val type = argument.type
val effectiveVariance = getEffectiveVariance(parameter.variance, projectionKind)
if (effectiveVariance == Variance.OUT_VARIANCE && !type.isMostPreciseCovariantArgument()) return true
if (effectiveVariance == Variance.IN_VARIANCE && !type.isMostPreciseContravariantArgument(parameter)) return true
}
return false
}
public fun getEffectiveVariance(parameterVariance: Variance, projectionKind: Variance): Variance {
if (parameterVariance === Variance.INVARIANT) {
return projectionKind
}
if (projectionKind === Variance.INVARIANT) {
return parameterVariance
}
if (parameterVariance === projectionKind) {
return parameterVariance
}
// In<out X> = In<*>
// Out<in X> = Out<*>
return Variance.OUT_VARIANCE
}