FIR IDE: introduce KtType rendering
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.frontend.api.types
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.*
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
|
||||||
|
object KtTypeRenderer {
|
||||||
|
fun render(type: KtType, options: KtTypeRendererOptions = KtTypeRendererOptions.DEFAULT): String = type.withValidityAssertion {
|
||||||
|
buildString { render(type, options) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.render(type: KtType, options: KtTypeRendererOptions) {
|
||||||
|
when (type) {
|
||||||
|
is KtDenotableType -> when (type) {
|
||||||
|
is KtClassType -> {
|
||||||
|
render(type.classId, options)
|
||||||
|
renderTypeArgumentsIfNotEmpty(type.typeArguments, options)
|
||||||
|
renderNullability(type.nullability)
|
||||||
|
}
|
||||||
|
is KtTypeParameterType -> {
|
||||||
|
append(type.name.asString())
|
||||||
|
renderNullability(type.nullability)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is KtNonDenotableType -> when (type) {
|
||||||
|
is KtFlexibleType -> inParens {
|
||||||
|
render(type.lowerBound, options)
|
||||||
|
append("..")
|
||||||
|
render(type.upperBound, options)
|
||||||
|
}
|
||||||
|
is KtIntersectionType -> inParens {
|
||||||
|
type.conjuncts.forEachIndexed { index, conjunct ->
|
||||||
|
render(conjunct, options)
|
||||||
|
if (index != type.conjuncts.lastIndex) {
|
||||||
|
append("&")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is KtErrorType -> {
|
||||||
|
append(type.error)
|
||||||
|
}
|
||||||
|
else -> error("Unsupported type ${type::class}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.renderTypeArgumentsIfNotEmpty(typeArguments: List<KtTypeArgument>, options: KtTypeRendererOptions) {
|
||||||
|
if (typeArguments.isNotEmpty()) {
|
||||||
|
append("<")
|
||||||
|
typeArguments.forEachIndexed { index, typeArgument ->
|
||||||
|
render(typeArgument, options)
|
||||||
|
if (index != typeArguments.lastIndex) {
|
||||||
|
append(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append(">")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.render(typeArgument: KtTypeArgument, options: KtTypeRendererOptions) {
|
||||||
|
when (typeArgument) {
|
||||||
|
KtStarProjectionTypeArgument -> {
|
||||||
|
append("*")
|
||||||
|
}
|
||||||
|
is KtTypeArgumentWithVariance -> {
|
||||||
|
val varianceWithSpace = when (typeArgument.variance) {
|
||||||
|
KtTypeArgumentVariance.COVARIANT -> "out "
|
||||||
|
KtTypeArgumentVariance.CONTRAVARIANT -> "in "
|
||||||
|
KtTypeArgumentVariance.INVARIANT -> ""
|
||||||
|
}
|
||||||
|
append(varianceWithSpace)
|
||||||
|
render(typeArgument.type, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.renderNullability(nullability: KtTypeNullability) {
|
||||||
|
if (nullability == KtTypeNullability.NULLABLE) {
|
||||||
|
append("?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.render(classId: ClassId, options: KtTypeRendererOptions) {
|
||||||
|
if (options.renderFqNames) {
|
||||||
|
append(classId.asString().replace('/', '.'))
|
||||||
|
} else {
|
||||||
|
append(classId.shortClassName.asString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun StringBuilder.inParens(render: StringBuilder.() -> Unit) {
|
||||||
|
append("(")
|
||||||
|
render()
|
||||||
|
append(")")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class KtTypeRendererOptions(
|
||||||
|
val renderFqNames: Boolean,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = KtTypeRendererOptions(
|
||||||
|
renderFqNames = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.frontend.api.types
|
||||||
|
|
||||||
|
import com.intellij.testFramework.LightProjectDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtFunction
|
||||||
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
|
|
||||||
|
class KtTypeRendererTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||||
|
private fun doTestByTypeText(
|
||||||
|
type: String,
|
||||||
|
expected: String,
|
||||||
|
typeArguments: List<String> = emptyList(),
|
||||||
|
rendererOptions: KtTypeRendererOptions = KtTypeRendererOptions.DEFAULT,
|
||||||
|
) {
|
||||||
|
val typeArgumentsRendered = typeArguments
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
?.joinToString(prefix = "<", postfix = ">")
|
||||||
|
.orEmpty()
|
||||||
|
val fakeKtFile = myFixture.configureByText("file.kt", "fun ${typeArgumentsRendered}foo(): $type = 1") as KtFile
|
||||||
|
val property = fakeKtFile.declarations.single() as KtFunction
|
||||||
|
val renderedType = executeOnPooledThreadInReadAction {
|
||||||
|
analyze(fakeKtFile) {
|
||||||
|
val ktType = getReturnTypeForKtDeclaration(property)
|
||||||
|
KtTypeRenderer.render(ktType, rendererOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(expected, renderedType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doTestByExpression(
|
||||||
|
expression: String,
|
||||||
|
expected: String,
|
||||||
|
rendererOptions: KtTypeRendererOptions = KtTypeRendererOptions.DEFAULT,
|
||||||
|
) {
|
||||||
|
val fakeKtFile = myFixture.configureByText("file.kt", "val a = $expression") as KtFile
|
||||||
|
val property = fakeKtFile.declarations.single() as KtProperty
|
||||||
|
val renderedType = executeOnPooledThreadInReadAction {
|
||||||
|
analyze(fakeKtFile) {
|
||||||
|
val ktType = getReturnTypeForKtDeclaration(property)
|
||||||
|
KtTypeRenderer.render(ktType, rendererOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(expected, renderedType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProjectDescriptor(): LightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||||
|
|
||||||
|
fun testInt() {
|
||||||
|
doTestByTypeText(type = "Int", expected = "kotlin.Int")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testList() {
|
||||||
|
doTestByTypeText(type = "List<Int>", expected = "kotlin.collections.List<kotlin.Int>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testTypeArguments() {
|
||||||
|
doTestByTypeText(type = "Map<Int, V>", expected = "kotlin.collections.Map<kotlin.Int, V>", typeArguments = listOf("V"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testNullable() {
|
||||||
|
doTestByTypeText(
|
||||||
|
type = "Map<List<Int?>, V?>?",
|
||||||
|
expected = "kotlin.collections.Map<kotlin.collections.List<kotlin.Int?>, V?>?",
|
||||||
|
typeArguments = listOf("V")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testNoFqNames() {
|
||||||
|
doTestByTypeText(
|
||||||
|
type = "Map<List<Int?>, V?>?",
|
||||||
|
expected = "Map<List<Int?>, V?>?",
|
||||||
|
typeArguments = listOf("V"),
|
||||||
|
rendererOptions = KtTypeRendererOptions(renderFqNames = false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testFlexibleType() {
|
||||||
|
doTestByExpression(
|
||||||
|
expression = "java.lang.String.CASE_INSENSITIVE_ORDER",
|
||||||
|
expected = "(Comparator<(String..String?)>..Comparator<(String..String?)>?)",
|
||||||
|
rendererOptions = KtTypeRendererOptions(renderFqNames = false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testIntersectionType() {
|
||||||
|
doTestByExpression(
|
||||||
|
expression = """
|
||||||
|
run {
|
||||||
|
val x: Any = 10
|
||||||
|
if (x is String && x is Int) x else null
|
||||||
|
}""".trimIndent(),
|
||||||
|
expected = "(String?&Int?)",
|
||||||
|
rendererOptions = KtTypeRendererOptions(renderFqNames = false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user