diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java index 4439691cfcf..0385d709710 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java @@ -79,6 +79,7 @@ import java.util.Set; import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isNullableAny; import static org.jetbrains.kotlin.codegen.AsmUtil.*; import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.isAnnotationOrJvm6Interface; +import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.isJvm8Interface; import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.isJvm8InterfaceMember; import static org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings.METHOD_FOR_FUNCTION; import static org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DECLARATION; @@ -163,9 +164,7 @@ public class FunctionCodegen { OwnerKind contextKind = methodContext.getContextKind(); if (isInterface(functionDescriptor.getContainingDeclaration()) && functionDescriptor.getVisibility() == Visibilities.PRIVATE && - (isJvm8InterfaceMember(functionDescriptor, state) - ? contextKind == OwnerKind.DEFAULT_IMPLS - : contextKind != OwnerKind.DEFAULT_IMPLS)) { + !processInterfaceMember(functionDescriptor, contextKind, state)) { return; } @@ -199,7 +198,7 @@ public class FunctionCodegen { generateBridges(functionDescriptor); - if (isJvm8InterfaceMember(functionDescriptor, state) && contextKind != OwnerKind.DEFAULT_IMPLS) { + if (isJvm8InterfaceMember(functionDescriptor, state) && contextKind != OwnerKind.DEFAULT_IMPLS && state.getGenerateDefaultImplsForJvm8()) { generateDelegateForDefaultImpl(functionDescriptor, origin.getElement()); } @@ -771,7 +770,7 @@ public class FunctionCodegen { ) { DeclarationDescriptor contextClass = owner.getContextDescriptor().getContainingDeclaration(); - if (kind != OwnerKind.DEFAULT_IMPLS && isInterface(contextClass)) { + if (isInterface(contextClass) && !processInterface(contextClass, kind, state)) { return; } @@ -1157,4 +1156,21 @@ public class FunctionCodegen { } ); } + + public static boolean processInterfaceMember( + @NotNull CallableMemberDescriptor function, + @NotNull OwnerKind kind, + @NotNull GenerationState state + ) { + return processInterface(function.getContainingDeclaration(), kind, state); + } + + public static boolean processInterface( + @NotNull DeclarationDescriptor contextClass, + @NotNull OwnerKind kind, + @NotNull GenerationState state + ) { + assert isInterface(contextClass) : "'processInterface' method should be called only for interfaces, but: " + contextClass; + return isJvm8Interface(contextClass, state) ? kind != OwnerKind.DEFAULT_IMPLS : kind == OwnerKind.DEFAULT_IMPLS; + } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java index 7a72265cb8c..470c8f124cc 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java @@ -231,7 +231,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { @Override protected void generateDefaultImplsIfNeeded() { - if (isInterface(descriptor) && !isLocal) { + if (isInterface(descriptor) && !isLocal && (!isJvm8Interface(descriptor, state) || state.getGenerateDefaultImplsForJvm8())) { Type defaultImplsType = state.getTypeMapper().mapDefaultImpls(descriptor); ClassBuilder defaultImplsBuilder = state.getFactory().newVisitor(JvmDeclarationOriginKt.DefaultImpls(myClass, descriptor), defaultImplsType, myClass.getContainingFile()); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java index ed2f98cfcee..f32a1e750f2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java @@ -79,7 +79,7 @@ public class JvmCodegenUtil { return !state.isJvm8Target(); } - private static boolean isJvm8Interface(@NotNull DeclarationDescriptor descriptor, @NotNull GenerationState state) { + public static boolean isJvm8Interface(@NotNull DeclarationDescriptor descriptor, @NotNull GenerationState state) { return DescriptorUtils.isInterface(descriptor) && !isAnnotationOrJvm6Interface(descriptor, state); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java index 40e17f495cc..dba795b49bb 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java @@ -275,7 +275,8 @@ public class PropertyCodegen { DeclarationDescriptor contextDescriptor = context.getContextDescriptor(); if (!isInterface(contextDescriptor) || - (isJvm8Interface(contextDescriptor, state) ? kind != OwnerKind.DEFAULT_IMPLS : kind == OwnerKind.DEFAULT_IMPLS)) { + (FunctionCodegen.processInterface(contextDescriptor, kind, state) || + (kind == OwnerKind.DEFAULT_IMPLS && state.getGenerateDefaultImplsForJvm8()))) { int flags = ACC_DEPRECATED | ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC; Method syntheticMethod = getSyntheticMethodSignature(descriptor); MethodVisitor mv = v.newMethod(JvmDeclarationOriginKt.OtherOrigin(descriptor), flags, syntheticMethod.getName(), diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index 93f7e2cbe76..f3ed077c14d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -157,6 +157,7 @@ class GenerationState @JvmOverloads constructor( val useTypeTableInSerializer: Boolean = configuration.getBoolean(JVMConfigurationKeys.USE_TYPE_TABLE) val inheritMultifileParts: Boolean = configuration.getBoolean(JVMConfigurationKeys.INHERIT_MULTIFILE_PARTS) val isJvm8Target: Boolean = configuration.get(JVMConfigurationKeys.JVM_TARGET) == JvmTarget.JVM_1_8 + val generateDefaultImplsForJvm8: Boolean = configuration.getBoolean(JVMConfigurationKeys.INTERFACE_COMPATIBILITY) val rootContext: CodegenContext<*> = RootContext(this) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java index 7370348cf9e..5adfa27b01b 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java @@ -98,6 +98,9 @@ public class K2JVMCompilerArguments extends CommonCompilerArguments { @Argument(value = "Xsingle-module", description = "Combine modules for source files and binary dependencies into a single module") public boolean singleModule; + @Argument(value = "Xinterface-compatibility", description = "Generate DefaultImpls classes for interfaces in JVM target bytecode version 1.8 for binary compatibility with 1.6") + public boolean interfaceCompatibility; + // Paths to output directories for friend modules. public String[] friendPaths; diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt index a69ed5a69f3..d6460074384 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt @@ -139,6 +139,17 @@ class K2JVMCompiler : CLICompiler() { } } + if (arguments.interfaceCompatibility) { + val target = configuration.get(JVMConfigurationKeys.JVM_TARGET) + if (target != JvmTarget.JVM_1_8) { + val errorMessage = "The -Xinterface-compatibility option has effect only for JVM target bytecode version 1.8." + messageCollector.report(CompilerMessageSeverity.WARNING, errorMessage, CompilerMessageLocation.NO_LOCATION) + } + else { + configuration.put(JVMConfigurationKeys.INTERFACE_COMPATIBILITY, true) + } + } + putAdvancedOptions(configuration, arguments) messageCollector.report(CompilerMessageSeverity.LOGGING, "Configuring the compilation environment", CompilerMessageLocation.NO_LOCATION) diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java index f36a99f10f3..82cddfd34a5 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java @@ -62,6 +62,9 @@ public class JVMConfigurationKeys { public static final CompilerConfigurationKey JVM_TARGET = CompilerConfigurationKey.create("JVM bytecode target version"); + public static final CompilerConfigurationKey INTERFACE_COMPATIBILITY = + CompilerConfigurationKey.create("Generate additional 'DefaultImpls' class files for jvm 8 target for compatibility with 6 target interfaces"); + public static final CompilerConfigurationKey INCREMENTAL_COMPILATION_COMPONENTS = CompilerConfigurationKey.create("incremental cache provider"); diff --git a/compiler/testData/cli/jvm/extraHelp.out b/compiler/testData/cli/jvm/extraHelp.out index b1464cf1042..cb1df787805 100644 --- a/compiler/testData/cli/jvm/extraHelp.out +++ b/compiler/testData/cli/jvm/extraHelp.out @@ -10,6 +10,7 @@ where advanced options include: -Xdump-declarations-to Path to JSON file to dump Java to Kotlin declaration mappings -Xload-script-configs Load script configuration files from project directory tree -Xsingle-module Combine modules for source files and binary dependencies into a single module + -Xinterface-compatibility Generate DefaultImpls classes for interfaces in JVM target bytecode version 1.8 for binary compatibility with 1.6 -Xno-inline Disable method inlining -Xrepeat Repeat compilation (for performance analysis) -Xplugin Load plugins from the given classpath diff --git a/compiler/testData/codegen/java8/box/jvm8/reflection/propertyAnnotationsCompatibility.kt b/compiler/testData/codegen/java8/box/jvm8/reflection/propertyAnnotationsCompatibility.kt new file mode 100644 index 00000000000..65b9f581607 --- /dev/null +++ b/compiler/testData/codegen/java8/box/jvm8/reflection/propertyAnnotationsCompatibility.kt @@ -0,0 +1,24 @@ +// JVM_TARGET: 1.8 +// WITH_REFLECT +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INTERFACE_COMPATIBILITY + +annotation class Property(val value: String) +annotation class Accessor(val value: String) + +interface Z { + @Property("OK") + val z: String; + @Accessor("OK") + get() = "OK" +} + + +class Test : Z + +fun box() : String { + val value = (Z::z.annotations.single() as Property).value + if (value != "OK") return value + val forName = Class.forName("Z\$DefaultImpls") + val annotation = forName.getDeclaredMethod("z\$annotations").getAnnotation(Property::class.java) + return annotation.value +} \ No newline at end of file diff --git a/compiler/testData/codegen/java8/writeFlags/defaultMethodCompatibility.kt b/compiler/testData/codegen/java8/writeFlags/defaultMethodCompatibility.kt new file mode 100644 index 00000000000..88535be722c --- /dev/null +++ b/compiler/testData/codegen/java8/writeFlags/defaultMethodCompatibility.kt @@ -0,0 +1,25 @@ +// JVM_TARGET: 1.8 +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INTERFACE_COMPATIBILITY + + +interface Test { + fun test(): String { + return "OK" + } +} + +// TESTED_OBJECT_KIND: function +// TESTED_OBJECTS: Test, test +// FLAGS: ACC_PUBLIC + +// TESTED_OBJECT_KIND: function +// TESTED_OBJECTS: Test, test$defaultImpl +// FLAGS: ACC_PUBLIC, ACC_STATIC, ACC_SYNTHETIC + +// TESTED_OBJECT_KIND: class +// TESTED_OBJECTS: Test$DefaultImpls +// FLAGS: ACC_PUBLIC, ACC_FINAL, ACC_SUPER + +// TESTED_OBJECT_KIND: function +// TESTED_OBJECTS: Test$DefaultImpls, test +// FLAGS: ACC_PUBLIC, ACC_STATIC diff --git a/compiler/testData/codegen/java8/writeFlags/defaultPropertyCompatibility.kt b/compiler/testData/codegen/java8/writeFlags/defaultPropertyCompatibility.kt new file mode 100644 index 00000000000..5ce5c3805c5 --- /dev/null +++ b/compiler/testData/codegen/java8/writeFlags/defaultPropertyCompatibility.kt @@ -0,0 +1,37 @@ +// JVM_TARGET: 1.8 +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INTERFACE_COMPATIBILITY + +interface Test { + var z: String + get() = "OK" + set(value) {} +} + +// TESTED_OBJECT_KIND: function +// TESTED_OBJECTS: Test, getZ +// FLAGS: ACC_PUBLIC + +// TESTED_OBJECT_KIND: function +// TESTED_OBJECTS: Test, setZ +// FLAGS: ACC_PUBLIC + + +// TESTED_OBJECT_KIND: function +// TESTED_OBJECTS: Test, getZ$defaultImpl +// FLAGS: ACC_PUBLIC, ACC_STATIC, ACC_SYNTHETIC + +// TESTED_OBJECT_KIND: function +// TESTED_OBJECTS: Test, setZ$defaultImpl +// FLAGS: ACC_PUBLIC, ACC_STATIC, ACC_SYNTHETIC + +// TESTED_OBJECT_KIND: class +// TESTED_OBJECTS: Test$DefaultImpls +// FLAGS: ACC_PUBLIC, ACC_FINAL, ACC_SUPER + +// TESTED_OBJECT_KIND: function +// TESTED_OBJECTS: Test$DefaultImpls, getZ +// FLAGS: ACC_PUBLIC, ACC_STATIC + +// TESTED_OBJECT_KIND: function +// TESTED_OBJECTS: Test$DefaultImpls, setZ +// FLAGS: ACC_PUBLIC, ACC_STATIC diff --git a/compiler/testData/codegen/java8/writeFlags/interfaceAndDefaultImpls.kt b/compiler/testData/codegen/java8/writeFlags/interfaceAndDefaultImpls.kt deleted file mode 100644 index 0190c13df2d..00000000000 --- a/compiler/testData/codegen/java8/writeFlags/interfaceAndDefaultImpls.kt +++ /dev/null @@ -1,15 +0,0 @@ -// JVM_TARGET: 1.8 - -interface Test { - fun a() { - - } -} - -// TESTED_OBJECT_KIND: class -// TESTED_OBJECTS: Test$DefaultImpls -// FLAGS: ACC_PUBLIC, ACC_FINAL, ACC_SUPER - -// TESTED_OBJECT_KIND: class -// TESTED_OBJECTS: Test -// FLAGS: ACC_PUBLIC, ACC_ABSTRACT, ACC_INTERFACE diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java b/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java index 1855a28bde2..e97bde12f40 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java @@ -209,6 +209,12 @@ public class BlackBoxWithJava8CodegenTestGenerated extends AbstractBlackBoxCodeg String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/jvm8/reflection/propertyAnnotations.kt"); doTest(fileName); } + + @TestMetadata("propertyAnnotationsCompatibility.kt") + public void testPropertyAnnotationsCompatibility() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/jvm8/reflection/propertyAnnotationsCompatibility.kt"); + doTest(fileName); + } } } diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/flags/WriteFlagsTestGenerated.java b/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/flags/WriteFlagsTestGenerated.java index 86e8164a403..87b3045c11a 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/flags/WriteFlagsTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/flags/WriteFlagsTestGenerated.java @@ -41,15 +41,21 @@ public class WriteFlagsTestGenerated extends AbstractWriteFlagsTest { doTest(fileName); } + @TestMetadata("defaultMethodCompatibility.kt") + public void testDefaultMethodCompatibility() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/writeFlags/defaultMethodCompatibility.kt"); + doTest(fileName); + } + @TestMetadata("defaultProperty.kt") public void testDefaultProperty() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/writeFlags/defaultProperty.kt"); doTest(fileName); } - @TestMetadata("interfaceAndDefaultImpls.kt") - public void testInterfaceAndDefaultImpls() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/writeFlags/interfaceAndDefaultImpls.kt"); + @TestMetadata("defaultPropertyCompatibility.kt") + public void testDefaultPropertyCompatibility() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/writeFlags/defaultPropertyCompatibility.kt"); doTest(fileName); } }