Skip to content

Commit

Permalink
Fix gathering inherited properties in PSI
Browse files Browse the repository at this point in the history
  • Loading branch information
BarkingBad committed Feb 15, 2022
1 parent 9309da2 commit 516ecaa
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 10 deletions.
5 changes: 5 additions & 0 deletions core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1833,6 +1833,11 @@ public final class org/jetbrains/dokka/model/JavaVisibility$Public : org/jetbrai
public static final field INSTANCE Lorg/jetbrains/dokka/model/JavaVisibility$Public;
}

public final class org/jetbrains/dokka/model/JvmFieldKt {
public static final fun isJvmField (Lorg/jetbrains/dokka/links/DRI;)Z
public static final fun isJvmField (Lorg/jetbrains/dokka/model/Annotations$Annotation;)Z
}

public final class org/jetbrains/dokka/model/JvmNameKt {
public static final fun isJvmName (Lorg/jetbrains/dokka/links/DRI;)Z
public static final fun isJvmName (Lorg/jetbrains/dokka/model/Annotations$Annotation;)Z
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/kotlin/model/JvmField.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.jetbrains.dokka.model

import org.jetbrains.dokka.links.DRI

fun DRI.isJvmField(): Boolean = packageName == "kotlin.jvm" && classNames == "JvmField"

fun Annotations.Annotation.isJvmField(): Boolean = dri.isJvmName()
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ class DefaultPsiToDocumentableTranslator(
private val PsiMethod.hash: Int
get() = "$returnType $name$parameterList".hashCode()

private val PsiField.hash: Int
get() = "$type $name".hashCode()

private val PsiClassType.shouldBeIgnored: Boolean
get() = isClass("java.lang.Enum") || isClass("java.lang.Object")

Expand Down Expand Up @@ -166,10 +169,16 @@ class DefaultPsiToDocumentableTranslator(
val dri = parent.withClass(name.toString())
val superMethodsKeys = hashSetOf<Int>()
val superMethods = mutableListOf<Pair<PsiMethod, DRI>>()
val superFieldsKeys = hashSetOf<Int>()
val superFields = mutableListOf<Pair<PsiField, DRI>>()
methods.asIterable().parallelForEach { superMethodsKeys.add(it.hash) }

/**
* Caution! This method mutates superMethodsKeys and superMethods
* Caution! This method mutates
* - superMethodsKeys
* - superMethods
* - superFieldsKeys
* - superKeys
*/
fun Array<PsiClassType>.getSuperTypesPsiClasses(): List<Pair<PsiClass, JavaClassKindTypes>> {
forEach { type ->
Expand All @@ -184,6 +193,13 @@ class DefaultPsiToDocumentableTranslator(
superMethods.add(Pair(method, definedAt))
}
}
it.fields.forEach { field ->
val hash = field.hash
if (!superFieldsKeys.contains(hash)) {
superFieldsKeys.add(hash)
superFields.add(Pair(field, definedAt))
}
}
}
}
return filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi ->
Expand Down Expand Up @@ -212,7 +228,22 @@ class DefaultPsiToDocumentableTranslator(
}

val ancestry: AncestryNode = traversePsiClassForAncestorsAndInheritedMembers(this)
val (regularFunctions, accessors) = splitFunctionsAndAccessors()
val (regularFunctions, accessors) = splitFunctionsAndAccessors(psi.fields, psi.methods)
val (regularSuperFunctions, superAccessors) = splitFunctionsAndAccessors(superFields.map { it.first }.toTypedArray(), superMethods.map { it.first }.toTypedArray())

val regularSuperFunctionsKeys = regularSuperFunctions.map { it.hash }.toSet()

val regularSuperFunctionsWithDRI = superMethods.filter { it.first.hash in regularSuperFunctionsKeys }

val superAccessorsWithDRI = superAccessors
.mapValues { (field, methods) ->
if (field.annotations.mapNotNull { it.toAnnotation() }.any { it.isJvmField() }) {
emptyList()
} else {
methods.mapNotNull { method -> superMethods.find { it.first.hash == method.hash } }
}
}

val overridden = regularFunctions.flatMap { it.findSuperMethods().toList() }
val documentation = javadocParser.parseDocumentation(this).toSourceSetDependent()
val allFunctions = async {
Expand All @@ -221,7 +252,15 @@ class DefaultPsiToDocumentableTranslator(
it,
parentDRI = dri
) else null
} + superMethods.filter { it.first !in overridden }.parallelMap { parseFunction(it.first, inheritedFrom = it.second) }
} + regularSuperFunctionsWithDRI.filter { it.first !in overridden }.parallelMap { parseFunction(it.first, inheritedFrom = it.second) }
}
val allFields = async {
fields.toList().parallelMapNotNull { parseField(it, accessors[it].orEmpty()) } +
superFields.parallelMapNotNull { parseFieldWithInheritingAccessors(
it.first,
superAccessorsWithDRI[it.first].orEmpty(),
inheritedFrom = it.second)
}
}
val source = PsiDocumentableSource(this).toSourceSetDependent()
val classlikes = async { innerClasses.asIterable().parallelMap { parseClasslike(it, dri) } }
Expand All @@ -246,7 +285,7 @@ class DefaultPsiToDocumentableTranslator(
null,
source,
allFunctions.await(),
fields.mapNotNull { parseField(it, accessors[it].orEmpty()) },
allFields.await(),
classlikes.await(),
visibility,
null,
Expand Down Expand Up @@ -305,7 +344,7 @@ class DefaultPsiToDocumentableTranslator(
null,
source,
allFunctions.await(),
fields.mapNotNull { parseField(it, accessors[it].orEmpty()) },
allFields.await(),
classlikes.await(),
visibility,
null,
Expand All @@ -324,7 +363,7 @@ class DefaultPsiToDocumentableTranslator(
name.orEmpty(),
constructors.map { parseFunction(it, true) },
allFunctions.await(),
fields.mapNotNull { parseField(it, accessors[it].orEmpty()) },
allFields.await(),
classlikes.await(),
source,
visibility,
Expand Down Expand Up @@ -534,7 +573,7 @@ class DefaultPsiToDocumentableTranslator(
else -> null
}

private fun PsiClass.splitFunctionsAndAccessors(): Pair<MutableList<PsiMethod>, MutableMap<PsiField, MutableList<PsiMethod>>> {
private fun splitFunctionsAndAccessors(fields: Array<PsiField>, methods: Array<PsiMethod>): Pair<MutableList<PsiMethod>, MutableMap<PsiField, MutableList<PsiMethod>>> {
val fieldNames = fields.map { it.name to it }.toMap()
val accessors = mutableMapOf<PsiField, MutableList<PsiMethod>>()
val regularMethods = mutableListOf<PsiMethod>()
Expand All @@ -549,7 +588,21 @@ class DefaultPsiToDocumentableTranslator(
return regularMethods to accessors
}

private fun parseField(psi: PsiField, accessors: List<PsiMethod>): DProperty {
private fun parseFieldWithInheritingAccessors(psi: PsiField, accessors: List<Pair<PsiMethod, DRI>>, inheritedFrom: DRI): DProperty = parseField(
psi,
accessors.firstOrNull { it.first.hasParameters() }?.let { parseFunction(it.first, inheritedFrom = it.second) },
accessors.firstOrNull { it.first.returnType == psi.type }?.let { parseFunction(it.first, inheritedFrom = it.second) },
inheritedFrom
)

private fun parseField(psi: PsiField, accessors: List<PsiMethod>, inheritedFrom: DRI? = null): DProperty = parseField(
psi,
accessors.firstOrNull { it.hasParameters() }?.let { parseFunction(it) },
accessors.firstOrNull { it.returnType == psi.type }?.let { parseFunction(it) },
inheritedFrom
)

private fun parseField(psi: PsiField, getter: DFunction?, setter: DFunction?, inheritedFrom: DRI? = null): DProperty {
val dri = DRI.from(psi)
return DProperty(
dri,
Expand All @@ -560,14 +613,15 @@ class DefaultPsiToDocumentableTranslator(
psi.getVisibility().toSourceSetDependent(),
getBound(psi.type),
null,
accessors.firstOrNull { it.hasParameters() }?.let { parseFunction(it) },
accessors.firstOrNull { it.returnType == psi.type }?.let { parseFunction(it) },
getter,
setter,
psi.getModifier().toSourceSetDependent(),
setOf(sourceSetData),
emptyList(),
false,
psi.additionalExtras().let {
PropertyContainer.withAll<DProperty>(
InheritedMember(inheritedFrom.toSourceSetDependent()),
it.toSourceSetDependent().toAdditionalModifiers(),
(psi.annotations.toList()
.toListOfAnnotations() + it.toListOfAnnotations()).toSourceSetDependent()
Expand Down
137 changes: 137 additions & 0 deletions plugins/base/src/test/kotlin/superFields/PsiSuperFieldsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package superFields

import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.InheritedMember
import org.junit.Ignore
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals


class PsiSuperFieldsTest : BaseAbstractTest() {

@Ignore // TODO: Remove with Kotlin 1.6.20
@Test
fun `java inheriting java`() {
testInline(
"""
|/src/test/A.java
|package test;
|public class A {
| public int a = 1;
|}
|
|/src/test/B.java
|package test;
|public class B extends A {}
""".trimIndent(),
dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
analysisPlatform = "jvm"
name = "jvm"
}
}
}
) {
documentablesMergingStage = {
it.packages.single().classlikes.single { it.name == "B" }.properties.single { it.name == "a" }.run {
assertNotNull(this)
this.extra[InheritedMember]?.inheritedFrom?.values?.single()?.run {
assertEquals(
DRI(packageName = "test", classNames = "A"),
this
)
}
}
}
}
}

@Ignore // TODO: Remove with Kotlin 1.6.20
@Test
fun `java inheriting kotlin`() {
testInline(
"""
|/src/test/A.kt
|package test
|open class A {
| var a: Int = 1
|}
|
|/src/test/B.java
|package test;
|public class B extends A {}
""".trimIndent(),
dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
analysisPlatform = "jvm"
name = "jvm"
}
}
}
) {
documentablesMergingStage = {
it.packages.single().classlikes.single { it.name == "B" }.properties.single { it.name == "a" }.run {
assertNotNull(this)
assertNotNull(this.getter)
assertNotNull(this.setter)
this.extra[InheritedMember]?.inheritedFrom?.values?.single()?.run {
assertEquals(
DRI(packageName = "test", classNames = "A"),
this
)
}
}
}
}
}

@Ignore // TODO: Remove with Kotlin 1.6.20
@Test
fun `java inheriting kotlin with @JvmField should not inherit beans`() {
testInline(
"""
|/src/test/A.kt
|package test
|open class A {
| @kotlin.jvm.JvmField
| var a: Int = 1
|}
|
|/src/test/B.java
|package test;
|public class B extends A {}
""".trimIndent(),
dokkaConfiguration {
sourceSets {
sourceSet {
sourceRoots = listOf("src/")
analysisPlatform = "jvm"
name = "jvm"
classpath += jvmStdlibPath!!
}
}
}
) {
documentablesMergingStage = {
it.packages.single().classlikes.single { it.name == "B" }.properties.single { it.name == "a" }.run {
assertNotNull(this)
assertNull(this.getter)
assertNull(this.setter)
this.extra[InheritedMember]?.inheritedFrom?.values?.single()?.run {
assertEquals(
DRI(packageName = "test", classNames = "A"),
this
)
}
}
}
}
}
}

0 comments on commit 516ecaa

Please sign in to comment.