From ddb67d6c9c53a49540046b80cd6ebce59fc597f9 Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Fri, 27 Nov 2015 11:49:36 +0300 Subject: [PATCH] Support JvmSuppressWildcards and JvmWildcard annotations #KT-9898 Fixed --- .../kotlin/codegen/state/JetTypeMapper.java | 23 ++++++++-- .../kotlin/codegen/state/TypeMappingMode.kt | 10 +++++ .../kotlin/codegen/state/typeMappingUtil.kt | 44 +++++++++++++++++++ .../allWildcardsOnClass/JavaClass.java | 23 ++++++++++ .../boxWithJava/allWildcardsOnClass/main.kt | 19 ++++++++ .../irrelevantImplMutableListKotlin.kt | 3 +- .../JavaClass.java | 23 ++++++++++ .../main.kt | 18 ++++++++ .../jvmWildcardAnnotations/onFunction.kt | 27 ++++++++++++ .../jvmWildcardAnnotations/onTypes.kt | 22 ++++++++++ .../jvmWildcardAnnotations/primitiveTypes.kt | 12 +++++ .../BlackBoxWithJavaCodegenTestGenerated.java | 12 +++++ .../compiler/WriteSignatureTestGenerated.java | 27 ++++++++++++ .../src/kotlin/jvm/JvmPlatformAnnotations.kt | 21 +++++++++ 14 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 compiler/testData/codegen/boxWithJava/allWildcardsOnClass/JavaClass.java create mode 100644 compiler/testData/codegen/boxWithJava/allWildcardsOnClass/main.kt create mode 100644 compiler/testData/codegen/boxWithJava/covariantOverrideWithDeclarationSiteProjection/JavaClass.java create mode 100644 compiler/testData/codegen/boxWithJava/covariantOverrideWithDeclarationSiteProjection/main.kt create mode 100644 compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onFunction.kt create mode 100644 compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onTypes.kt create mode 100644 compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/primitiveTypes.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java index e367c486c60..263f174d94e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/JetTypeMapper.java @@ -326,9 +326,16 @@ public class JetTypeMapper { return mapType(descriptor.getReturnType(), sw, TypeMappingMode.GENERIC_TYPE); } else { + boolean isAnnotationMethod = DescriptorUtils.isAnnotationClass(descriptor.getContainingDeclaration()); + TypeMappingMode typeMappingModeFromAnnotation = + TypeMappingUtil.extractTypeMappingModeFromAnnotation(descriptor, returnType, isAnnotationMethod); + if (typeMappingModeFromAnnotation != null) { + return mapType(returnType, sw, typeMappingModeFromAnnotation); + } + TypeMappingMode mappingMode = TypeMappingMode.getOptimalModeForReturnType( returnType, - /* isAnnotationMethod = */ DescriptorUtils.isAnnotationClass(descriptor.getContainingDeclaration())); + /* isAnnotationMethod = */ isAnnotationMethod); return mapType(returnType, sw, mappingMode); } @@ -631,11 +638,13 @@ public class JetTypeMapper { signatureVisitor.writeUnboundedWildcard(); } else { - Variance projectionKind = getVarianceForWildcard(parameter, argument, mode); + TypeMappingMode argumentMode = TypeMappingUtil.updateArgumentModeFromAnnotations(mode, argument.getType()); + Variance projectionKind = getVarianceForWildcard(parameter, argument, argumentMode); + signatureVisitor.writeTypeArgument(projectionKind); mapType(argument.getType(), signatureVisitor, - mode.toGenericArgumentMode( + argumentMode.toGenericArgumentMode( TypeMappingUtil.getEffectiveVariance(parameter.getVariance(), argument.getProjectionKind()))); signatureVisitor.writeTypeArgumentEnd(); @@ -1204,7 +1213,13 @@ public class JetTypeMapper { TypeMappingMode typeMappingMode; - if (TypeMappingUtil.isMethodWithDeclarationSiteWildcards(callableDescriptor) && !type.getArguments().isEmpty()) { + TypeMappingMode typeMappingModeFromAnnotation = + TypeMappingUtil.extractTypeMappingModeFromAnnotation(callableDescriptor, type, /* isForAnnotationParameter = */ false); + + if (typeMappingModeFromAnnotation != null) { + typeMappingMode = typeMappingModeFromAnnotation; + } + else if (TypeMappingUtil.isMethodWithDeclarationSiteWildcards(callableDescriptor) && !type.getArguments().isEmpty()) { typeMappingMode = TypeMappingMode.GENERIC_TYPE; // Render all wildcards } else { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/TypeMappingMode.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/TypeMappingMode.kt index b7c1974940b..6ca1d17d137 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/TypeMappingMode.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/TypeMappingMode.kt @@ -93,6 +93,16 @@ internal class TypeMappingMode private constructor( skipDeclarationSiteWildcardsIfPossible = true, genericContravariantArgumentMode = contravariantArgumentMode) } + + @JvmStatic + fun createWithConstantDeclarationSiteWildcardsMode( + skipDeclarationSiteWildcards: Boolean, + isForAnnotationParameter: Boolean, + fallbackMode: TypeMappingMode? = null + ) = TypeMappingMode( + isForAnnotationParameter = isForAnnotationParameter, + skipDeclarationSiteWildcards = skipDeclarationSiteWildcards, + genericArgumentMode = fallbackMode) } fun toGenericArgumentMode(effectiveVariance: Variance): TypeMappingMode = diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/typeMappingUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/typeMappingUtil.kt index 09a0974ae16..f9cf525b2ad 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/typeMappingUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/typeMappingUtil.kt @@ -19,9 +19,11 @@ package org.jetbrains.kotlin.codegen.state import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.descriptorUtil.firstOverridden import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull +import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.descriptorUtil.propertyIfAccessor import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.Variance @@ -83,3 +85,45 @@ private val METHODS_WITH_DECLARATION_SITE_WILDCARDS = setOf( FqName("kotlin.MutableList.addAll"), FqName("kotlin.MutableMap.putAll") ) + +internal fun TypeMappingMode.updateArgumentModeFromAnnotations(type: KotlinType): TypeMappingMode { + type.suppressWildcardsMode()?.let { + return TypeMappingMode.createWithConstantDeclarationSiteWildcardsMode( + skipDeclarationSiteWildcards = it, isForAnnotationParameter = isForAnnotationParameter) + } + + if (type.annotations.hasAnnotation(JVM_WILDCARD_ANNOTATION_FQ_NAME)) { + return TypeMappingMode.createWithConstantDeclarationSiteWildcardsMode( + skipDeclarationSiteWildcards = false, isForAnnotationParameter = isForAnnotationParameter, fallbackMode = this) + } + + return this +} + +internal fun extractTypeMappingModeFromAnnotation( + callableDescriptor: CallableDescriptor, + outerType: KotlinType, + isForAnnotationParameter: Boolean +): TypeMappingMode? = + (outerType.suppressWildcardsMode() ?: callableDescriptor.suppressWildcardsMode())?.let { + if (outerType.arguments.isNotEmpty()) + TypeMappingMode.createWithConstantDeclarationSiteWildcardsMode( + skipDeclarationSiteWildcards = it, isForAnnotationParameter = isForAnnotationParameter) + else + TypeMappingMode.DEFAULT + } + +private fun DeclarationDescriptor.suppressWildcardsMode(): Boolean? = + parentsWithSelf.mapNotNull { + it.annotations.findAnnotation(JVM_SUPPRESS_WILDCARDS_ANNOTATION_FQ_NAME) + }.firstOrNull().suppressWildcardsMode() + +private fun KotlinType.suppressWildcardsMode(): Boolean? = + annotations.findAnnotation(JVM_SUPPRESS_WILDCARDS_ANNOTATION_FQ_NAME).suppressWildcardsMode() + +private fun AnnotationDescriptor?.suppressWildcardsMode(): Boolean? { + return (this ?: return null).allValueArguments.values.firstOrNull()?.value as? Boolean ?: true +} + +private val JVM_SUPPRESS_WILDCARDS_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmSuppressWildcards") +private val JVM_WILDCARD_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmWildcard") diff --git a/compiler/testData/codegen/boxWithJava/allWildcardsOnClass/JavaClass.java b/compiler/testData/codegen/boxWithJava/allWildcardsOnClass/JavaClass.java new file mode 100644 index 00000000000..89eadf58002 --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/allWildcardsOnClass/JavaClass.java @@ -0,0 +1,23 @@ +public class JavaClass { + + public static class C extends B { + public OutPair foo() { + return super.foo(); + } + + public In bar() { + return super.bar(); + } + } + + public static String test() { + A a = new C(); + + if (!a.foo().getX().equals("OK")) return "fail 1"; + if (!a.foo().getY().equals(123)) return "fail 2"; + + if (!a.bar().make("123").equals("123")) return "fail 3"; + + return "OK"; + } +} diff --git a/compiler/testData/codegen/boxWithJava/allWildcardsOnClass/main.kt b/compiler/testData/codegen/boxWithJava/allWildcardsOnClass/main.kt new file mode 100644 index 00000000000..e1ad0f82402 --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/allWildcardsOnClass/main.kt @@ -0,0 +1,19 @@ +class OutPair(val x: X, val y: Y) +class In { + fun make(x: Z): String = x.toString() +} + +@JvmSuppressWildcards(suppress = false) +interface A { + fun foo(): OutPair + fun bar(): In +} + +abstract class B : A { + override fun foo(): OutPair = OutPair("OK", 123) + override fun bar(): In = In() +} + +fun box(): String { + return JavaClass.test(); +} diff --git a/compiler/testData/codegen/boxWithJava/collections/irrelevantImplMutableListKotlin/irrelevantImplMutableListKotlin.kt b/compiler/testData/codegen/boxWithJava/collections/irrelevantImplMutableListKotlin/irrelevantImplMutableListKotlin.kt index d9bf259991a..5ec84c5c6be 100644 --- a/compiler/testData/codegen/boxWithJava/collections/irrelevantImplMutableListKotlin/irrelevantImplMutableListKotlin.kt +++ b/compiler/testData/codegen/boxWithJava/collections/irrelevantImplMutableListKotlin/irrelevantImplMutableListKotlin.kt @@ -7,11 +7,12 @@ public abstract class AImpl { throw UnsupportedOperationException() } + @JvmSuppressWildcards(suppress = false) fun addAll(elements: Collection): Boolean { throw UnsupportedOperationException() } - fun addAll(index: Int, elements: Collection): Boolean { + fun addAll(index: Int, elements: Collection<@JvmWildcard String>): Boolean { throw UnsupportedOperationException() } diff --git a/compiler/testData/codegen/boxWithJava/covariantOverrideWithDeclarationSiteProjection/JavaClass.java b/compiler/testData/codegen/boxWithJava/covariantOverrideWithDeclarationSiteProjection/JavaClass.java new file mode 100644 index 00000000000..89eadf58002 --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/covariantOverrideWithDeclarationSiteProjection/JavaClass.java @@ -0,0 +1,23 @@ +public class JavaClass { + + public static class C extends B { + public OutPair foo() { + return super.foo(); + } + + public In bar() { + return super.bar(); + } + } + + public static String test() { + A a = new C(); + + if (!a.foo().getX().equals("OK")) return "fail 1"; + if (!a.foo().getY().equals(123)) return "fail 2"; + + if (!a.bar().make("123").equals("123")) return "fail 3"; + + return "OK"; + } +} diff --git a/compiler/testData/codegen/boxWithJava/covariantOverrideWithDeclarationSiteProjection/main.kt b/compiler/testData/codegen/boxWithJava/covariantOverrideWithDeclarationSiteProjection/main.kt new file mode 100644 index 00000000000..0f77d0fb2f9 --- /dev/null +++ b/compiler/testData/codegen/boxWithJava/covariantOverrideWithDeclarationSiteProjection/main.kt @@ -0,0 +1,18 @@ +class OutPair(val x: X, val y: Y) +class In { + fun make(x: Z): String = x.toString() +} + +interface A { + fun foo(): OutPair<@JvmWildcard CharSequence, @JvmSuppressWildcards(false) Number> + fun bar(): In<@JvmWildcard String> +} + +abstract class B : A { + override fun foo(): OutPair = OutPair("OK", 123) + override fun bar(): In = In() +} + +fun box(): String { + return JavaClass.test(); +} diff --git a/compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onFunction.kt b/compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onFunction.kt new file mode 100644 index 00000000000..97afceab96f --- /dev/null +++ b/compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onFunction.kt @@ -0,0 +1,27 @@ +class Out +class In +open class Open + +@JvmSuppressWildcards(true) +fun deepOpen(x: Out>>) {} +// method: OnFunctionKt::deepOpen +// generic signature: (LOut;>;>;)V + +interface A { + @JvmSuppressWildcards(true) + fun foo(): Out +} +// method: A::foo +// generic signature: ()LOut; + +interface B { + @JvmSuppressWildcards(true) + fun foo(): In +} +// method: B::foo +// generic signature: ()LIn; + +@JvmSuppressWildcards(false) +fun bar(): Out = null!! +// method: OnFunctionKt::bar +// generic signature: ()LOut<+LOpen;>; diff --git a/compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onTypes.kt b/compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onTypes.kt new file mode 100644 index 00000000000..3e44ae24e9d --- /dev/null +++ b/compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onTypes.kt @@ -0,0 +1,22 @@ +class OutPair +class Out +class In + +class X + +fun simpleOut(x: Out<@JvmWildcard X>) {} +// method: OnTypesKt::simpleOut +// generic signature: (LOut<+LX;>;)V + +fun simpleIn(x: In<@JvmWildcard Any?>) {} +// method: OnTypesKt::simpleIn +// generic signature: (LIn<-Ljava/lang/Object;>;)V + +fun falseTrueFalse(): @JvmSuppressWildcards(false) OutPair, Out<@JvmSuppressWildcards(false) X>>> = null!! +// method: OnTypesKt::falseTrueFalse +// generic signature: ()LOutPair<+LX;LOutPair;LOut<+LX;>;>;>; + +open class Open +fun combination(): @JvmSuppressWildcards OutPair< Open, @JvmWildcard OutPair>> = null!! +// method: OnTypesKt::combination +// generic signature: ()LOutPair;>;>; diff --git a/compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/primitiveTypes.kt b/compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/primitiveTypes.kt new file mode 100644 index 00000000000..15f631a9667 --- /dev/null +++ b/compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/primitiveTypes.kt @@ -0,0 +1,12 @@ +class Out +class In + +@JvmSuppressWildcards(false) +fun foo(x: Boolean, y: Out): Int = 1 +// method: PrimitiveTypesKt::foo +// generic signature: (ZLOut<+Ljava/lang/Integer;>;)I + +@JvmSuppressWildcards(true) +fun bar(x: Boolean, y: In, z: @JvmSuppressWildcards(false) Long): Int = 1 +// method: PrimitiveTypesKt::bar +// generic signature: (ZLIn;J)I diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java index eca9efca2a9..1ff9cf9bd0e 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithJavaCodegenTestGenerated.java @@ -35,6 +35,12 @@ public class BlackBoxWithJavaCodegenTestGenerated extends AbstractBlackBoxCodege KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithJava"), Pattern.compile("^([^\\.]+)$"), true); } + @TestMetadata("allWildcardsOnClass") + public void testAllWildcardsOnClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/allWildcardsOnClass/"); + doTestWithJava(fileName); + } + @TestMetadata("annotatedSamFunExpression") public void testAnnotatedSamFunExpression() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/annotatedSamFunExpression/"); @@ -59,6 +65,12 @@ public class BlackBoxWithJavaCodegenTestGenerated extends AbstractBlackBoxCodege doTestWithJava(fileName); } + @TestMetadata("covariantOverrideWithDeclarationSiteProjection") + public void testCovariantOverrideWithDeclarationSiteProjection() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/covariantOverrideWithDeclarationSiteProjection/"); + doTestWithJava(fileName); + } + @TestMetadata("deprecatedFieldForObject") public void testDeprecatedFieldForObject() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithJava/deprecatedFieldForObject/"); diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java index d177a9cd4cd..36d829d6296 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java @@ -373,6 +373,33 @@ public class WriteSignatureTestGenerated extends AbstractWriteSignatureTest { doTest(fileName); } + @TestMetadata("compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JvmWildcardAnnotations extends AbstractWriteSignatureTest { + public void testAllFilesPresentInJvmWildcardAnnotations() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("onFunction.kt") + public void testOnFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onFunction.kt"); + doTest(fileName); + } + + @TestMetadata("onTypes.kt") + public void testOnTypes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/onTypes.kt"); + doTest(fileName); + } + + @TestMetadata("primitiveTypes.kt") + public void testPrimitiveTypes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/writeSignature/declarationSiteVariance/jvmWildcardAnnotations/primitiveTypes.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/writeSignature/declarationSiteVariance/wildcardOptimization") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/libraries/stdlib/src/kotlin/jvm/JvmPlatformAnnotations.kt b/libraries/stdlib/src/kotlin/jvm/JvmPlatformAnnotations.kt index b83424f6991..163d44a81d8 100644 --- a/libraries/stdlib/src/kotlin/jvm/JvmPlatformAnnotations.kt +++ b/libraries/stdlib/src/kotlin/jvm/JvmPlatformAnnotations.kt @@ -99,3 +99,24 @@ internal annotation class JvmVersion(public val minimum: Int = 6, public val max @MustBeDocumented public annotation class JvmField +/** + * Instructs compiler to generate or omit wildcards for type arguments corresponding to parameters with declaration-site variance. + * If the innermost applied @JvmSuppressWildcards has suppress=true, the type is generated as without wildcards. + * If the innermost applied @JvmSuppressWildcards has suppress=false, the type is generated as with wildcards. + * + * It may be helpful only if declaration seems to be inconvenient to use from Java. + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.TYPE) +@Retention(AnnotationRetention.BINARY) +@MustBeDocumented +annotation class JvmSuppressWildcards(val suppress: Boolean = true) + +/** + * Instructs compiler to generate wildcard for annotated type arguments corresponding to parameters with declaration-site variance. + * + * It may be helpful only if declaration seems to be inconvenient to use from Java without wildcard. + */ +@Target(AnnotationTarget.TYPE) +@Retention(AnnotationRetention.BINARY) +@MustBeDocumented +annotation class JvmWildcard