Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix generic types caching #2619

Merged
merged 2 commits into from Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -501,18 +501,29 @@ 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<Annotations.Annotation>) -> 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),
name = resolved.name.orEmpty(),
extra = type.annotations()
)
}

Regex("kotlin\\.jvm\\.functions\\.Function.*").matches(resolved.qualifiedName ?: "") ||
Regex("java\\.util\\.function\\.Function.*").matches(
resolved.qualifiedName ?: ""
Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: this comments explains the code, but doesn't explain the intention behind it, so it's not really clear why we can't cache type parameters. Describing a use case where this can hurt would be nice

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()
}
}


Expand Down
37 changes: 37 additions & 0 deletions plugins/base/src/test/kotlin/model/JavaTest.kt
Expand Up @@ -143,6 +143,43 @@ class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") {
) {
with((this / "java" / "Foo").cast<DClass>()) {
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 typeParameterIntoDifferentClasses2596() {
inlineModelTest(
"""
|class GenericDocument { }
|public interface DocumentClassFactory<T> {
| String getSchemaName();
| GenericDocument toGenericDocument(T document);
| T fromGenericDocument(GenericDocument genericDoc);
|}
|
|public final class DocumentClassFactoryRegistry {
| public <T> DocumentClassFactory<T> getOrCreateFactory(T documentClass) {
| return null;
| }
|}
""", configuration = configuration
) {
with((this / "java" / "DocumentClassFactory").cast<DInterface>()) {
generics counts 1
generics[0].dri.classNames equals "DocumentClassFactory"
}
with((this / "java" / "DocumentClassFactoryRegistry").cast<DClass>()) {
functions.forEach {
(it.type as GenericTypeConstructor).dri.classNames equals "DocumentClassFactory"
((it.type as GenericTypeConstructor).projections[0] as TypeParameter).dri.classNames equals "DocumentClassFactoryRegistry"
}
}
}
}
Expand Down