diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirName.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirName.kt index f7b0effaf1b..d29793e3c48 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirName.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/cir/CirName.kt @@ -95,7 +95,10 @@ class CirEntityId private constructor(val packageName: CirPackageName, val relat else -> false } - override fun hashCode(): Int = hashCode(packageName).appendHashCode(relativeNameSegments) + private var _hashCode: Int = 0 + + override fun hashCode(): Int = if (_hashCode != 0) _hashCode else + hashCode(packageName).appendHashCode(relativeNameSegments).also { hashCode -> _hashCode = hashCode } override fun toString(): String = buildString { packageName.segments.joinTo(this, "/") diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt index 6c3787fe45e..4ad239c243c 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt @@ -11,12 +11,14 @@ import org.jetbrains.kotlin.commonizer.cir.CirTypeAliasType import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers internal class ClassOrTypeAliasTypeCommonizer( - private val classifiers: CirKnownClassifiers + private val classifiers: CirKnownClassifiers, + private val options: TypeCommonizer.Options ) : AssociativeCommonizer { override fun commonize(first: CirClassOrTypeAliasType, second: CirClassOrTypeAliasType): CirClassOrTypeAliasType? { if (first is CirClassType && second is CirClassType) { return ClassTypeCommonizer(classifiers).commonize(listOf(first, second)) + ?: if (options.allowOptimisticNumberTypeCommonization) OptimisticNumbersTypeCommonizer.commonize(first, second) else null } if (first is CirTypeAliasType && second is CirTypeAliasType) { @@ -25,7 +27,8 @@ internal class ClassOrTypeAliasTypeCommonizer( try our luck with commonizing those class types */ return TypeAliasTypeCommonizer(classifiers).commonize(listOf(first, second)) - ?: ClassTypeCommonizer(classifiers).commonize(listOf(first.expandedType(), second.expandedType())) + ?: ClassOrTypeAliasTypeCommonizer(classifiers, options.withAllowOptimisticNumberTypeCommonization()) + .commonize(first.expandedType(), second.expandedType()) } val classType = when { @@ -47,7 +50,7 @@ internal class ClassOrTypeAliasTypeCommonizer( val typeAliasClassType = TypeAliasTypeCommonizer(classifiers).commonize(listOf(typeAliasType))?.expandedType() ?: typeAliasType.expandedType() - return ClassTypeCommonizer(classifiers).commonize(listOf(classType, typeAliasClassType)) + return commonize(classType, typeAliasClassType) } } diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt new file mode 100644 index 00000000000..785da516c3b --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/OptimisticNumbersTypeCommonizer.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2021 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.commonizer.core + +import org.jetbrains.kotlin.commonizer.cir.CirClassType +import org.jetbrains.kotlin.commonizer.cir.CirEntityId + +private typealias BitWidth = Int + +private class SubstitutableNumbers(private val numbers: Map) { + fun choose(first: CirClassType, second: CirClassType): CirClassType? { + val firstBitWidth = numbers[first.classifierId] ?: return null + val secondBitWidth = numbers[second.classifierId] ?: return null + return if (secondBitWidth > firstBitWidth) second else first + } +} + +private val signedIntegers = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlin/Byte") to 8, + CirEntityId.create("kotlin/Short") to 16, + CirEntityId.create("kotlin/Int") to 32, + CirEntityId.create("kotlin/Long") to 64 + ) +) + +private val unsignedIntegers = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlin/UByte") to 8, + CirEntityId.create("kotlin/UShort") to 16, + CirEntityId.create("kotlin/UInt") to 32, + CirEntityId.create("kotlin/ULong") to 64 + ) +) + +private val floatingPoints = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlin/Float") to 32, + CirEntityId.create("kotlin/Double") to 64, + ) +) + +private val signedVarIntegers = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlinx/cinterop/ByteVarOf") to 8, + CirEntityId.create("kotlinx/cinterop/ShortVarOf") to 16, + CirEntityId.create("kotlinx/cinterop/IntVarOf") to 32, + CirEntityId.create("kotlinx/cinterop/LongVarOf") to 64, + ) +) + +private val unsignedVarIntegers = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlinx/cinterop/UByteVarOf") to 8, + CirEntityId.create("kotlinx/cinterop/UShortVarOf") to 16, + CirEntityId.create("kotlinx/cinterop/UIntVarOf") to 32, + CirEntityId.create("kotlinx/cinterop/ULongVarOf") to 64, + ) +) + +private val floatingPointVars = SubstitutableNumbers( + mapOf( + CirEntityId.create("kotlinx/cinterop/FloatVarOf") to 32, + CirEntityId.create("kotlinx/cinterop/DoubleVarOf") to 64, + ) +) + +object OptimisticNumbersTypeCommonizer : AssociativeCommonizer { + override fun commonize(first: CirClassType, second: CirClassType): CirClassType? { + return signedIntegers.choose(first, second) + ?: unsignedIntegers.choose(first, second) + ?: floatingPoints.choose(first, second) + ?: signedVarIntegers.choose(first, second) + ?: unsignedVarIntegers.choose(first, second) + ?: floatingPointVars.choose(first, second) + } +} \ No newline at end of file diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt index 9e96026272d..1589e68d54e 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt @@ -5,7 +5,8 @@ package org.jetbrains.kotlin.commonizer.core -import org.jetbrains.kotlin.commonizer.cir.* +import org.jetbrains.kotlin.commonizer.cir.CirClassOrTypeAliasType +import org.jetbrains.kotlin.commonizer.cir.CirTypeAlias import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers class TypeAliasCommonizer( @@ -18,7 +19,7 @@ class TypeAliasCommonizer( val typeParameters = TypeParameterListCommonizer(classifiers) .commonize(listOf(first.typeParameters, second.typeParameters)) ?: return null - val underlyingType = TypeCommonizer(classifiers) + val underlyingType = TypeCommonizer(classifiers, TypeCommonizer.Options.default.withAllowOptimisticNumberTypeCommonization()) .commonize(first.underlyingType, second.underlyingType) as? CirClassOrTypeAliasType ?: return null val visibility = VisibilityCommonizer.lowering().commonize(listOf(first, second)) ?: return null @@ -32,4 +33,4 @@ class TypeAliasCommonizer( expandedType = underlyingType.expandedType() ) } -} \ No newline at end of file +} diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt index 36465234958..bd6a2fa4457 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt @@ -8,10 +8,14 @@ package org.jetbrains.kotlin.commonizer.core import org.jetbrains.kotlin.commonizer.cir.* import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers -class TypeCommonizer(private val classifiers: CirKnownClassifiers) : AssociativeCommonizer { + +class TypeCommonizer( + private val classifiers: CirKnownClassifiers, + private val options: Options = Options.default +) : AssociativeCommonizer { override fun commonize(first: CirType, second: CirType): CirType? { if (first is CirClassOrTypeAliasType && second is CirClassOrTypeAliasType) { - return ClassOrTypeAliasTypeCommonizer(classifiers).commonize(first, second) + return ClassOrTypeAliasTypeCommonizer(classifiers, options).commonize(first, second) } if (first is CirTypeParameterType && second is CirTypeParameterType) { @@ -19,11 +23,25 @@ class TypeCommonizer(private val classifiers: CirKnownClassifiers) : Associative } if (first is CirFlexibleType && second is CirFlexibleType) { - return FlexibleTypeAssociativeCommonizer(classifiers).commonize(first, second) + return FlexibleTypeAssociativeCommonizer(classifiers, options).commonize(first, second) } return null } + + data class Options( + val allowOptimisticNumberTypeCommonization: Boolean = false + ) { + + fun withAllowOptimisticNumberTypeCommonization(): Options { + return if (allowOptimisticNumberTypeCommonization) this + else copy(allowOptimisticNumberTypeCommonization = true) + } + + companion object { + val default = Options() + } + } } private object TypeParameterTypeCommonizer : AssociativeCommonizer { @@ -35,11 +53,14 @@ private object TypeParameterTypeCommonizer : AssociativeCommonizer { +private class FlexibleTypeAssociativeCommonizer( + private val classifiers: CirKnownClassifiers, + private val options: TypeCommonizer.Options +) : AssociativeCommonizer { override fun commonize(first: CirFlexibleType, second: CirFlexibleType): CirFlexibleType? { - val lowerBound = TypeCommonizer(classifiers).commonize(first.lowerBound, second.lowerBound) ?: return null - val upperBound = TypeCommonizer(classifiers).commonize(first.upperBound, second.upperBound) ?: return null + val lowerBound = TypeCommonizer(classifiers, options).commonize(first.lowerBound, second.lowerBound) ?: return null + val upperBound = TypeCommonizer(classifiers, options).commonize(first.upperBound, second.upperBound) ?: return null return CirFlexibleType( lowerBound = lowerBound as CirSimpleType, diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/AliasedNumberTypeCommonizationTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/AliasedNumberTypeCommonizationTest.kt new file mode 100644 index 00000000000..efd593e2494 --- /dev/null +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/AliasedNumberTypeCommonizationTest.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2010-2021 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.commonizer.hierarchical + +import org.jetbrains.kotlin.commonizer.AbstractInlineSourcesCommonizationTest +import org.jetbrains.kotlin.commonizer.assertCommonized +import org.jetbrains.kotlin.commonizer.utils.InlineSourceBuilder + +class AliasedNumberTypeCommonizationTest : AbstractInlineSourcesCommonizationTest() { + + fun `test Int and Long - typealias`() { + val result = commonize { + outputTarget("(a, b)") + simpleSingleSourceTarget("a", "typealias X = Int") + simpleSingleSourceTarget("b", "typealias X = Long") + } + + result.assertCommonized("(a, b)", "typealias X = Long") + } + + fun `test UInt and ULong - typealias`() { + val result = commonize { + outputTarget("(a, b)") + registerDependency("a", "b", "(a, b)") { unsignedIntegers() } + simpleSingleSourceTarget("a", "typealias X = UInt") + simpleSingleSourceTarget("b", "typealias X = ULong") + } + + result.assertCommonized("(a, b)", "typealias X = ULong") + } + + + fun `test int and long - chain - typealias`() { + val result = commonize { + outputTarget("(a, b)") + + simpleSingleSourceTarget( + "a", """ + typealias A = Int + typealias B = A + typealias C = B + typealias X = C + """.trimIndent() + ) + + simpleSingleSourceTarget( + "b", """ + typealias A = Long + typealias B = A + typealias X = B + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + typealias A = Long + typealias B = A + typealias X = Long + """.trimIndent() + ) + } + + fun `test function with pure number types parameter`() { + val result = commonize { + outputTarget("(a, b)") + simpleSingleSourceTarget("a", "fun x(p: Int)") + simpleSingleSourceTarget("b", "fun x(p: Long)") + } + + /* + Only functions that use a TA in their signature are supposed to be + commonized using our number's commonization hack. + + This is a hard requirement. It would also be reasonable if we would add + support for this case, since there would be reasonable code that people + could write with this! + */ + result.assertCommonized("(a, b)", "") + } + + fun `test function with aliased number value parameter`() { + val result = commonize { + outputTarget("(a, b)") + simpleSingleSourceTarget( + "a", """ + typealias A = Int + typealias X = A + fun x(p: X) + """.trimIndent() + ) + simpleSingleSourceTarget( + "b", """ + typealias B = Long + typealias X = B + fun x(p: X) + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + typealias X = Long + expect fun x(p: X) + """.trimIndent() + ) + } + + fun `test property with pure number return type`() { + val result = commonize { + outputTarget("(a, b)") + registerDependency("a", "b", "(a, b)") { unsignedIntegers() } + simpleSingleSourceTarget("a", "val x: UInt") + simpleSingleSourceTarget("b", "val x: ULong") + } + + /* + Only commonize return types that were specified using a type-alias. + As with function value parameters, this is not a hard requirement. + It would also be reasonable to support this case. + */ + result.assertCommonized("(a, b)", "") + } + + fun `test property with aliased number return type`() { + val result = commonize { + outputTarget("(a, b)") + registerDependency("a", "b", "(a, b)") { unsignedIntegers() } + simpleSingleSourceTarget( + "a", """ + typealias X = UShort + val x: X = TODO() + """.trimIndent() + ) + simpleSingleSourceTarget( + "b", """ + typealias X = ULong + val x: X = TODO() + """.trimIndent() + ) + } + + result.assertCommonized( + "(a, b)", """ + typealias X = ULong + expect val x: X + """.trimIndent() + ) + } +} + +private fun InlineSourceBuilder.ModuleBuilder.unsignedIntegers() { + source( + """ + package kotlin + class UByte + class UShort + class UInt + class ULong + """.trimIndent(), "unsigned.kt" + ) +} \ No newline at end of file diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt index d7b0ca7511c..9d37157a9c2 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/HierarchicalClassAndTypeAliasCommonizationTest.kt @@ -33,13 +33,22 @@ class HierarchicalClassAndTypeAliasCommonizationTest : AbstractInlineSourcesComm fun `test commonization of typeAlias and class hierarchically`() { val result = commonize { outputTarget("(a, b)", "(c, d)", "(a, b, c, d)") - simpleSingleSourceTarget("a", "typealias X = Int") - simpleSingleSourceTarget("b", "typealias X = Long") + registerDependency("a", "b", "c", "d", "(a, b)", "(c, d)", "(a, b, c, d)") { + source( + """ + interface A + interface B: A + interface C: A + """.trimIndent() + ) + } + simpleSingleSourceTarget("a", "typealias X = B") + simpleSingleSourceTarget("b", "typealias X = C") simpleSingleSourceTarget("c", "class X") simpleSingleSourceTarget("d", "typealias X = Short") } - result.assertCommonized("(a, b)", "expect class X: Number") + result.assertCommonized("(a, b)", "expect class X: A") result.assertCommonized("(c, d)", "expect class X") result.assertCommonized("((a, b), (c, d))", "expect class X") } @@ -558,7 +567,7 @@ class HierarchicalClassAndTypeAliasCommonizationTest : AbstractInlineSourcesComm result.assertCommonized( "(c, d)", """ - expect class Proxy: Number + typealias Proxy = Int typealias X = Proxy expect val x: X """.trimIndent() @@ -566,7 +575,7 @@ class HierarchicalClassAndTypeAliasCommonizationTest : AbstractInlineSourcesComm result.assertCommonized( "(a, b, c, d)", """ - expect class Proxy: Number + typealias Proxy = Long typealias X = Proxy expect val x: X """.trimIndent()