JVM, JVM IR: report error if not all parts of multifile class are @JvmSynthetic

#KT-41884 Fixed
This commit is contained in:
Alexander Udalov
2021-03-10 22:03:34 +01:00
parent 75850a618c
commit bc5fc122c5
12 changed files with 163 additions and 1 deletions
@@ -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" }
@@ -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");
@@ -74,6 +74,7 @@ public interface ErrorsJvm {
DiagnosticFactory0<KtAnnotationEntry> JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_FILES_WITH_CLASSES = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtDeclaration> STATE_IN_MULTIFILE_CLASS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> NOT_ALL_MULTIFILE_CLASS_PARTS_ARE_JVM_SYNTHETIC = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INTERFACE_CANT_CALL_DEFAULT_METHOD_VIA_SUPER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC = DiagnosticFactory0.create(ERROR);
@@ -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<JvmBackendContext, IrModuleFragment>(
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) {
@@ -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 = ""
@@ -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 <clinit>(): void
public final static @org.jetbrains.annotations.NotNull method getG(): java.lang.String
}
@@ -0,0 +1,33 @@
// WITH_RUNTIME
// FILE: f.kt
@file:JvmName("Foo")
@file:JvmMultifileClass
<!NOT_ALL_MULTIFILE_CLASS_PARTS_ARE_JVM_SYNTHETIC!>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
<!NOT_ALL_MULTIFILE_CLASS_PARTS_ARE_JVM_SYNTHETIC!>package test<!>
fun h() {}
// FILE: z.kt
@file:JvmName("Bar")
@file:JvmMultifileClass
package test
fun z() {}
@@ -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
}
@@ -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")
@@ -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")
@@ -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");
@@ -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");