[NI] Use alternative to intersection type in public declarations

The new inference uses inferred intersection types normally, unlike the old inference.
However, intersection types in public declarations are approximated to supertype, which
potentially may give a less presice type, then it would be with the OI.
For non-related T1, T2 the NI approximates {T1 & T2} to Any in public declarations,
and if the OI was inferring T1 instead of the intersection type, it may lead to
less precise declaration type and related errors.

The solution is to remember an alternative for an intersection type when present.
Before approximation the alternative replaces the intersection type.

^KT-36249 Fixed
This commit is contained in:
Pavel Kirpichenkov
2020-02-20 19:55:13 +03:00
parent 4038c758ca
commit dc42b20ae3
67 changed files with 2120 additions and 30 deletions
@@ -27,6 +27,15 @@ import org.jetbrains.kotlin.types.refinement.TypeRefinement
import java.util.*
class IntersectionTypeConstructor(typesToIntersect: Collection<KotlinType>) : TypeConstructor {
private var alternative: KotlinType? = null
private constructor(
typesToIntersect: Collection<KotlinType>,
alternative: KotlinType?,
) : this(typesToIntersect) {
this.alternative = alternative
}
init {
assert(!typesToIntersect.isEmpty()) { "Attempt to create an empty intersection" }
}
@@ -75,7 +84,13 @@ class IntersectionTypeConstructor(typesToIntersect: Collection<KotlinType>) : Ty
@TypeRefinement
override fun refine(kotlinTypeRefiner: KotlinTypeRefiner) =
IntersectionTypeConstructor(intersectedTypes.map { it.refine(kotlinTypeRefiner) })
transformComponents { it.refine(kotlinTypeRefiner) } ?: this
fun setAlternative(alternative: KotlinType?): IntersectionTypeConstructor {
return IntersectionTypeConstructor(intersectedTypes, alternative)
}
fun getAlternativeType(): KotlinType? = alternative
}
inline fun IntersectionTypeConstructor.transformComponents(
@@ -94,5 +109,9 @@ inline fun IntersectionTypeConstructor.transformComponents(
if (!changed) return null
return IntersectionTypeConstructor(newSupertypes)
val updatedAlternative = getAlternativeType()?.let { alternative ->
if (predicate(alternative)) transform(alternative) else alternative
}
return IntersectionTypeConstructor(newSupertypes).setAlternative(updatedAlternative)
}
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.types.typeUtil.contains
import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -606,6 +607,21 @@ interface ClassicTypeSystemContext : TypeSystemInferenceExtensionContext, TypeSy
val descriptor = constructor.declarationDescriptor
return descriptor is ClassDescriptor && (descriptor.kind == ClassKind.INTERFACE || descriptor.kind == ClassKind.ANNOTATION_CLASS)
}
override fun createTypeWithAlternativeForIntersectionResult(
firstCandidate: KotlinTypeMarker,
secondCandidate: KotlinTypeMarker,
): KotlinTypeMarker {
require(firstCandidate is KotlinType, this::errorMessage)
require(secondCandidate is KotlinType, this::errorMessage)
firstCandidate.constructor.safeAs<IntersectionTypeConstructor>()?.let { intersectionConstructor ->
val intersectionTypeWithAlternative = intersectionConstructor.setAlternative(secondCandidate).createType()
return if (firstCandidate.isMarkedNullable) intersectionTypeWithAlternative.makeNullableAsSpecified(true)
else intersectionTypeWithAlternative
} ?: error("Expected intersection type, found $firstCandidate")
}
}
fun TypeVariance.convertVariance(): Variance {
@@ -164,6 +164,11 @@ interface TypeSystemInferenceExtensionContext : TypeSystemContext, TypeSystemBui
fun TypeVariableMarker.defaultType(): SimpleTypeMarker
fun createTypeWithAlternativeForIntersectionResult(
firstCandidate: KotlinTypeMarker,
secondCandidate: KotlinTypeMarker
): KotlinTypeMarker
}