Skip to content

Commit

Permalink
Update java supertype processing
Browse files Browse the repository at this point in the history
  • Loading branch information
juliamcclellan committed Jan 15, 2025
1 parent d54787b commit 522f732
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,11 @@ internal class DokkaPsiParser(
* - superMethods
* - superFieldsKeys
* - superKeys
*
* First processes the list of [PsiClassType]s to add their methods and fields to the maps mentioned above,
* then filters the list to return a pair of the optional superclass type and a list of interface types.
*/
/**
* Caution! This method mutates
* - superMethodsKeys
* - superMethods
* - superFieldsKeys
* - superKeys
*/
fun Array<PsiClassType>.getSuperTypesPsiClasses(): List<Pair<PsiClass, JavaClassKindTypes>> {
fun List<PsiClassType>.getSuperclassAndInterfaces(): Pair<PsiClassType?, List<PsiClassType>> {
forEach { type ->
type.resolve()?.let {
val definedAt = DRI.from(it)
Expand All @@ -137,38 +133,57 @@ internal class DokkaPsiParser(
}
}
}
return filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi ->
val supertypesToKinds = filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi ->
supertypePsi.resolve()?.let { supertypePsiClass ->
val javaClassKind = when {
supertypePsiClass.isInterface -> JavaClassKindTypes.INTERFACE
else -> JavaClassKindTypes.CLASS
}
supertypePsiClass to javaClassKind
supertypePsi to javaClassKind
}
}
val (superclassPairs, interfacePairs) =
supertypesToKinds.partition { it.second == JavaClassKindTypes.CLASS }
return superclassPairs.firstOrNull()?.first to interfacePairs.map { it.first}
}

fun traversePsiClassForAncestorsAndInheritedMembers(psiClass: PsiClass): AncestryNode {
val (classes, interfaces) = psiClass.superTypes.getSuperTypesPsiClasses()
.partition { it.second == JavaClassKindTypes.CLASS }
/**
* Creates an [AncestryNode] for the [type] given the list of all [supertypes].
*
* Also processes all super methods and fields using the getSuperclassAndInterfaces function defined above.
*/
fun createAncestryNode(type: GenericTypeConstructor, supertypes: List<PsiClassType>): AncestryNode {
fun createAncestryNodeForPsiClassType(psiClassType: PsiClassType): AncestryNode {
return createAncestryNode(
type = GenericTypeConstructor(
DRI.from(psiClassType.resolve()!!),
psiClassType.parameters.map { getProjection(it) }
),
supertypes = psiClassType.superTypes.filterIsInstance<PsiClassType>()
)
}

val (superclass, interfaces) = supertypes.getSuperclassAndInterfaces()
return AncestryNode(
typeConstructor = GenericTypeConstructor(
DRI.from(psiClass),
psiClass.typeParameters.map { typeParameter ->
TypeParameter(
dri = DRI.from(typeParameter),
name = typeParameter.name.orEmpty(),
extra = typeParameter.annotations()
)
}
),
superclass = classes.singleOrNull()?.first?.let(::traversePsiClassForAncestorsAndInheritedMembers),
interfaces = interfaces.map { traversePsiClassForAncestorsAndInheritedMembers(it.first) }
typeConstructor = type,
superclass = superclass?.let(::createAncestryNodeForPsiClassType),
interfaces = interfaces.map { createAncestryNodeForPsiClassType(it) }
)
}

val ancestry: AncestryNode = traversePsiClassForAncestorsAndInheritedMembers(this)
val ancestry = createAncestryNode(
type = GenericTypeConstructor(
DRI.from(this),
typeParameters.map { typeParameter ->
TypeParameter(
dri = DRI.from(typeParameter),
name = typeParameter.name.orEmpty(),
extra = typeParameter.annotations()
)
}
),
supertypes = superTypes.toList()
)

val (regularFunctions, accessors) = splitFunctionsAndAccessors(psi.fields, psi.methods)
val (regularSuperFunctions, superAccessors) = splitFunctionsAndAccessors(
Expand Down
42 changes: 39 additions & 3 deletions dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.doc.Param
import org.jetbrains.dokka.model.doc.See
import org.jetbrains.dokka.model.doc.Text
import utils.AbstractModelTest
import utils.*
import utils.assertContains
import utils.assertNotNull
import utils.name
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
Expand Down Expand Up @@ -94,6 +92,25 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
}
}

@Test
fun interfaceWithGeneric() {
inlineModelTest(
"""
|interface Bar<T> {}
|public class Foo implements Bar<String> {}
""", configuration = configuration
) {
with((this / "java" / "Foo").cast<DClass>()) {
val interfaceType = supertypes.values.flatten().single()
assertEquals(interfaceType.kind, JavaClassKindTypes.INTERFACE)
assertEquals(interfaceType.typeConstructor.dri.classNames, "Bar")
// The interface type should be Bar<String>, and not use Bar<T> like the interface definition
val generic = interfaceType.typeConstructor.projections.single() as GenericTypeConstructor
assertEquals(generic.dri.classNames, "String")
}
}
}

@Test
fun superClass() {
inlineModelTest(
Expand All @@ -110,6 +127,25 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
}
}

@Test
fun superclassWithGeneric() {
inlineModelTest(
"""
|class Bar<T> {}
|public class Foo extends Bar<String> {}
""", configuration = configuration
) {
with((this / "java" / "Foo").cast<DClass>()) {
val superclassType = supertypes.values.flatten().single()
assertEquals(superclassType.kind, JavaClassKindTypes.CLASS)
assertEquals(superclassType.typeConstructor.dri.classNames, "Bar")
// The superclass type should be Bar<String>, and not use Bar<T> like the class definition
val generic = superclassType.typeConstructor.projections.single() as GenericTypeConstructor
assertEquals(generic.dri.classNames, "String")
}
}
}

@Test
fun arrayType() {
inlineModelTest(
Expand Down

0 comments on commit 522f732

Please sign in to comment.