diff --git a/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt b/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt index 299bae3705..5270078948 100644 --- a/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt +++ b/dokka-subprojects/analysis-java-psi/src/main/kotlin/org/jetbrains/dokka/analysis/java/parsers/DokkaPsiParser.kt @@ -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.getSuperTypesPsiClasses(): List> { + fun List.getSuperclassAndInterfaces(): Pair> { forEach { type -> type.resolve()?.let { val definedAt = DRI.from(it) @@ -137,38 +133,61 @@ 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): AncestryNode { + fun createAncestryNodeForPsiClassType(psiClassType: PsiClassType): AncestryNode { + return createAncestryNode( + type = GenericTypeConstructor( + DRI.from(psiClassType.resolve()!!), + psiClassType.parameters.map { getProjection(it) } + ), + supertypes = psiClassType.superTypes.filterIsInstance() + ) + } + 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) + // Creates the AncestryNode for this class. The AncestryNodes for this class's supertypes will be done using + // PsiClassTypes, not PsiClasses. This is important because the type parameters used in the class hierarchy + // should reflect the usage in the extends/implements clause, not the type parameters in the supertype + // class definitions, which the PsiClasses would use. + 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( diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt index 5d76cc843a..32a207c149 100644 --- a/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt +++ b/dokka-subprojects/plugin-base/src/test/kotlin/model/JavaTest.kt @@ -80,6 +80,26 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { } } + @Test fun allImplementedInterfacesWithGenericsInJava() { + inlineModelTest( + """ + |interface Highest { } + |interface Lower extends Highest { } + |class Extendable { } + |class Tested extends Extendable implements Lower { } + """, configuration = configuration){ + with((this / "java" / "Tested").cast()){ + val implementedInterfaces = extra[ImplementedInterfaces]?.interfaces?.entries?.single()?.value!! + implementedInterfaces.map { it.dri.sureClassNames }.sorted() equals listOf("Highest", "Lower").sorted() + for (implementedInterface in implementedInterfaces) { + // The type parameter T from Tested should be used for each interface, not the type parameters in + // the interface definitions. + assertEquals((implementedInterface.projections.single() as TypeParameter).name, "T") + } + } + } + } + @Test fun multipleClassInheritanceWithInterface() { inlineModelTest( """ @@ -94,6 +114,25 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { } } + @Test + fun interfaceWithGeneric() { + inlineModelTest( + """ + |interface Bar {} + |public class Foo implements Bar {} + """, configuration = configuration + ) { + with((this / "java" / "Foo").cast()) { + val interfaceType = supertypes.values.flatten().single() + assertEquals(interfaceType.kind, JavaClassKindTypes.INTERFACE) + assertEquals(interfaceType.typeConstructor.dri.classNames, "Bar") + // The interface type should be Bar, and not use Bar like the interface definition + val generic = interfaceType.typeConstructor.projections.single() as GenericTypeConstructor + assertEquals(generic.dri.classNames, "String") + } + } + } + @Test fun superClass() { inlineModelTest( @@ -110,6 +149,25 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { } } + @Test + fun superclassWithGeneric() { + inlineModelTest( + """ + |class Bar {} + |public class Foo extends Bar {} + """, configuration = configuration + ) { + with((this / "java" / "Foo").cast()) { + val superclassType = supertypes.values.flatten().single() + assertEquals(superclassType.kind, JavaClassKindTypes.CLASS) + assertEquals(superclassType.typeConstructor.dri.classNames, "Bar") + // The superclass type should be Bar, and not use Bar like the class definition + val generic = superclassType.typeConstructor.projections.single() as GenericTypeConstructor + assertEquals(generic.dri.classNames, "String") + } + } + } + @Test fun arrayType() { inlineModelTest(