only stub default constructor when compiling against .java source files

This commit is contained in:
Kevin Bierhoff
2020-03-09 11:33:20 -07:00
committed by Alexander Udalov
parent c83860187c
commit 7448761dfd
19 changed files with 90 additions and 2 deletions

View File

@@ -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) {

View File

@@ -100,6 +100,8 @@ class JavaClassImpl(psiClass: PsiClass) : JavaClassifierImpl<PsiClass>(psiClass)
return constructors(psi.constructors.filter { method -> method.isConstructor })
}
override fun hasDefaultConstructor() = !isInterface && constructors.isEmpty()
override val isAbstract: Boolean
get() = JavaElementUtil.isAbstract(this)

View File

@@ -47,6 +47,7 @@ class BinaryJavaClass(
override val methods = arrayListOf<JavaMethod>()
override val fields = arrayListOf<JavaField>()
override val constructors = arrayListOf<JavaConstructor>()
override fun hasDefaultConstructor() = false // never: all constructors explicit in bytecode
override val annotationsByFqName by buildLazyValueForMap()

View File

@@ -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()

View File

@@ -73,6 +73,8 @@ class FakeSymbolBasedClass(
override val constructors: Collection<JavaConstructor> get() = emptyList()
override fun hasDefaultConstructor() = false
override val innerClassNames: Collection<Name> get() = emptyList()
override val virtualFile: VirtualFile? by lazy {

View File

@@ -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<Name>
get() = innerClasses.keys

View File

@@ -134,6 +134,8 @@ class TreeBasedClass(
TreeBasedConstructor(constructor as JCTree.JCMethodDecl, compilationUnit, this, javac)
}
override fun hasDefaultConstructor() = !isInterface && constructors.isEmpty()
override val innerClassNames: Collection<Name>
get() = innerClasses.keys

View File

@@ -0,0 +1,3 @@
package test
fun foo(msg: String) = msg

View File

@@ -0,0 +1,4 @@
compiler/testData/compileKotlinAgainstCustomBinaries/classfileWithoutConstructors/shouldNotCompile.kt:6:9: error: unresolved reference: TopLevelKt
TopLevelKt() // error here
^
COMPILATION_ERROR

View File

@@ -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
}
}

View File

@@ -0,0 +1,4 @@
package test;
public class ClassDefaultConstructor {
}

View File

@@ -0,0 +1,3 @@
package test
fun useClassDefaultConstructor() = ClassDefaultConstructor()

View File

@@ -0,0 +1,7 @@
package test
public fun useClassDefaultConstructor(): test.ClassDefaultConstructor
public open class ClassDefaultConstructor {
public constructor ClassDefaultConstructor()
}

View File

@@ -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<out String>?
): 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))

View File

@@ -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");

View File

@@ -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

View File

@@ -90,6 +90,7 @@ interface JavaClass : JavaClassifier, JavaTypeParameterListOwner, JavaModifierLi
val methods: Collection<JavaMethod>
val fields: Collection<JavaField>
val constructors: Collection<JavaConstructor>
fun hasDefaultConstructor(): Boolean
}
val JavaClass.classId: ClassId?

View File

@@ -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