From e36091b926185ad8ddacdc13c615fdfedcdc1d6d Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Mon, 12 Sep 2022 16:47:12 +0200 Subject: [PATCH] K2: introduce even more readable renderer for types in messages #KT-53343 Fixed --- .../problems/argumentTypeMismatch.kt | 2 +- .../kotlin/fir/renderer/ConeTypeRenderer.kt | 42 ++++++---- .../ConeTypeRendererWithJavaFlexibleTypes.kt | 76 +++++++++++++++++++ .../kotlin/fir/types/ConeTypeUtils.kt | 5 +- .../compiler/visualizer/FirVisualizer.kt | 33 +------- .../kotlin/renderer/RenderingUtils.kt | 24 ++++++ .../kotlin/renderer/DescriptorRendererImpl.kt | 45 +++-------- 7 files changed, 148 insertions(+), 79 deletions(-) create mode 100644 compiler/fir/cones/src/org/jetbrains/kotlin/fir/renderer/ConeTypeRendererWithJavaFlexibleTypes.kt diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/argumentTypeMismatch.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/argumentTypeMismatch.kt index 696f1c6b14f..644abac2d8e 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/argumentTypeMismatch.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/problems/argumentTypeMismatch.kt @@ -9,5 +9,5 @@ public class Sample { // FILE: test.kt fun main() { - Sample.foo(, kotlin/collections/List?>>, kotlin/collections/List, kotlin/collections/List?>>?>; kotlin/String")!>"123") + Sample.foo(!>!; kotlin/String")!>"123") } diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/renderer/ConeTypeRenderer.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/renderer/ConeTypeRenderer.kt index 1a9b93e458b..5dfe2e44892 100644 --- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/renderer/ConeTypeRenderer.kt +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/renderer/ConeTypeRenderer.kt @@ -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) } diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/renderer/ConeTypeRendererWithJavaFlexibleTypes.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/renderer/ConeTypeRendererWithJavaFlexibleTypes.kt new file mode 100644 index 00000000000..0f9f2e69fb1 --- /dev/null +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/renderer/ConeTypeRendererWithJavaFlexibleTypes.kt @@ -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 -> (Mutable)List! + val simpleCollection = replacePrefixesInTypeRepresentations( + lowerRendered, + kotlinCollectionsPrefix + mutablePrefix, + upperRendered, + kotlinCollectionsPrefix, + "$kotlinCollectionsPrefix($mutablePrefix)" + ) + if (simpleCollection != null) return simpleCollection + // java.util.Map.Entry -> (Mutable)Map.(Mutable)Entry! + 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" + } +} \ No newline at end of file diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt index 44ef8ee0050..14947b72149 100644 --- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypeUtils.kt @@ -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() } diff --git a/compiler/visualizer/render-fir/src/org/jetbrains/kotlin/compiler/visualizer/FirVisualizer.kt b/compiler/visualizer/render-fir/src/org/jetbrains/kotlin/compiler/visualizer/FirVisualizer.kt index e4111496b9a..ec080bb392a 100644 --- a/compiler/visualizer/render-fir/src/org/jetbrains/kotlin/compiler/visualizer/FirVisualizer.kt +++ b/compiler/visualizer/render-fir/src/org/jetbrains/kotlin/compiler/visualizer/FirVisualizer.kt @@ -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, diff --git a/core/compiler.common/src/org/jetbrains/kotlin/renderer/RenderingUtils.kt b/core/compiler.common/src/org/jetbrains/kotlin/renderer/RenderingUtils.kt index 3d64ba10798..48027929da0 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/renderer/RenderingUtils.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/renderer/RenderingUtils.kt @@ -48,3 +48,27 @@ fun renderFqName(pathSegments: List): 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 + diff --git a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt index b30176ee904..59840f2499d 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/renderer/DescriptorRendererImpl.kt @@ -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 -> (Mutable)List! - 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 -> (Mutable)Map.(Mutable)Entry! - 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): String = if (typeArguments.isEmpty()) "" - else buildString { - append(lt()) - this.appendTypeProjections(typeArguments) - append(gt()) - } + override fun renderTypeArguments(typeArguments: List): 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() }