FIR renderer: extract separate ConeTypeRenderer

This commit is contained in:
Mikhail Glukhikh
2022-07-05 11:56:50 +02:00
committed by Space
parent 591143be7c
commit a82baf87cb
46 changed files with 438 additions and 266 deletions
@@ -0,0 +1,199 @@
/*
* Copyright 2010-2022 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.fir.renderer
import org.jetbrains.kotlin.builtins.functions.FunctionClassKind
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
open class ConeTypeRenderer(protected val builder: StringBuilder) {
open fun renderAsPossibleFunctionType(
type: ConeKotlinType, renderType: ConeTypeProjection.() -> Unit = { render() }
) {
val kind = type.functionTypeKind
if (!kind.withPrettyRender()) {
type.renderType()
return
}
if (type.isMarkedNullable) {
builder.append("(")
}
if (kind == FunctionClassKind.SuspendFunction) {
builder.append("suspend ")
}
val typeArguments = type.typeArguments
val isExtension = type.isExtensionFunctionType
val (receiver, otherTypeArguments) = if (isExtension && typeArguments.first() != ConeStarProjection) {
typeArguments.first() to typeArguments.drop(1)
} else {
null to typeArguments.toList()
}
val arguments = otherTypeArguments.subList(0, otherTypeArguments.size - 1)
val returnType = otherTypeArguments.last()
if (receiver != null) {
receiver.render()
builder.append(".")
}
builder.append("(")
for ((index, argument) in arguments.withIndex()) {
if (index != 0) {
builder.append(", ")
}
argument.render()
}
builder.append(") -> ")
returnType.render()
if (type.isMarkedNullable) {
builder.append(")?")
}
}
@OptIn(ExperimentalContracts::class)
private fun FunctionClassKind?.withPrettyRender(): Boolean {
contract {
returns(true) implies (this@withPrettyRender != null)
}
return this != null && this != FunctionClassKind.KSuspendFunction && this != FunctionClassKind.KFunction
}
fun render(type: ConeKotlinType) {
if (type !is ConeFlexibleType && type !is ConeDefinitelyNotNullType) {
// We don't render attributes for flexible/definitely not null types here,
// because bounds duplicate these attributes often
type.renderAttributes()
}
when (type) {
is ConeTypeVariableType -> {
builder.append("TypeVariable(")
builder.append(type.lookupTag.name)
builder.append(")")
}
is ConeDefinitelyNotNullType -> {
render(type.original)
builder.append(" & Any")
}
is ConeErrorType -> {
builder.append("ERROR CLASS: ${type.diagnostic.reason}")
}
is ConeCapturedType -> {
builder.append("CapturedType(")
type.constructor.projection.render()
builder.append(")")
}
is ConeClassLikeType -> {
type.render()
}
is ConeLookupTagBasedType -> {
builder.append(type.lookupTag.name.asString())
}
is ConeDynamicType -> {
builder.append("dynamic")
}
is ConeFlexibleType -> {
type.render()
}
is ConeIntersectionType -> {
builder.append("it(")
for ((index, intersected) in type.intersectedTypes.withIndex()) {
if (index > 0) {
builder.append(" & ")
}
render(intersected)
}
builder.append(")")
}
is ConeStubTypeForSyntheticFixation -> {
builder.append("Stub (fixation): ${type.constructor.variable}")
}
is ConeStubTypeForChainInference -> {
builder.append("Stub (chain inference): ${type.constructor.variable}")
}
is ConeStubType -> {
builder.append("Stub (subtyping): ${type.constructor.variable}")
}
is ConeIntegerLiteralConstantType -> {
builder.append("ILT: ${type.value}")
}
is ConeIntegerConstantOperatorType -> {
builder.append("IOT")
}
}
if (type !is ConeFlexibleType && type !is ConeErrorType) {
builder.append(type.nullability.suffix)
}
}
private fun ConeClassLikeType.render() {
lookupTag.classId.render()
if (typeArguments.isEmpty()) return
builder.append("<")
for ((index, typeArgument) in typeArguments.withIndex()) {
if (index > 0) {
builder.append(", ")
}
typeArgument.render()
}
builder.append(">")
}
protected open fun ClassId.render() {
builder.append(relativeClassName.asString())
}
private fun ConeFlexibleType.render() {
val lowerBound = lowerBound
val upperBound = upperBound
if (lowerBound is ConeLookupTagBasedType && upperBound is ConeLookupTagBasedType &&
lowerBound.lookupTag == upperBound.lookupTag &&
lowerBound.nullability == ConeNullability.NOT_NULL && upperBound.nullability == ConeNullability.NULLABLE
) {
if (lowerBound !is ConeClassLikeType || lowerBound.typeArguments.isEmpty()) {
if (upperBound !is ConeClassLikeType || upperBound.typeArguments.isEmpty()) {
render(lowerBound)
builder.append("!")
return
}
}
}
builder.append("ft<")
render(lowerBound)
builder.append(", ")
render(upperBound)
builder.append(">")
}
private fun ConeKotlinType.renderAttributes() {
if (!attributes.any()) return
builder.append(attributes.joinToString(" ", postfix = " ") { it.toString() })
}
private fun ConeTypeProjection.render() {
when (this) {
ConeStarProjection -> {
builder.append("*")
}
is ConeKotlinTypeConflictingProjection -> {
builder.append("CONFLICTING-PROJECTION ")
render(type)
}
is ConeKotlinTypeProjectionIn -> {
builder.append("in ")
render(type)
}
is ConeKotlinTypeProjectionOut -> {
builder.append("out ")
render(type)
}
is ConeKotlinType -> {
render(this)
}
}
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2022 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.fir.renderer
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeProjection
class ConeTypeRendererForDebugging(builder: StringBuilder) : ConeTypeRendererWithFqNames(builder) {
override fun renderAsPossibleFunctionType(type: ConeKotlinType, renderType: ConeTypeProjection.() -> Unit) {
builder.append("R|")
super.renderAsPossibleFunctionType(type, renderType)
builder.append("|")
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2022 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.fir.renderer
import org.jetbrains.kotlin.name.ClassId
open class ConeTypeRendererWithFqNames(builder: StringBuilder) : ConeTypeRenderer(builder) {
override fun ClassId.render() {
builder.append(asString())
}
}
@@ -5,6 +5,10 @@
package org.jetbrains.kotlin.fir.types
import org.jetbrains.kotlin.builtins.functions.FunctionClassKind
import org.jetbrains.kotlin.fir.renderer.ConeTypeRenderer
import org.jetbrains.kotlin.fir.renderer.ConeTypeRendererForDebugging
import org.jetbrains.kotlin.fir.renderer.ConeTypeRendererWithFqNames
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.types.Variance
@@ -107,3 +111,30 @@ fun ConeClassLikeType.replaceArgumentsWithStarProjections(): ConeClassLikeType {
val newArguments = Array(typeArguments.size) { ConeStarProjection }
return withArguments(newArguments)
}
val ConeKotlinType?.functionTypeKind: FunctionClassKind?
get() {
val classId = (this as? ConeClassLikeType)?.lookupTag?.classId ?: return null
return FunctionClassKind.getFunctionalClassKind(
classId.shortClassName.asString(), classId.packageFqName
)
}
fun ConeKotlinType.renderForDebugging(): String {
val builder = StringBuilder()
ConeTypeRendererForDebugging(builder).render(this)
return builder.toString()
}
fun ConeKotlinType.renderReadable(): String {
val builder = StringBuilder()
ConeTypeRenderer(builder).render(this)
return builder.toString()
}
fun ConeKotlinType.renderReadableWithFqNames(): String {
val builder = StringBuilder()
ConeTypeRendererWithFqNames(builder).render(this)
return builder.toString()
}
@@ -28,7 +28,7 @@ sealed class ConeKotlinType : ConeKotlinTypeProjection(), KotlinTypeMarker, Type
abstract val attributes: ConeAttributes
final override fun toString(): String {
return render()
return renderForDebugging()
}
abstract override fun equals(other: Any?): Boolean
@@ -160,7 +160,7 @@ data class ConeCapturedType(
}
override fun hashCode(): Int {
var result = 0
var result = 7
result = 31 * result + (lowerType?.hashCode() ?: 0)
result = 31 * result + constructor.projection.hashCode()
result = 31 * result + constructor.typeParameterMarker.hashCode()
@@ -1,129 +0,0 @@
/*
* Copyright 2010-2019 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.fir.types
import org.jetbrains.kotlin.builtins.functions.FunctionClassKind
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
fun ConeKotlinType.render(renderFqNames: Boolean = true): String {
val nullabilitySuffix = if (this !is ConeFlexibleType && this !is ConeErrorType) nullability.suffix else ""
return when (this) {
is ConeTypeVariableType -> "${renderAttributes()}TypeVariable(${this.lookupTag.name})"
is ConeDefinitelyNotNullType -> "${original.render(renderFqNames)} & Any"
is ConeErrorType -> "${renderAttributes()}ERROR CLASS: ${diagnostic.reason}"
is ConeCapturedType -> "${renderAttributes()}CapturedType(${constructor.projection.render(renderFqNames)})"
is ConeClassLikeType -> {
buildString {
append(renderAttributes())
if (renderFqNames) {
append(lookupTag.classId.asString())
} else {
append(lookupTag.classId.relativeClassName.asString())
}
if (typeArguments.isNotEmpty()) {
append(typeArguments.joinToString(prefix = "<", postfix = ">") {
it.render(renderFqNames)
})
}
}
}
is ConeLookupTagBasedType -> {
"${renderAttributes()}${lookupTag.name.asString()}"
}
is ConeDynamicType -> "dynamic"
is ConeFlexibleType -> this.render(renderFqNames)
is ConeIntersectionType -> {
intersectedTypes.joinToString(
separator = " & ",
prefix = "${renderAttributes()}it(",
postfix = ")"
) {
it.render(renderFqNames)
}
}
is ConeStubTypeForSyntheticFixation -> "${renderAttributes()}Stub (fixation): ${constructor.variable}"
is ConeStubTypeForChainInference -> "${renderAttributes()}Stub (chain inference): ${constructor.variable}"
is ConeStubType -> "${renderAttributes()}Stub (subtyping): ${constructor.variable}"
is ConeIntegerLiteralConstantType -> "${renderAttributes()}ILT: $value"
is ConeIntegerConstantOperatorType -> "${renderAttributes()}IOT"
} + nullabilitySuffix
}
private fun ConeFlexibleType.render(renderFqNames: Boolean): String {
if (lowerBound is ConeLookupTagBasedType && upperBound is ConeLookupTagBasedType &&
lowerBound.lookupTag == upperBound.lookupTag &&
lowerBound.nullability == ConeNullability.NOT_NULL && upperBound.nullability == ConeNullability.NULLABLE
) {
if (lowerBound !is ConeClassLikeType || lowerBound.typeArguments.isEmpty()) {
if (upperBound !is ConeClassLikeType || upperBound.typeArguments.isEmpty()) {
return buildString {
append(lowerBound.render(renderFqNames))
append("!")
}
}
}
}
return buildString {
append("ft<")
append(lowerBound.render(renderFqNames))
append(", ")
append(upperBound.render(renderFqNames))
append(">")
}
}
private fun ConeKotlinType.renderAttributes(): String {
if (!attributes.any()) return ""
return attributes.joinToString(" ", postfix = " ") { it.toString() }
}
fun ConeTypeProjection.render(renderFqNames: Boolean): String {
return when (this) {
ConeStarProjection -> "*"
is ConeKotlinTypeConflictingProjection -> "CONFLICTING-PROJECTION ${type.render(renderFqNames)}"
is ConeKotlinTypeProjectionIn -> "in ${type.render(renderFqNames)}"
is ConeKotlinTypeProjectionOut -> "out ${type.render(renderFqNames)}"
is ConeKotlinType -> render(renderFqNames)
}
}
fun ConeKotlinType.renderFunctionType(
kind: FunctionClassKind?, renderFqNames: Boolean, renderType: ConeTypeProjection.() -> String = { render(renderFqNames) }
): String {
if (!kind.withPrettyRender()) return renderType()
val isExtension = isExtensionFunctionType
val renderedType = buildString {
if (kind == FunctionClassKind.SuspendFunction) {
append("suspend ")
}
val (receiver, otherTypeArguments) = if (isExtension && typeArguments.first() != ConeStarProjection) {
typeArguments.first() to typeArguments.drop(1)
} else {
null to typeArguments.toList()
}
val arguments = otherTypeArguments.subList(0, otherTypeArguments.size - 1)
val returnType = otherTypeArguments.last()
if (receiver != null) {
append(receiver.renderType())
append(".")
}
append(arguments.joinToString(", ", "(", ")") { it.renderType() })
append(" -> ")
append(returnType.renderType())
}
return if (isMarkedNullable) "($renderedType)?" else renderedType
}
@OptIn(ExperimentalContracts::class)
fun FunctionClassKind?.withPrettyRender(): Boolean {
contract {
returns(true) implies (this@withPrettyRender != null)
}
return this != null && this != FunctionClassKind.KSuspendFunction && this != FunctionClassKind.KFunction
}