K2: introduce even more readable renderer for types in messages

#KT-53343 Fixed
This commit is contained in:
Mikhail Glukhikh
2022-09-12 16:47:12 +02:00
committed by Space
parent 3848be596e
commit e36091b926
7 changed files with 148 additions and 79 deletions
@@ -9,5 +9,5 @@ public class Sample {
// FILE: test.kt
fun main() {
Sample.foo(<!ARGUMENT_TYPE_MISMATCH("ft<kotlin/collections/MutableList<ft<kotlin/collections/MutableList<kotlin/String!>, kotlin/collections/List<kotlin/String!>?>>, kotlin/collections/List<ft<kotlin/collections/MutableList<kotlin/String!>, kotlin/collections/List<kotlin/String!>?>>?>; kotlin/String")!>"123"<!>)
Sample.foo(<!ARGUMENT_TYPE_MISMATCH("kotlin/collections/(Mutable)List<kotlin/collections/(Mutable)List<kotlin/String!>!>!; kotlin/String")!>"123"<!>)
}
@@ -15,12 +15,6 @@ open class ConeTypeRenderer() {
lateinit var builder: StringBuilder
lateinit var idRenderer: ConeIdRenderer
constructor(builder: StringBuilder, idRenderer: ConeIdRenderer) : this() {
this.builder = builder
this.idRenderer = idRenderer
idRenderer.builder = builder
}
open fun renderAsPossibleFunctionType(
type: ConeKotlinType, renderType: ConeTypeProjection.() -> Unit = { render() }
) {
@@ -83,30 +77,38 @@ open class ConeTypeRenderer() {
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()
render(type)
}
is ConeIntersectionType -> {
builder.append("it(")
for ((index, intersected) in type.intersectedTypes.withIndex()) {
@@ -117,18 +119,23 @@ open class ConeTypeRenderer() {
}
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")
}
@@ -151,9 +158,7 @@ open class ConeTypeRenderer() {
builder.append(">")
}
private fun ConeFlexibleType.render() {
val lowerBound = lowerBound
val upperBound = upperBound
private fun ConeFlexibleType.renderForSameLookupTags(): Boolean {
if (lowerBound is ConeLookupTagBasedType && upperBound is ConeLookupTagBasedType &&
lowerBound.lookupTag == upperBound.lookupTag &&
lowerBound.nullability == ConeNullability.NOT_NULL && upperBound.nullability == ConeNullability.NULLABLE
@@ -162,14 +167,21 @@ open class ConeTypeRenderer() {
if (upperBound !is ConeClassLikeType || upperBound.typeArguments.isEmpty()) {
render(lowerBound)
builder.append("!")
return
return true
}
}
}
return false
}
protected open fun render(flexibleType: ConeFlexibleType) {
if (flexibleType.renderForSameLookupTags()) {
return
}
builder.append("ft<")
render(lowerBound)
render(flexibleType.lowerBound)
builder.append(", ")
render(upperBound)
render(flexibleType.upperBound)
builder.append(">")
}
@@ -183,18 +195,22 @@ open class ConeTypeRenderer() {
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,76 @@
/*
* 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.StandardNames
import org.jetbrains.kotlin.fir.types.ConeFlexibleType
import org.jetbrains.kotlin.renderer.replacePrefixesInTypeRepresentations
import org.jetbrains.kotlin.renderer.typeStringsDifferOnlyInNullability
class ConeTypeRendererWithJavaFlexibleTypes : ConeTypeRenderer {
private val idRendererCreator: () -> ConeIdRenderer
@Suppress("ConvertSecondaryConstructorToPrimary")
constructor(builder: StringBuilder, idRendererCreator: () -> ConeIdRenderer) : super() {
this.builder = builder
this.idRendererCreator = idRendererCreator
this.idRenderer = idRendererCreator()
idRenderer.builder = builder
}
override fun render(flexibleType: ConeFlexibleType) {
val lowerRenderer = ConeTypeRendererWithJavaFlexibleTypes(StringBuilder(), idRendererCreator)
lowerRenderer.render(flexibleType.lowerBound)
val upperRenderer = ConeTypeRendererWithJavaFlexibleTypes(StringBuilder(), idRendererCreator)
upperRenderer.render(flexibleType.upperBound)
builder.append(renderFlexibleType(lowerRenderer.builder.toString(), upperRenderer.builder.toString()))
}
private fun renderFlexibleType(lowerRendered: String, upperRendered: String): String {
if (typeStringsDifferOnlyInNullability(lowerRendered, upperRendered)) {
if (upperRendered.startsWith("(")) {
// the case of complex type, e.g. (() -> Unit)?
return "($lowerRendered)!"
}
return "$lowerRendered!"
}
val kotlinCollectionsPrefix = StandardNames.COLLECTIONS_PACKAGE_FQ_NAME.asString().replace(".", "/") + "/"
val mutablePrefix = "Mutable"
// java.util.List<Foo> -> (Mutable)List<Foo!>!
val simpleCollection = replacePrefixesInTypeRepresentations(
lowerRendered,
kotlinCollectionsPrefix + mutablePrefix,
upperRendered,
kotlinCollectionsPrefix,
"$kotlinCollectionsPrefix($mutablePrefix)"
)
if (simpleCollection != null) return simpleCollection
// java.util.Map.Entry<Foo, Bar> -> (Mutable)Map.(Mutable)Entry<Foo!, Bar!>!
val mutableEntry = replacePrefixesInTypeRepresentations(
lowerRendered,
kotlinCollectionsPrefix + "MutableMap.MutableEntry",
upperRendered,
kotlinCollectionsPrefix + "Map.Entry",
"$kotlinCollectionsPrefix(Mutable)Map.(Mutable)Entry"
)
if (mutableEntry != null) return mutableEntry
val kotlinPrefix = StandardNames.BUILT_INS_PACKAGE_FQ_NAME.asString() + "/"
// Foo[] -> Array<(out) Foo!>!
val array = replacePrefixesInTypeRepresentations(
lowerRendered = lowerRendered,
lowerPrefix = kotlinPrefix + "Array<",
upperRendered = upperRendered,
upperPrefix = kotlinPrefix + "Array<out ",
foldedPrefix = kotlinPrefix + "Array<(out) "
)
if (array != null) return array
return "ft<$lowerRendered, $upperRendered>"
}
}
@@ -28,6 +28,7 @@ fun ConeKotlinType.forEachType(action: (ConeKotlinType) -> Unit) {
lowerBound.forEachType(action)
upperBound.forEachType(action)
}
is ConeDefinitelyNotNullType -> original.forEachType(action)
is ConeIntersectionType -> intersectedTypes.forEach { it.forEachType(action) }
else -> typeArguments.forEach { if (it is ConeKotlinTypeProjection) it.type.forEachType(action) }
@@ -126,13 +127,13 @@ fun ConeKotlinType.renderForDebugging(): String {
fun ConeKotlinType.renderReadable(): String {
val builder = StringBuilder()
ConeTypeRenderer(builder, idRenderer = ConeIdShortRenderer()).render(this)
ConeTypeRendererWithJavaFlexibleTypes(builder) { ConeIdShortRenderer() }.render(this)
return builder.toString()
}
fun ConeKotlinType.renderReadableWithFqNames(): String {
val builder = StringBuilder()
ConeTypeRenderer(builder, idRenderer = ConeIdRendererForDebugging()).render(this)
ConeTypeRendererWithJavaFlexibleTypes(builder) { ConeIdRendererForDebugging() }.render(this)
return builder.toString()
}
@@ -15,7 +15,6 @@ import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
import org.jetbrains.kotlin.fir.references.*
import org.jetbrains.kotlin.fir.renderer.ConeTypeRendererForDebugging
import org.jetbrains.kotlin.fir.renderer.FirPrinter
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
@@ -31,6 +30,7 @@ import org.jetbrains.kotlin.psi
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.renderer.replacePrefixesInTypeRepresentations
import org.jetbrains.kotlin.types.AbstractStrictEqualityTypeChecker
import org.jetbrains.kotlin.types.ConstantValueKind
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
@@ -342,33 +342,8 @@ class FirVisualizer(private val firFile: FirFile) : BaseRenderer() {
builder.append(projectionBuilder.toString().removeCurrentFilePackage())
}
// TODO rewrite or extract in common utils
private fun replacePrefixes(
lowerRendered: String,
lowerPrefix: String,
upperRendered: String,
upperPrefix: String,
foldedPrefix: String
): String? {
if (lowerRendered.startsWith(lowerPrefix) && upperRendered.startsWith(upperPrefix)) {
val lowerWithoutPrefix = lowerRendered.substring(lowerPrefix.length)
val upperWithoutPrefix = upperRendered.substring(upperPrefix.length)
val flexibleCollectionName = foldedPrefix + lowerWithoutPrefix
if (lowerWithoutPrefix == upperWithoutPrefix) return flexibleCollectionName
if (differsOnlyInNullability(lowerWithoutPrefix, upperWithoutPrefix)) {
return "$flexibleCollectionName!"
}
}
return null
}
private fun differsOnlyInNullability(lower: String, upper: String) =
lower == upper.replace("?", "") || upper.endsWith("?") && ("$lower?") == upper || "($lower)?" == upper
private fun tryToSquashFlexibleType(lowerRendered: String, upperRendered: String): String? {
val simpleCollection = replacePrefixes(
val simpleCollection = replacePrefixesInTypeRepresentations(
lowerRendered,
"kotlin/collections/Mutable",
upperRendered,
@@ -377,7 +352,7 @@ class FirVisualizer(private val firFile: FirFile) : BaseRenderer() {
)
if (simpleCollection != null) return simpleCollection
val mutableEntry = replacePrefixes(
val mutableEntry = replacePrefixesInTypeRepresentations(
lowerRendered,
"kotlin/collections/MutableMap.MutableEntry",
upperRendered,
@@ -386,7 +361,7 @@ class FirVisualizer(private val firFile: FirFile) : BaseRenderer() {
)
if (mutableEntry != null) return mutableEntry
val array = replacePrefixes(
val array = replacePrefixesInTypeRepresentations(
lowerRendered,
"kotlin/Array<",
upperRendered,
@@ -48,3 +48,27 @@ fun renderFqName(pathSegments: List<Name>): String {
}
}
fun replacePrefixesInTypeRepresentations(
lowerRendered: String,
lowerPrefix: String,
upperRendered: String,
upperPrefix: String,
foldedPrefix: String
): String? {
if (lowerRendered.startsWith(lowerPrefix) && upperRendered.startsWith(upperPrefix)) {
val lowerWithoutPrefix = lowerRendered.substring(lowerPrefix.length)
val upperWithoutPrefix = upperRendered.substring(upperPrefix.length)
val flexibleCollectionName = foldedPrefix + lowerWithoutPrefix
if (lowerWithoutPrefix == upperWithoutPrefix) return flexibleCollectionName
if (typeStringsDifferOnlyInNullability(lowerWithoutPrefix, upperWithoutPrefix)) {
return "$flexibleCollectionName!"
}
}
return null
}
fun typeStringsDifferOnlyInNullability(lower: String, upper: String) =
lower == upper.replace("?", "") || upper.endsWith("?") && ("$lower?") == upper || "($lower)?" == upper
@@ -184,7 +184,7 @@ internal class DescriptorRendererImpl(
}
override fun renderFlexibleType(lowerRendered: String, upperRendered: String, builtIns: KotlinBuiltIns): String {
if (differsOnlyInNullability(lowerRendered, upperRendered)) {
if (typeStringsDifferOnlyInNullability(lowerRendered, upperRendered)) {
if (upperRendered.startsWith("(")) {
// the case of complex type, e.g. (() -> Unit)?
return "($lowerRendered)!"
@@ -195,7 +195,7 @@ internal class DescriptorRendererImpl(
val kotlinCollectionsPrefix = classifierNamePolicy.renderClassifier(builtIns.collection, this).substringBefore("Collection")
val mutablePrefix = "Mutable"
// java.util.List<Foo> -> (Mutable)List<Foo!>!
val simpleCollection = replacePrefixes(
val simpleCollection = replacePrefixesInTypeRepresentations(
lowerRendered,
kotlinCollectionsPrefix + mutablePrefix,
upperRendered,
@@ -204,7 +204,7 @@ internal class DescriptorRendererImpl(
)
if (simpleCollection != null) return simpleCollection
// java.util.Map.Entry<Foo, Bar> -> (Mutable)Map.(Mutable)Entry<Foo!, Bar!>!
val mutableEntry = replacePrefixes(
val mutableEntry = replacePrefixesInTypeRepresentations(
lowerRendered,
kotlinCollectionsPrefix + "MutableMap.MutableEntry",
upperRendered,
@@ -215,7 +215,7 @@ internal class DescriptorRendererImpl(
val kotlinPrefix = classifierNamePolicy.renderClassifier(builtIns.array, this).substringBefore("Array")
// Foo[] -> Array<(out) Foo!>!
val array = replacePrefixes(
val array = replacePrefixesInTypeRepresentations(
lowerRendered,
kotlinPrefix + escape("Array<"),
upperRendered,
@@ -227,12 +227,13 @@ internal class DescriptorRendererImpl(
return "($lowerRendered..$upperRendered)"
}
override fun renderTypeArguments(typeArguments: List<TypeProjection>): String = if (typeArguments.isEmpty()) ""
else buildString {
append(lt())
this.appendTypeProjections(typeArguments)
append(gt())
}
override fun renderTypeArguments(typeArguments: List<TypeProjection>): String =
if (typeArguments.isEmpty()) ""
else buildString {
append(lt())
this.appendTypeProjections(typeArguments)
append(gt())
}
private fun StringBuilder.renderDefaultType(type: KotlinType) {
this.renderAnnotations(type)
@@ -1198,29 +1199,5 @@ internal class DescriptorRendererImpl(
}
}
private fun replacePrefixes(
lowerRendered: String,
lowerPrefix: String,
upperRendered: String,
upperPrefix: String,
foldedPrefix: String
): String? {
if (lowerRendered.startsWith(lowerPrefix) && upperRendered.startsWith(upperPrefix)) {
val lowerWithoutPrefix = lowerRendered.substring(lowerPrefix.length)
val upperWithoutPrefix = upperRendered.substring(upperPrefix.length)
val flexibleCollectionName = foldedPrefix + lowerWithoutPrefix
if (lowerWithoutPrefix == upperWithoutPrefix) return flexibleCollectionName
if (differsOnlyInNullability(lowerWithoutPrefix, upperWithoutPrefix)) {
return "$flexibleCollectionName!"
}
}
return null
}
private fun differsOnlyInNullability(lower: String, upper: String) =
lower == upper.replace("?", "") || upper.endsWith("?") && ("$lower?") == upper || "($lower)?" == upper
private fun overridesSomething(callable: CallableMemberDescriptor) = !callable.overriddenDescriptors.isEmpty()
}