K2: introduce even more readable renderer for types in messages
#KT-53343 Fixed
This commit is contained in:
+1
-1
@@ -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)
|
||||
}
|
||||
|
||||
+76
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+4
-29
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user