diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaSymbolProvider.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaSymbolProvider.kt index dc963ceabb6..1a08bb7c3ae 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaSymbolProvider.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaSymbolProvider.kt @@ -250,7 +250,9 @@ class JavaSymbolProvider( } } - if (javaClassDeclaredConstructors.isEmpty() && javaClass.classKind == ClassKind.CLASS) { + if (javaClassDeclaredConstructors.isEmpty() + && javaClass.classKind == ClassKind.CLASS + && javaClass.hasDefaultConstructor()) { declarations += prepareJavaConstructor(isPrimary = true).build() } for (javaConstructor in javaClassDeclaredConstructors) { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt index a5ec0e97d62..f5085cd1021 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt @@ -100,6 +100,8 @@ class JavaClassImpl(psiClass: PsiClass) : JavaClassifierImpl(psiClass) return constructors(psi.constructors.filter { method -> method.isConstructor }) } + override fun hasDefaultConstructor() = !isInterface && constructors.isEmpty() + override val isAbstract: Boolean get() = JavaElementUtil.isAbstract(this) diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/classFiles/BinaryJavaClass.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/classFiles/BinaryJavaClass.kt index 87c6a94be1f..893b93717fc 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/classFiles/BinaryJavaClass.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/classFiles/BinaryJavaClass.kt @@ -47,6 +47,7 @@ class BinaryJavaClass( override val methods = arrayListOf() override val fields = arrayListOf() override val constructors = arrayListOf() + override fun hasDefaultConstructor() = false // never: all constructors explicit in bytecode override val annotationsByFqName by buildLazyValueForMap() diff --git a/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/resolve/KotlinClassifiersCache.kt b/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/resolve/KotlinClassifiersCache.kt index 6b3c32501ac..a601a8fdc6c 100644 --- a/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/resolve/KotlinClassifiersCache.kt +++ b/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/resolve/KotlinClassifiersCache.kt @@ -159,6 +159,7 @@ class MockKotlinClassifier(override val classId: ClassId, override val methods get() = shouldNotBeCalled() override val fields get() = shouldNotBeCalled() override val constructors get() = shouldNotBeCalled() + override fun hasDefaultConstructor() = shouldNotBeCalled() override val annotations get() = shouldNotBeCalled() override val isDeprecatedInJavaDoc get() = shouldNotBeCalled() override fun findAnnotation(fqName: FqName) = shouldNotBeCalled() diff --git a/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/symbols/FakeSymbolBasedClass.kt b/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/symbols/FakeSymbolBasedClass.kt index abaee504a9b..4ccafe2b63e 100644 --- a/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/symbols/FakeSymbolBasedClass.kt +++ b/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/symbols/FakeSymbolBasedClass.kt @@ -73,6 +73,8 @@ class FakeSymbolBasedClass( override val constructors: Collection get() = emptyList() + override fun hasDefaultConstructor() = false + override val innerClassNames: Collection get() = emptyList() override val virtualFile: VirtualFile? by lazy { diff --git a/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/symbols/SymbolBasedClass.kt b/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/symbols/SymbolBasedClass.kt index b63d4a40c6a..2366edc88d9 100644 --- a/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/symbols/SymbolBasedClass.kt +++ b/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/symbols/SymbolBasedClass.kt @@ -133,6 +133,8 @@ class SymbolBasedClass( .filter { it.kind == ElementKind.CONSTRUCTOR } .map { SymbolBasedConstructor(it as ExecutableElement, this, javac) } + override fun hasDefaultConstructor() = false // default constructors are explicit in symbols + override val innerClassNames: Collection get() = innerClasses.keys diff --git a/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/trees/TreeBasedClass.kt b/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/trees/TreeBasedClass.kt index a9f3cc027d7..2bc69c8bf27 100644 --- a/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/trees/TreeBasedClass.kt +++ b/compiler/javac-wrapper/src/org/jetbrains/kotlin/javac/wrappers/trees/TreeBasedClass.kt @@ -134,6 +134,8 @@ class TreeBasedClass( TreeBasedConstructor(constructor as JCTree.JCMethodDecl, compilationUnit, this, javac) } + override fun hasDefaultConstructor() = !isInterface && constructors.isEmpty() + override val innerClassNames: Collection get() = innerClasses.keys diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/TopLevel.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/TopLevel.kt new file mode 100644 index 00000000000..f4b74438c84 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/TopLevel.kt @@ -0,0 +1,3 @@ +package test + +fun foo(msg: String) = msg \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/TopLevel.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/TopLevel.txt new file mode 100644 index 00000000000..d86bac9de59 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/TopLevel.txt @@ -0,0 +1 @@ +OK diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/output.txt b/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/output.txt new file mode 100644 index 00000000000..2b5540516f0 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/output.txt @@ -0,0 +1,4 @@ +compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/shouldNotCompile.kt:6:9: error: unresolved reference: TopLevelKt + TopLevelKt() // error here + ^ +COMPILATION_ERROR diff --git a/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/shouldNotCompile.kt b/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/shouldNotCompile.kt new file mode 100644 index 00000000000..0ecc94f7a86 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/shouldNotCompile.kt @@ -0,0 +1,10 @@ +package test + +public class B { + + public fun test(): String { + TopLevelKt() // error here + return TopLevelKt.foo("OK") // no error here: can still call static functions + } + +} \ No newline at end of file diff --git a/compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.java b/compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.java new file mode 100644 index 00000000000..c0270f58ddf --- /dev/null +++ b/compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.java @@ -0,0 +1,4 @@ +package test; + +public class ClassDefaultConstructor { +} diff --git a/compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.kt b/compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.kt new file mode 100644 index 00000000000..2fd99ba0569 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.kt @@ -0,0 +1,3 @@ +package test + +fun useClassDefaultConstructor() = ClassDefaultConstructor() diff --git a/compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.txt b/compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.txt new file mode 100644 index 00000000000..ecd2e119fb5 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.txt @@ -0,0 +1,7 @@ +package test + +public fun useClassDefaultConstructor(): test.ClassDefaultConstructor + +public open class ClassDefaultConstructor { + public constructor ClassDefaultConstructor() +} diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.kt index cb6ec65e201..55c66caf65c 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstCustomBinariesTest.kt @@ -458,6 +458,37 @@ class CompileKotlinAgainstCustomBinariesTest : AbstractKotlinCompilerIntegration } } + /* Regression test for KT-37107: compile against .class file without any constructors. */ + fun testClassfileWithoutConstructors() { + compileKotlin("TopLevel.kt", tmpdir, expectedFileName = "TopLevel.txt") + + val inlineFunClass = File(tmpdir.absolutePath, "test/TopLevelKt.class") + val cw = ClassWriter(Opcodes.API_VERSION) + ClassReader(inlineFunClass.readBytes()).accept(object : ClassVisitor(Opcodes.API_VERSION, cw) { + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? = + if (desc == JvmAnnotationNames.METADATA_DESC) null else super.visitAnnotation(desc, visible) + + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array? + ): MethodVisitor { + assertEquals("foo", name) // test sanity: shouldn't see any constructors, only the "foo" method + return super.visitMethod(access, name, descriptor, signature, exceptions) + } + }, 0) + + assert(inlineFunClass.delete()) + assert(!inlineFunClass.exists()) + + inlineFunClass.writeBytes(cw.toByteArray()) + + val (_, exitCode) = compileKotlin("shouldNotCompile.kt", tmpdir, listOf(tmpdir)) + assertEquals(1, exitCode.code) // double-check that we failed :) output.txt also says so + } + fun testReplaceAnnotationClassWithInterface() { val library1 = compileLibrary("library-1") val usage = compileLibrary("usage", extraClassPath = listOf(library1)) diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstJavaTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstJavaTestGenerated.java index 5dc4b48cfec..656534e10f3 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstJavaTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/CompileKotlinAgainstJavaTestGenerated.java @@ -70,6 +70,11 @@ public class CompileKotlinAgainstJavaTestGenerated extends AbstractCompileKotlin runTest("compiler/testData/compileKotlinAgainstJava/Class.kt"); } + @TestMetadata("ClassDefaultConstructor.kt") + public void testClassDefaultConstructor() throws Exception { + runTest("compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.kt"); + } + @TestMetadata("ClassWithNestedEnum.kt") public void testClassWithNestedEnum() throws Exception { runTest("compiler/testData/compileKotlinAgainstJava/ClassWithNestedEnum.kt"); @@ -353,6 +358,11 @@ public class CompileKotlinAgainstJavaTestGenerated extends AbstractCompileKotlin runTest("compiler/testData/compileKotlinAgainstJava/Class.kt"); } + @TestMetadata("ClassDefaultConstructor.kt") + public void testClassDefaultConstructor() throws Exception { + runTest("compiler/testData/compileKotlinAgainstJava/ClassDefaultConstructor.kt"); + } + @TestMetadata("ClassWithNestedEnum.kt") public void testClassWithNestedEnum() throws Exception { runTest("compiler/testData/compileKotlinAgainstJava/ClassWithNestedEnum.kt"); diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassMemberScope.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassMemberScope.kt index 9ded0329f0d..1bffd9697fb 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassMemberScope.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaClassMemberScope.kt @@ -619,7 +619,7 @@ class LazyJavaClassMemberScope( private fun createDefaultConstructor(): ClassConstructorDescriptor? { val isAnnotation: Boolean = jClass.isAnnotationType - if (jClass.isInterface && !isAnnotation) + if ((jClass.isInterface || !jClass.hasDefaultConstructor()) && !isAnnotation) return null val classDescriptor = ownerDescriptor diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/structure/javaElements.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/structure/javaElements.kt index 013bbf4f060..45e221f9716 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/structure/javaElements.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/structure/javaElements.kt @@ -90,6 +90,7 @@ interface JavaClass : JavaClassifier, JavaTypeParameterListOwner, JavaModifierLi val methods: Collection val fields: Collection val constructors: Collection + fun hasDefaultConstructor(): Boolean } val JavaClass.classId: ClassId? diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/descriptors/runtime/structure/ReflectJavaClass.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/descriptors/runtime/structure/ReflectJavaClass.kt index f4a88670b86..b2097b11e79 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/descriptors/runtime/structure/ReflectJavaClass.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/descriptors/runtime/structure/ReflectJavaClass.kt @@ -96,6 +96,8 @@ class ReflectJavaClass( .map(::ReflectJavaConstructor) .toList() + override fun hasDefaultConstructor() = false // any default constructor is returned by constructors + override val lightClassOriginKind: LightClassOriginKind? get() = null