Skip to content

Commit af87c48

Browse files
Update Java supertype processing (#3997)
1 parent ac6e7f1 commit af87c48

File tree

2 files changed

+103
-26
lines changed
  • dokka-subprojects

2 files changed

+103
-26
lines changed

dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt

+45-26
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,11 @@ internal class DokkaPsiParser(
107107
* - superMethods
108108
* - superFieldsKeys
109109
* - superKeys
110+
*
111+
* First processes the list of [PsiClassType]s to add their methods and fields to the maps mentioned above,
112+
* then filters the list to return a pair of the optional superclass type and a list of interface types.
110113
*/
111-
/**
112-
* Caution! This method mutates
113-
* - superMethodsKeys
114-
* - superMethods
115-
* - superFieldsKeys
116-
* - superKeys
117-
*/
118-
fun Array<PsiClassType>.getSuperTypesPsiClasses(): List<Pair<PsiClass, JavaClassKindTypes>> {
114+
fun List<PsiClassType>.getSuperclassAndInterfaces(): Pair<PsiClassType?, List<PsiClassType>> {
119115
forEach { type ->
120116
type.resolve()?.let {
121117
val definedAt = DRI.from(it)
@@ -137,38 +133,61 @@ internal class DokkaPsiParser(
137133
}
138134
}
139135
}
140-
return filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi ->
136+
val supertypesToKinds = filter { !it.shouldBeIgnored }.mapNotNull { supertypePsi ->
141137
supertypePsi.resolve()?.let { supertypePsiClass ->
142138
val javaClassKind = when {
143139
supertypePsiClass.isInterface -> JavaClassKindTypes.INTERFACE
144140
else -> JavaClassKindTypes.CLASS
145141
}
146-
supertypePsiClass to javaClassKind
142+
supertypePsi to javaClassKind
147143
}
148144
}
145+
val (superclassPairs, interfacePairs) =
146+
supertypesToKinds.partition { it.second == JavaClassKindTypes.CLASS }
147+
return superclassPairs.firstOrNull()?.first to interfacePairs.map { it.first}
149148
}
150149

151-
fun traversePsiClassForAncestorsAndInheritedMembers(psiClass: PsiClass): AncestryNode {
152-
val (classes, interfaces) = psiClass.superTypes.getSuperTypesPsiClasses()
153-
.partition { it.second == JavaClassKindTypes.CLASS }
150+
/**
151+
* Creates an [AncestryNode] for the [type] given the list of all [supertypes].
152+
*
153+
* Also processes all super methods and fields using the getSuperclassAndInterfaces function defined above.
154+
*/
155+
fun createAncestryNode(type: GenericTypeConstructor, supertypes: List<PsiClassType>): AncestryNode {
156+
fun createAncestryNodeForPsiClassType(psiClassType: PsiClassType): AncestryNode {
157+
return createAncestryNode(
158+
type = GenericTypeConstructor(
159+
DRI.from(psiClassType.resolve()!!),
160+
psiClassType.parameters.map { getProjection(it) }
161+
),
162+
supertypes = psiClassType.superTypes.filterIsInstance<PsiClassType>()
163+
)
164+
}
154165

166+
val (superclass, interfaces) = supertypes.getSuperclassAndInterfaces()
155167
return AncestryNode(
156-
typeConstructor = GenericTypeConstructor(
157-
DRI.from(psiClass),
158-
psiClass.typeParameters.map { typeParameter ->
159-
TypeParameter(
160-
dri = DRI.from(typeParameter),
161-
name = typeParameter.name.orEmpty(),
162-
extra = typeParameter.annotations()
163-
)
164-
}
165-
),
166-
superclass = classes.singleOrNull()?.first?.let(::traversePsiClassForAncestorsAndInheritedMembers),
167-
interfaces = interfaces.map { traversePsiClassForAncestorsAndInheritedMembers(it.first) }
168+
typeConstructor = type,
169+
superclass = superclass?.let(::createAncestryNodeForPsiClassType),
170+
interfaces = interfaces.map { createAncestryNodeForPsiClassType(it) }
168171
)
169172
}
170173

171-
val ancestry: AncestryNode = traversePsiClassForAncestorsAndInheritedMembers(this)
174+
// Creates the AncestryNode for this class. The AncestryNodes for this class's supertypes will be done using
175+
// PsiClassTypes, not PsiClasses. This is important because the type parameters used in the class hierarchy
176+
// should reflect the usage in the extends/implements clause, not the type parameters in the supertype
177+
// class definitions, which the PsiClasses would use.
178+
val ancestry = createAncestryNode(
179+
type = GenericTypeConstructor(
180+
DRI.from(this),
181+
typeParameters.map { typeParameter ->
182+
TypeParameter(
183+
dri = DRI.from(typeParameter),
184+
name = typeParameter.name.orEmpty(),
185+
extra = typeParameter.annotations()
186+
)
187+
}
188+
),
189+
supertypes = superTypes.toList()
190+
)
172191

173192
val (regularFunctions, accessors) = splitFunctionsAndAccessors(psi.fields, psi.methods)
174193
val (regularSuperFunctions, superAccessors) = splitFunctionsAndAccessors(

dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt

+58
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
8080
}
8181
}
8282

83+
@Test fun allImplementedInterfacesWithGenericsInJava() {
84+
inlineModelTest(
85+
"""
86+
|interface Highest<H> { }
87+
|interface Lower<L> extends Highest<L> { }
88+
|class Extendable { }
89+
|class Tested<T> extends Extendable implements Lower<T> { }
90+
""", configuration = configuration){
91+
with((this / "java" / "Tested").cast<DClass>()){
92+
val implementedInterfaces = extra[ImplementedInterfaces]?.interfaces?.entries?.single()?.value!!
93+
implementedInterfaces.map { it.dri.sureClassNames }.sorted() equals listOf("Highest", "Lower").sorted()
94+
for (implementedInterface in implementedInterfaces) {
95+
// The type parameter T from Tested should be used for each interface, not the type parameters in
96+
// the interface definitions.
97+
assertEquals((implementedInterface.projections.single() as TypeParameter).name, "T")
98+
}
99+
}
100+
}
101+
}
102+
83103
@Test fun multipleClassInheritanceWithInterface() {
84104
inlineModelTest(
85105
"""
@@ -94,6 +114,25 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
94114
}
95115
}
96116

117+
@Test
118+
fun interfaceWithGeneric() {
119+
inlineModelTest(
120+
"""
121+
|interface Bar<T> {}
122+
|public class Foo implements Bar<String> {}
123+
""", configuration = configuration
124+
) {
125+
with((this / "java" / "Foo").cast<DClass>()) {
126+
val interfaceType = supertypes.values.flatten().single()
127+
assertEquals(interfaceType.kind, JavaClassKindTypes.INTERFACE)
128+
assertEquals(interfaceType.typeConstructor.dri.classNames, "Bar")
129+
// The interface type should be Bar<String>, and not use Bar<T> like the interface definition
130+
val generic = interfaceType.typeConstructor.projections.single() as GenericTypeConstructor
131+
assertEquals(generic.dri.classNames, "String")
132+
}
133+
}
134+
}
135+
97136
@Test
98137
fun superClass() {
99138
inlineModelTest(
@@ -110,6 +149,25 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
110149
}
111150
}
112151

152+
@Test
153+
fun superclassWithGeneric() {
154+
inlineModelTest(
155+
"""
156+
|class Bar<T> {}
157+
|public class Foo extends Bar<String> {}
158+
""", configuration = configuration
159+
) {
160+
with((this / "java" / "Foo").cast<DClass>()) {
161+
val superclassType = supertypes.values.flatten().single()
162+
assertEquals(superclassType.kind, JavaClassKindTypes.CLASS)
163+
assertEquals(superclassType.typeConstructor.dri.classNames, "Bar")
164+
// The superclass type should be Bar<String>, and not use Bar<T> like the class definition
165+
val generic = superclassType.typeConstructor.projections.single() as GenericTypeConstructor
166+
assertEquals(generic.dri.classNames, "String")
167+
}
168+
}
169+
}
170+
113171
@Test
114172
fun arrayType() {
115173
inlineModelTest(

0 commit comments

Comments
 (0)