FIR: do not read a Java nested class if the outer class is Kotlin

Example: if C is a Kotlin class and there is an import of C.Companion.f,
a call to f will need to check if it is a constructor, for which
it will attempt to load C.Companion.f as a Java class. This involves
loading all outer classes, including C itself, as BinaryJavaClass - see
KotlinCliJavaFileManagerImpl - despite the fact that C and C.Companion
have both already been loaded from their Metadata annotations as Kotlin
classes, wasting cycles and polluting caches.

Thus the theoretical motivation for this change is to marginally speed
up FIR.

The real motivation is that with the -Xemit-jvm-type-annotations option,
kotlinc sometimes generates invalid annotations, and reading them with
BinaryJavaClass throws an exception (the Kotlin metadata however is
valid):

    // MODULE: lib
    // FILE: C.kt
    // The constructor has an argument of type
    //   List<? extends Function1<? super String, Integer>>
    // and a ParameterName type annotation with path
    //   TYPE_ARGUMENT(0), TYPE_ARGUMENT(0)
    // which is missing a WILDCARD between the two TYPE_ARGUMENTs
    class C(val x: List<(name: String) -> Int>) {
      companion object {
        fun foo() {}
      }
    }
    // MODULE: main(lib)
    // FILE: main.kt
    import C.Companion.f

    fun bar() = foo() // crashes FIR

The parameter annotation should obviously be fixed too, but invalid
bytecode may already exist in the wild. Also, did I mention that this
change marginally speeds up FIR?
This commit is contained in:
pyos
2022-08-23 14:17:27 +02:00
committed by teamcity
parent 928e0e7fb8
commit ecdd80769e
2 changed files with 20 additions and 11 deletions
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.declarations.getDeprecationsProvider
import org.jetbrains.kotlin.fir.deserialization.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.java.FirJavaFacade
import org.jetbrains.kotlin.fir.java.declarations.FirJavaClass
import org.jetbrains.kotlin.fir.scopes.FirKotlinScopeProvider
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.load.kotlin.*
@@ -97,23 +98,24 @@ class JvmClassFileBasedSymbolProvider(
private val KotlinJvmBinaryClass.isPreReleaseInvisible: Boolean
get() = classHeader.isPreRelease
private fun loadJavaClass(classId: ClassId, content: ByteArray?): ClassMetadataFindResult? {
val javaClass = javaFacade.findClass(classId, content) ?: return null
return ClassMetadataFindResult.NoMetadata { symbol ->
javaFacade.convertJavaClassToFir(symbol, classId.outerClassId?.let(::getClass), javaClass)
}
}
override fun extractClassMetadata(classId: ClassId, parentContext: FirDeserializationContext?): ClassMetadataFindResult? {
// Kotlin classes are annotated Java classes, so this check also looks for them.
if (!javaFacade.hasTopLevelClassOf(classId)) return null
val result = kotlinClassFinder.findKotlinClassOrContent(classId)
val kotlinClass = when (result) {
is KotlinClassFinder.Result.KotlinClass -> result.kotlinJvmBinaryClass
is KotlinClassFinder.Result.ClassFileContent -> return loadJavaClass(classId, result.content)
null -> return loadJavaClass(classId, null)
if (result !is KotlinClassFinder.Result.KotlinClass) {
if (parentContext != null || (classId.isNestedClass && getClass(classId.outermostClassId)?.fir !is FirJavaClass)) {
// Nested class of Kotlin class should have been a Kotlin class.
return null
}
val javaClass = javaFacade.findClass(classId, (result as? KotlinClassFinder.Result.ClassFileContent)?.content)
?: return null
return ClassMetadataFindResult.NoMetadata { symbol ->
javaFacade.convertJavaClassToFir(symbol, classId.outerClassId?.let(::getClass), javaClass)
}
}
val kotlinClass = result.kotlinJvmBinaryClass
if (kotlinClass.classHeader.kind != KotlinClassHeader.Kind.CLASS) return null
val data = kotlinClass.classHeader.data ?: return null
val strings = kotlinClass.classHeader.strings ?: return null