diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeRenderer.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeRenderer.kt index 0692ab9ce2e..3f9770b03ce 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeRenderer.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeRenderer.kt @@ -16,10 +16,16 @@ abstract class KtTypeRenderer : KtAnalysisSessionComponent() { data class KtTypeRendererOptions( val renderFqNames: Boolean, + val renderFunctionTypes: Boolean ) { companion object { val DEFAULT = KtTypeRendererOptions( - renderFqNames = true + renderFqNames = true, + renderFunctionTypes = true, + ) + val SHORT_NAMES = KtTypeRendererOptions( + renderFqNames = false, + renderFunctionTypes = true, ) } } @@ -33,9 +39,7 @@ class KtDefaultTypeRenderer(override val analysisSession: KtAnalysisSession, ove when (type) { is KtDenotableType -> when (type) { is KtClassType -> { - render(type.classId, options) - renderTypeArgumentsIfNotEmpty(type.typeArguments, options) - renderNullability(type.nullability) + renderClassType(type, options) } is KtTypeParameterType -> { append(type.name.asString()) @@ -64,6 +68,67 @@ class KtDefaultTypeRenderer(override val analysisSession: KtAnalysisSession, ove } } + private fun StringBuilder.renderClassType( + type: KtClassType, + options: KtTypeRendererOptions + ) { + when { + type is KtUsualClassType || !options.renderFunctionTypes -> { + renderClassTypeAsNonFunctional(type, options) + } + type is KtFunctionalType -> { + renderFunctionalType(type, options) + } + } + } + + private fun StringBuilder.renderClassTypeAsNonFunctional( + type: KtClassType, + options: KtTypeRendererOptions + ) { + render(type.classId, options) + renderTypeArgumentsIfNotEmpty(type.typeArguments, options) + renderNullability(type.nullability) + } + + private fun StringBuilder.renderFunctionalType( + type: KtFunctionalType, + options: KtTypeRendererOptions + ) { + wrapIf(type.nullability == KtTypeNullability.NULLABLE, left = "(", right = ")?") { + if (type.isSuspend) append("suspend ") + renderReceiverTypeOfFunctionalType(type, options) + renderParametersOfFunctionalType(type, options) + append(" -> ") + render(type.typeArguments.last(), options) + } + } + + private fun StringBuilder.renderReceiverTypeOfFunctionalType( + type: KtFunctionalType, + options: KtTypeRendererOptions + ) { + type.receiverType?.let { receiverType -> + render(receiverType, options) + append(".") + } + } + + private fun StringBuilder.renderParametersOfFunctionalType( + type: KtFunctionalType, + options: KtTypeRendererOptions + ) { + append("(") + val parameterTypes = type.parameterTypes + type.parameterTypes.forEachIndexed { index, parameterType -> + render(parameterType, options) + if (index != parameterTypes.lastIndex) { + append(", ") + } + } + append(")") + } + private fun StringBuilder.renderTypeArgumentsIfNotEmpty(typeArguments: List, options: KtTypeRendererOptions) { if (typeArguments.isNotEmpty()) { append("<") @@ -113,4 +178,10 @@ class KtDefaultTypeRenderer(override val analysisSession: KtAnalysisSession, ove render() append(")") } + + private inline fun StringBuilder.wrapIf(condition: Boolean, left: String, right: String, render: StringBuilder.() -> Unit) { + if (condition) append(left) + render() + if (condition) append(right) + } } \ No newline at end of file diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/types/KtTypeRendererTest.kt b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/types/KtTypeRendererTest.kt index 1f132b524fc..7af01d14f71 100644 --- a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/types/KtTypeRendererTest.kt +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/types/KtTypeRendererTest.kt @@ -83,7 +83,7 @@ class KtTypeRendererTest : KotlinLightCodeInsightFixtureTestCase() { type = "Map, V?>?", expected = "Map, V?>?", typeArguments = listOf("V"), - rendererOptions = KtTypeRendererOptions(renderFqNames = false) + rendererOptions = KtTypeRendererOptions.SHORT_NAMES ) } @@ -91,7 +91,55 @@ class KtTypeRendererTest : KotlinLightCodeInsightFixtureTestCase() { doTestByExpression( expression = "java.lang.String.CASE_INSENSITIVE_ORDER", expected = "(Comparator<(String..String?)>..Comparator<(String..String?)>?)", - rendererOptions = KtTypeRendererOptions(renderFqNames = false) + rendererOptions = KtTypeRendererOptions.SHORT_NAMES + ) + } + + fun testFunctionalType() { + doTestByTypeText( + type = "(String, Int?) -> Long", + expected = "(String, Int?) -> Long", + rendererOptions = KtTypeRendererOptions.SHORT_NAMES + ) + } + + fun testNullableFunctionalType() { + doTestByTypeText( + type = "((String, Int?) -> Long)?", + expected = "((String, Int?) -> Long)?", + rendererOptions = KtTypeRendererOptions.SHORT_NAMES + ) + } + + fun testFunctionalTypeNoArguments() { + doTestByTypeText( + type = "() -> Long", + expected = "() -> Long", + rendererOptions = KtTypeRendererOptions.SHORT_NAMES + ) + } + + fun testFunctionalTypeReceiver() { + doTestByTypeText( + type = "Int.(String) -> Long", + expected = "Int.(String) -> Long", + rendererOptions = KtTypeRendererOptions.SHORT_NAMES + ) + } + + fun testFunctionalTypeUnitReturnType() { + doTestByTypeText( + type = "(String) -> Unit", + expected = "(String) -> Unit", + rendererOptions = KtTypeRendererOptions.SHORT_NAMES + ) + } + + fun testFunctionalTypeRenderAsUsualClassType() { + doTestByTypeText( + type = "Int.(String, Long) -> Char", + expected = "Function3", + rendererOptions = KtTypeRendererOptions.SHORT_NAMES.copy(renderFunctionTypes = false) ) } @@ -103,7 +151,7 @@ class KtTypeRendererTest : KotlinLightCodeInsightFixtureTestCase() { if (x is String && x is Int) x else null }""".trimIndent(), expected = "(String?&Int?)", - rendererOptions = KtTypeRendererOptions(renderFqNames = false) + rendererOptions = KtTypeRendererOptions.SHORT_NAMES ) } } \ No newline at end of file