diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/AbstractFunctionOrPropertyCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/AbstractFunctionOrPropertyCommonizer.kt index c03027f4d9e..34b5b2ac9ee 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/AbstractFunctionOrPropertyCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/AbstractFunctionOrPropertyCommonizer.kt @@ -5,9 +5,11 @@ package org.jetbrains.kotlin.commonizer.core -import org.jetbrains.kotlin.commonizer.cir.* +import org.jetbrains.kotlin.commonizer.cir.CirFunctionOrProperty +import org.jetbrains.kotlin.commonizer.cir.CirName +import org.jetbrains.kotlin.commonizer.cir.CirType +import org.jetbrains.kotlin.commonizer.core.TypeCommonizer.Options.Companion.default import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers -import org.jetbrains.kotlin.commonizer.utils.safeCastValues import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DELEGATION import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED @@ -45,19 +47,7 @@ private class ReturnTypeCommonizer( override fun invoke(values: List): CirType? { if (values.isEmpty()) return null val isTopLevel = values.all { it.containingClass == null } - val returnTypes = if (isTopLevel) makeNullableIfNecessary(values.map { it.returnType }) else values.map { it.returnType } - return TypeCommonizer(classifiers).asCommonizer().commonize(returnTypes) - } - - private fun makeNullableIfNecessary(types: List): List { - val simpleTypes = types.safeCastValues() ?: return types - - if ( - simpleTypes.all { type -> type.isMarkedNullable } || - simpleTypes.none { type -> type.isMarkedNullable } - ) return types - - return simpleTypes.map { type -> type.makeNullable() } + return TypeCommonizer(classifiers, default.withCovariantNullabilityCommonizationEnabled(isTopLevel)) + .asCommonizer().commonize(values.map { it.returnType }) } } - 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 4ad239c243c..f8546af6d80 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassOrTypeAliasTypeCommonizer.kt @@ -17,8 +17,8 @@ internal class ClassOrTypeAliasTypeCommonizer( 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 + return ClassTypeCommonizer(classifiers, options).commonize(listOf(first, second)) + ?: if (options.enableOptimisticNumberTypeCommonization) OptimisticNumbersTypeCommonizer.commonize(first, second) else null } if (first is CirTypeAliasType && second is CirTypeAliasType) { @@ -26,9 +26,10 @@ internal class ClassOrTypeAliasTypeCommonizer( In case regular type-alias-type commonization fails, we try to expand all type-aliases and try our luck with commonizing those class types */ - return TypeAliasTypeCommonizer(classifiers).commonize(listOf(first, second)) - ?: ClassOrTypeAliasTypeCommonizer(classifiers, options.withAllowOptimisticNumberTypeCommonization()) - .commonize(first.expandedType(), second.expandedType()) + return TypeAliasTypeCommonizer(classifiers, options).commonize(listOf(first, second)) + ?: ClassOrTypeAliasTypeCommonizer( + classifiers, options.withOptimisticNumberTypeCommonizationEnabled() + ).commonize(first.expandedType(), second.expandedType()) } val classType = when { @@ -47,7 +48,7 @@ internal class ClassOrTypeAliasTypeCommonizer( TypeAliasCommonizer will be able to figure out if the typealias will be represented as expect class in common. If so, re-use this class type, otherwise: try to expand the typeAlias */ - val typeAliasClassType = TypeAliasTypeCommonizer(classifiers).commonize(listOf(typeAliasType))?.expandedType() + val typeAliasClassType = TypeAliasTypeCommonizer(classifiers, options).commonize(listOf(typeAliasType))?.expandedType() ?: typeAliasType.expandedType() return commonize(classType, typeAliasClassType) diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassTypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassTypeCommonizer.kt index 2ca609bf4c9..f0a705fc5d5 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassTypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/ClassTypeCommonizer.kt @@ -11,13 +11,16 @@ import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers import org.jetbrains.kotlin.commonizer.utils.isUnderKotlinNativeSyntheticPackages import org.jetbrains.kotlin.descriptors.Visibility -internal class ClassTypeCommonizer(private val classifiers: CirKnownClassifiers) : +internal class ClassTypeCommonizer( + private val classifiers: CirKnownClassifiers, + options: TypeCommonizer.Options = TypeCommonizer.Options.default +) : AbstractStandardCommonizer() { private lateinit var classId: CirEntityId private val outerType = OuterClassTypeCommonizer(classifiers) private lateinit var anyVisibility: Visibility private val arguments = TypeArgumentListCommonizer(classifiers) - private var isMarkedNullable = false + private val isMarkedNullable = TypeNullabilityCommonizer(options).asCommonizer() override fun commonizationResult() = CirClassType.createInterned( classId = classId, @@ -28,17 +31,16 @@ internal class ClassTypeCommonizer(private val classifiers: CirKnownClassifiers) // to reach better interning rate. visibility = anyVisibility, arguments = arguments.result, - isMarkedNullable = isMarkedNullable + isMarkedNullable = isMarkedNullable.result ) override fun initialize(first: CirClassType) { classId = first.classifierId anyVisibility = first.visibility - isMarkedNullable = first.isMarkedNullable } override fun doCommonizeWith(next: CirClassType) = - isMarkedNullable == next.isMarkedNullable + isMarkedNullable.commonizeWith(next.isMarkedNullable) && classId == next.classifierId && outerType.commonizeWith(next.outerType) && isClassifierAvailableInCommon(classifiers, classId) diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableContextualSingleInvocationCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableContextualSingleInvocationCommonizer.kt new file mode 100644 index 00000000000..7abdd71bdcb --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableContextualSingleInvocationCommonizer.kt @@ -0,0 +1,23 @@ +/* + * 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 + +interface NullableContextualSingleInvocationCommonizer { + operator fun invoke(values: List): R? +} + +fun NullableContextualSingleInvocationCommonizer.asCommonizer(): Commonizer = + object : Commonizer { + private val collectedValues = mutableListOf() + + override val result: R? + get() = this@asCommonizer.invoke(collectedValues) + + override fun commonizeWith(next: Context): Boolean { + collectedValues.add(next) + return true + } + } diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableSingleInvocationCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableSingleInvocationCommonizer.kt index 0e382d06798..7eacfabb371 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableSingleInvocationCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/NullableSingleInvocationCommonizer.kt @@ -22,18 +22,3 @@ fun NullableSingleInvocationCommonizer.asCommonizer(): Commonizer { - operator fun invoke(values: List): R? -} - -fun NullableContextualSingleInvocationCommonizer.asCommonizer(): Commonizer = object : Commonizer { - private val collectedValues = mutableListOf() - - override val result: R? - get() = this@asCommonizer.invoke(collectedValues) - - override fun commonizeWith(next: T): Boolean { - collectedValues.add(next) - return true - } -} 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 b1bf40df979..d4bda4d14e4 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasCommonizer.kt @@ -22,7 +22,7 @@ class TypeAliasCommonizer( val typeParameters = TypeParameterListCommonizer(classifiers).commonize(values.map { it.typeParameters }) ?: return null - val underlyingType = TypeCommonizer(classifiers, TypeCommonizer.Options.default.withAllowOptimisticNumberTypeCommonization()) + val underlyingType = TypeCommonizer(classifiers, TypeCommonizer.Options.default.withOptimisticNumberTypeCommonizationEnabled()) .asCommonizer().commonize(values.map { it.underlyingType }) as? CirClassOrTypeAliasType ?: return null val visibility = VisibilityCommonizer.lowering().commonize(values) ?: return null diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasTypeCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasTypeCommonizer.kt index a0c051b5f44..1520c18fc13 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasTypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeAliasTypeCommonizer.kt @@ -10,13 +10,16 @@ import org.jetbrains.kotlin.commonizer.core.CommonizedTypeAliasAnswer.Companion. import org.jetbrains.kotlin.commonizer.core.CommonizedTypeAliasAnswer.Companion.SUCCESS_FROM_DEPENDENCY_LIBRARY import org.jetbrains.kotlin.commonizer.mergedtree.CirKnownClassifiers -internal class TypeAliasTypeCommonizer(private val classifiers: CirKnownClassifiers) : +internal class TypeAliasTypeCommonizer( + private val classifiers: CirKnownClassifiers, + options: TypeCommonizer.Options +) : AbstractStandardCommonizer() { private lateinit var typeAliasId: CirEntityId private val arguments = TypeArgumentListCommonizer(classifiers) private val underlyingTypeArguments = TypeArgumentListCommonizer(classifiers) - private var isMarkedNullable = false + private val isMarkedNullable = TypeNullabilityCommonizer(options).asCommonizer() private var commonizedTypeBuilder: CommonizedTypeAliasTypeBuilder? = null // null means not selected yet override fun commonizationResult() = @@ -24,17 +27,16 @@ internal class TypeAliasTypeCommonizer(private val classifiers: CirKnownClassifi typeAliasId = typeAliasId, arguments = arguments.result, underlyingTypeArguments = underlyingTypeArguments.result, - isMarkedNullable = isMarkedNullable + isMarkedNullable = isMarkedNullable.result ) override fun initialize(first: CirTypeAliasType) { typeAliasId = first.classifierId - isMarkedNullable = first.expandedType().isMarkedNullable } override fun doCommonizeWith(next: CirTypeAliasType): Boolean { - if (isMarkedNullable != next.expandedType().isMarkedNullable || typeAliasId != next.classifierId) - return false + if (typeAliasId != next.classifierId) return false + if (!isMarkedNullable.commonizeWith(next.expandedType().isMarkedNullable)) return false if (commonizedTypeBuilder == null) { val answer = commonizeTypeAlias(typeAliasId, classifiers) 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 bd6a2fa4457..af8af761ec6 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeCommonizer.kt @@ -30,12 +30,18 @@ class TypeCommonizer( } data class Options( - val allowOptimisticNumberTypeCommonization: Boolean = false + val enableOptimisticNumberTypeCommonization: Boolean = false, + val enableCovariantNullabilityCommonization: Boolean = false ) { - fun withAllowOptimisticNumberTypeCommonization(): Options { - return if (allowOptimisticNumberTypeCommonization) this - else copy(allowOptimisticNumberTypeCommonization = true) + fun withOptimisticNumberTypeCommonizationEnabled(enabled: Boolean = true): Options { + return if (enableOptimisticNumberTypeCommonization == enabled) this + else copy(enableOptimisticNumberTypeCommonization = enabled) + } + + fun withCovariantNullabilityCommonizationEnabled(enabled: Boolean = true): Options { + return if (enableCovariantNullabilityCommonization == enabled) this + else copy(enableCovariantNullabilityCommonization = enabled) } companion object { diff --git a/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeNullabilityCommonizer.kt b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeNullabilityCommonizer.kt new file mode 100644 index 00000000000..706364d79a2 --- /dev/null +++ b/native/commonizer/src/org/jetbrains/kotlin/commonizer/core/TypeNullabilityCommonizer.kt @@ -0,0 +1,28 @@ +/* + * 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 + +private typealias IsMarkedNullable = Boolean + +internal interface TypeNullabilityCommonizer : AssociativeCommonizer + +internal fun TypeNullabilityCommonizer(options: TypeCommonizer.Options): TypeNullabilityCommonizer { + return if (options.enableCovariantNullabilityCommonization) CovariantTypeNullabilityCommonizer + else EqualTypeNullabilityCommonizer +} + +private object CovariantTypeNullabilityCommonizer : TypeNullabilityCommonizer { + override fun commonize(first: IsMarkedNullable, second: IsMarkedNullable): IsMarkedNullable { + return first || second + } +} + +private object EqualTypeNullabilityCommonizer : TypeNullabilityCommonizer { + override fun commonize(first: IsMarkedNullable, second: IsMarkedNullable): IsMarkedNullable? { + if (first != second) return null + return first + } +} \ No newline at end of file diff --git a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/ReturnTypeNullabilityCommonizationTest.kt b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/ReturnTypeNullabilityCommonizationTest.kt index 3725364c644..3ca87708ec9 100644 --- a/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/ReturnTypeNullabilityCommonizationTest.kt +++ b/native/commonizer/tests/org/jetbrains/kotlin/commonizer/hierarchical/ReturnTypeNullabilityCommonizationTest.kt @@ -92,7 +92,7 @@ class ReturnTypeNullabilityCommonizationTest : AbstractInlineSourcesCommonizatio result.assertCommonized( "(a, b)", """ expect class X - expect fun x(): X + expect fun x(): X? """.trimIndent() ) } @@ -124,7 +124,9 @@ class ReturnTypeNullabilityCommonizationTest : AbstractInlineSourcesCommonizatio typealias Z = Any expect class Y expect class X - fun x(): X + + // Still cary nullability mark here to be extra safe + expect fun x(): X? """.trimIndent() ) } @@ -156,7 +158,8 @@ class ReturnTypeNullabilityCommonizationTest : AbstractInlineSourcesCommonizatio typealias Z = Any expect class Y expect class X - expect val x: X + // Still cary nullability mark here to be more safe! + expect val x: X? """.trimIndent() ) } @@ -176,6 +179,12 @@ class ReturnTypeNullabilityCommonizationTest : AbstractInlineSourcesCommonizatio result.assertCommonized("(a, b, c, d)", "expect val x: Any?") } + + /* + We expect *no* covariant nullability commonization on any member function/property because this might mess + with overrides of super classes/interfaces + */ + fun `test member - property - hierarchically`() { val result = commonize { outputTarget("(a, b)", "(c, d)", "(a, b, c, d)")