From bc5fc122c5976f073c192e2642fe35b7b4516964 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 10 Mar 2021 22:03:34 +0100 Subject: [PATCH] JVM, JVM IR: report error if not all parts of multifile class are @JvmSynthetic #KT-41884 Fixed --- .../kotlin/codegen/MultifileClassCodegen.kt | 24 +++++++++++++- .../diagnostics/DefaultErrorMessagesJvm.java | 1 + .../resolve/jvm/diagnostics/ErrorsJvm.java | 1 + .../jvm/lower/GenerateMultifileFacades.kt | 18 ++++++++++ .../multifileClasses/jvmSynthetic.kt | 18 ++++++++++ .../multifileClasses/jvmSynthetic.txt | 19 +++++++++++ .../multifileClasses/jvmSynthetic.kt | 33 +++++++++++++++++++ .../multifileClasses/jvmSynthetic.txt | 8 +++++ ...gnosticsTestWithJvmIrBackendGenerated.java | 16 +++++++++ ...nosticsTestWithOldJvmBackendGenerated.java | 16 +++++++++ .../codegen/BytecodeListingTestGenerated.java | 5 +++ .../ir/IrBytecodeListingTestGenerated.java | 5 +++ 12 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.kt create mode 100644 compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.txt create mode 100644 compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.kt create mode 100644 compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.txt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassCodegen.kt index 041b6b2d4e3..8becdf49b50 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassCodegen.kt @@ -34,9 +34,13 @@ import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtTypeAlias import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.MemberComparator +import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm import org.jetbrains.kotlin.resolve.jvm.diagnostics.MultifileClass import org.jetbrains.kotlin.resolve.jvm.diagnostics.MultifileClassPart import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin @@ -125,8 +129,19 @@ class MultifileClassCodegenImpl( val superClassForFacade = if (shouldGeneratePartHierarchy) partInternalNamesSorted.last() else J_L_OBJECT state.factory.newVisitor(MultifileClass(files.firstOrNull(), actualPackageFragment), facadeClassType, files).apply { + var attributes = FACADE_CLASS_ATTRIBUTES + + val nonJvmSyntheticParts = files.filterNot { it.isJvmSynthetic() } + if (nonJvmSyntheticParts.isEmpty()) { + attributes = attributes or Opcodes.ACC_SYNTHETIC + } else if (nonJvmSyntheticParts.size < files.size) { + for (part in nonJvmSyntheticParts) { + state.diagnostics.report(ErrorsJvm.NOT_ALL_MULTIFILE_CLASS_PARTS_ARE_JVM_SYNTHETIC.on(part.packageDirective ?: part)) + } + } + defineClass( - singleSourceFile, state.classFileVersion, FACADE_CLASS_ATTRIBUTES, + singleSourceFile, state.classFileVersion, attributes, facadeClassType.internalName, null, superClassForFacade, emptyArray() ) if (singleSourceFile != null) { @@ -146,6 +161,13 @@ class MultifileClassCodegenImpl( } } + private fun KtFile.isJvmSynthetic(): Boolean { + return annotationEntries.any { entry -> + val descriptor = state.bindingContext[BindingContext.ANNOTATION, entry] + descriptor?.annotationClass?.let(DescriptorUtils::getFqNameSafe) == JVM_SYNTHETIC_ANNOTATION_FQ_NAME + } + } + override fun generate() { assert(delegateGenerationTasks.isEmpty()) { "generate() is called twice for facade class $facadeFqName" } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java index b75c785baf1..6674f79772b 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java @@ -78,6 +78,7 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_FILES_WITH_CLASSES, "''@JvmPackageName'' annotation is not supported for files with class declarations"); MAP.put(STATE_IN_MULTIFILE_CLASS, "Non-const property with backing field or delegate is not allowed in a multi-file class if -Xmultifile-parts-inherit is enabled"); + MAP.put(NOT_ALL_MULTIFILE_CLASS_PARTS_ARE_JVM_SYNTHETIC, "All of multi-file class parts should be annotated with @JvmSynthetic if at least one of them is"); MAP.put(NO_REFLECTION_IN_CLASS_PATH, "Call uses reflection API which is not found in compilation classpath. " + "Make sure you have kotlin-reflect.jar in the classpath"); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index cb549a51268..383503c7924 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -74,6 +74,7 @@ public interface ErrorsJvm { DiagnosticFactory0 JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_FILES_WITH_CLASSES = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 STATE_IN_MULTIFILE_CLASS = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 NOT_ALL_MULTIFILE_CLASS_PARTS_ARE_JVM_SYNTHETIC = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 INTERFACE_CANT_CALL_DEFAULT_METHOD_VIA_SUPER = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC = DiagnosticFactory0.create(ERROR); diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/GenerateMultifileFacades.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/GenerateMultifileFacades.kt index a3eb1ad4156..fae8e761705 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/GenerateMultifileFacades.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/GenerateMultifileFacades.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.backend.common.phaser.makeCustomPhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.codegen.fileParent +import org.jetbrains.kotlin.backend.jvm.ir.getKtFile import org.jetbrains.kotlin.config.JvmAnalysisFlags import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.Modality @@ -42,6 +43,8 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities import org.jetbrains.kotlin.resolve.inline.INLINE_ONLY_ANNOTATION_FQ_NAME import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm internal val generateMultifileFacadesPhase = makeCustomPhase( name = "GenerateMultifileFacades", @@ -133,7 +136,22 @@ private fun generateMultifileFacades( } } } + + val nonJvmSyntheticParts = partClasses.filterNot { it.hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME) } + if (nonJvmSyntheticParts.isEmpty()) { + annotations = annotations + partClasses.first().getAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME)!!.deepCopyWithSymbols() + } else if (nonJvmSyntheticParts.size < partClasses.size) { + for (part in nonJvmSyntheticParts) { + val partFile = part.fileParent.getKtFile() ?: error("Not a KtFile: ${part.render()} ${part.fileParent}") + // If at least one of parts is annotated with @JvmSynthetic, then all other parts should also be annotated. + // We report this error on the package directive for each non-@JvmSynthetic part. + context.state.diagnostics.report( + ErrorsJvm.NOT_ALL_MULTIFILE_CLASS_PARTS_ARE_JVM_SYNTHETIC.on(partFile.packageDirective ?: partFile) + ) + } + } } + file.declarations.add(facadeClass) for (partClass in partClasses) { diff --git a/compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.kt b/compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.kt new file mode 100644 index 00000000000..0f9d9fee407 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.kt @@ -0,0 +1,18 @@ +// WITH_RUNTIME +// FILE: f.kt + +@file:JvmName("Foo") +@file:JvmMultifileClass +@file:JvmSynthetic +package test + +fun f() {} + +// FILE: g.kt + +@file:JvmName("Foo") +@file:JvmMultifileClass +@file:JvmSynthetic +package test + +val g = "" diff --git a/compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.txt b/compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.txt new file mode 100644 index 00000000000..e735ffe27f0 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.txt @@ -0,0 +1,19 @@ +@kotlin.Metadata +public synthetic final class test/Foo { + public final static method f(): void + public final static @org.jetbrains.annotations.NotNull method getG(): java.lang.String +} + +@kotlin.Metadata +synthetic final class test/Foo__FKt { + // source: 'f.kt' + public final static method f(): void +} + +@kotlin.Metadata +synthetic final class test/Foo__GKt { + // source: 'g.kt' + private final static @org.jetbrains.annotations.NotNull field g: java.lang.String + static method (): void + public final static @org.jetbrains.annotations.NotNull method getG(): java.lang.String +} diff --git a/compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.kt b/compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.kt new file mode 100644 index 00000000000..46b296b7c6b --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.kt @@ -0,0 +1,33 @@ +// WITH_RUNTIME +// FILE: f.kt + +@file:JvmName("Foo") +@file:JvmMultifileClass +package test + +fun f() {} + +// FILE: g.kt + +@file:JvmName("Foo") +@file:JvmMultifileClass +@file:JvmSynthetic +package test + +val g = "" + +// FILE: h.kt + +@file:JvmName("Foo") +@file:JvmMultifileClass +package test + +fun h() {} + +// FILE: z.kt + +@file:JvmName("Bar") +@file:JvmMultifileClass +package test + +fun z() {} diff --git a/compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.txt b/compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.txt new file mode 100644 index 00000000000..726b6ce77b4 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.txt @@ -0,0 +1,8 @@ +package + +package test { + public val g: kotlin.String = "" + public fun f(): kotlin.Unit + public fun h(): kotlin.Unit + public fun z(): kotlin.Unit +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJvmIrBackendGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJvmIrBackendGenerated.java index 6379c344a4f..54c39aef3a2 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJvmIrBackendGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithJvmIrBackendGenerated.java @@ -641,6 +641,22 @@ public class DiagnosticsTestWithJvmIrBackendGenerated extends AbstractDiagnostic } } + @Nested + @TestMetadata("compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses") + @TestDataPath("$PROJECT_ROOT") + public class MultifileClasses { + @Test + public void testAllFilesPresentInMultifileClasses() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("jvmSynthetic.kt") + public void testJvmSynthetic() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.kt"); + } + } + @Nested @TestMetadata("compiler/testData/diagnostics/testsWithJvmBackend/valueClasses") @TestDataPath("$PROJECT_ROOT") diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithOldJvmBackendGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithOldJvmBackendGenerated.java index ac06ee6d804..151ca0cde71 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithOldJvmBackendGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticsTestWithOldJvmBackendGenerated.java @@ -629,6 +629,22 @@ public class DiagnosticsTestWithOldJvmBackendGenerated extends AbstractDiagnosti } } + @Nested + @TestMetadata("compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses") + @TestDataPath("$PROJECT_ROOT") + public class MultifileClasses { + @Test + public void testAllFilesPresentInMultifileClasses() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_OLD, true); + } + + @Test + @TestMetadata("jvmSynthetic.kt") + public void testJvmSynthetic() throws Exception { + runTest("compiler/testData/diagnostics/testsWithJvmBackend/multifileClasses/jvmSynthetic.kt"); + } + } + @Nested @TestMetadata("compiler/testData/diagnostics/testsWithJvmBackend/valueClasses") @TestDataPath("$PROJECT_ROOT") diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java index d973fa1dfdf..818a87166a0 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java @@ -1548,6 +1548,11 @@ public class BytecodeListingTestGenerated extends AbstractBytecodeListingTest { runTest("compiler/testData/codegen/bytecodeListing/multifileClasses/emptyMultifileFacade.kt"); } + @TestMetadata("jvmSynthetic.kt") + public void testJvmSynthetic() throws Exception { + runTest("compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.kt"); + } + @TestMetadata("kt43519.kt") public void testKt43519() throws Exception { runTest("compiler/testData/codegen/bytecodeListing/multifileClasses/kt43519.kt"); diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java index 02dc1d95b53..968c74442fe 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java @@ -1548,6 +1548,11 @@ public class IrBytecodeListingTestGenerated extends AbstractIrBytecodeListingTes runTest("compiler/testData/codegen/bytecodeListing/multifileClasses/emptyMultifileFacade.kt"); } + @TestMetadata("jvmSynthetic.kt") + public void testJvmSynthetic() throws Exception { + runTest("compiler/testData/codegen/bytecodeListing/multifileClasses/jvmSynthetic.kt"); + } + @TestMetadata("kt43519.kt") public void testKt43519() throws Exception { runTest("compiler/testData/codegen/bytecodeListing/multifileClasses/kt43519.kt");