From e2531f212b47f407d4e697f7d7253a492190a3fe Mon Sep 17 00:00:00 2001 From: vmishenev Date: Wed, 10 Aug 2022 14:31:57 +0300 Subject: [PATCH] Fix generic type caching --- .../psi/DefaultPsiToDocumentableTranslator.kt | 56 ++++++++++++------- .../base/src/test/kotlin/model/JavaTest.kt | 37 ++++++++++++ 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index 3c1a2cc794..e94641a294 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -501,11 +501,21 @@ class DefaultPsiToDocumentableTranslator( } ?: PropertyContainer.empty() private fun getBound(type: PsiType): Bound { - fun bound() = when (type) { + //We would like to cache most of the bounds since it is not common to annotate them, + //but if this is the case, we treat them as 'one of' + fun PsiType.cacheBoundIfHasNoAnnotation(f: (List) -> Bound): Bound { + val annotations = this.annotations.toList().toListOfAnnotations() + return if (annotations.isNotEmpty()) f(annotations) + else cachedBounds.getOrPut(canonicalText) { + f(annotations) + } + } + + return when (type) { is PsiClassReferenceType -> type.resolve()?.let { resolved -> when { - resolved.qualifiedName == "java.lang.Object" -> JavaObject(type.annotations()) + resolved.qualifiedName == "java.lang.Object" -> type.cacheBoundIfHasNoAnnotation { annotations -> JavaObject(annotations.annotations()) } resolved is PsiTypeParameter -> { TypeParameter( dri = DRI.from(resolved), @@ -513,6 +523,7 @@ class DefaultPsiToDocumentableTranslator( extra = type.annotations() ) } + Regex("kotlin\\.jvm\\.functions\\.Function.*").matches(resolved.qualifiedName ?: "") || Regex("java\\.util\\.function\\.Function.*").matches( resolved.qualifiedName ?: "" @@ -521,33 +532,40 @@ class DefaultPsiToDocumentableTranslator( type.parameters.map { getProjection(it) }, extra = type.annotations() ) - else -> GenericTypeConstructor( - DRI.from(resolved), - type.parameters.map { getProjection(it) }, - extra = type.annotations() - ) + + else -> { + // cache types that has no annotation and no type parameter + val typeParameters = type.parameters.map { getProjection(it) } + if (typeParameters.isEmpty()) + type.cacheBoundIfHasNoAnnotation { annotations -> + GenericTypeConstructor( + DRI.from(resolved), + typeParameters, + extra = annotations.annotations() + ) + } + else + GenericTypeConstructor( + DRI.from(resolved), + typeParameters, + extra = type.annotations() + ) + } } } ?: UnresolvedBound(type.presentableText, type.annotations()) + is PsiArrayType -> GenericTypeConstructor( DRI("kotlin", "Array"), listOf(getProjection(type.componentType)), extra = type.annotations() ) + is PsiPrimitiveType -> if (type.name == "void") Void - else PrimitiveJavaType(type.name, type.annotations()) - is PsiImmediateClassType -> JavaObject(type.annotations()) + else type.cacheBoundIfHasNoAnnotation { annotations -> PrimitiveJavaType(type.name, annotations.annotations()) } + is PsiImmediateClassType -> + type.cacheBoundIfHasNoAnnotation { annotations -> JavaObject(annotations.annotations()) } else -> throw IllegalStateException("${type.presentableText} is not supported by PSI parser") } - - //We would like to cache most of the bounds since it is not common to annotate them, - //but if this is the case, we treat them as 'one of' - return if (type.annotations.toList().toListOfAnnotations().isEmpty()) { - cachedBounds.getOrPut(type.canonicalText) { - bound() - } - } else { - bound() - } } diff --git a/plugins/base/src/test/kotlin/model/JavaTest.kt b/plugins/base/src/test/kotlin/model/JavaTest.kt index 0abaf1b156..b497ea30e3 100644 --- a/plugins/base/src/test/kotlin/model/JavaTest.kt +++ b/plugins/base/src/test/kotlin/model/JavaTest.kt @@ -143,6 +143,43 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { ) { with((this / "java" / "Foo").cast()) { generics counts 1 + generics[0].dri.classNames equals "Foo" + (functions[0].type as? TypeParameter)?.dri?.run { + packageName equals "java" + name equals "Foo" + callable?.name equals "foo" + } + } + } + } + + @Test + fun typeParameterIntoInheritedClasses2596() { + inlineModelTest( + """ + |class GenericDocument { } + |public interface DocumentClassFactory { + | String getSchemaName(); + | GenericDocument toGenericDocument(T document); + | T fromGenericDocument(GenericDocument genericDoc); + |} + | + |public final class DocumentClassFactoryRegistry { + | public DocumentClassFactory getOrCreateFactory(T documentClass) { + | return null; + | } + |} + """, configuration = configuration + ) { + with((this / "java" / "DocumentClassFactory").cast()) { + generics counts 1 + generics[0].dri.classNames equals "DocumentClassFactory" + } + with((this / "java" / "DocumentClassFactoryRegistry").cast()) { + functions.forEach { + (it.type as GenericTypeConstructor).dri.classNames equals "DocumentClassFactory" + ((it.type as GenericTypeConstructor).projections[0] as TypeParameter).dri.classNames equals "DocumentClassFactoryRegistry" + } } } }