K2: Avoid using Nothing? as inference result in the majority of cases

Namely, do not choose `Nothing?` result type when fixing a variable
that has other constraints besides the ones that came from
the relevant type parameter's upper bounds.

See more details in KT-55691.

In K1, the case from specialCallWithMaterializeAndExpectedType.kt
was working (inferred to String?) just because the branches
were analyzed independently with `String?` expected type.

This change became necessary after the previous commit when we united
inference subsystems for if/when branches (see motivation there).

NB: For K1, the behavior is left the same, but the code
was refactored a bit.

^KT-55691 Fixed
^KT-56448 Fixed
This commit is contained in:
Denis.Zharkov
2022-12-02 19:28:18 +01:00
committed by Space Team
parent f12a4e08cf
commit 2bafcddf7a
18 changed files with 289 additions and 68 deletions
@@ -146,15 +146,39 @@ class ResultTypeResolver(
for (constraint in filteredConstraints) {
if (!checkConstraint(this, constraint.type, constraint.kind, resultType)) return false
}
if (!trivialConstraintTypeInferenceOracle.isSuitableResultedType(resultType)) {
if (resultType.isNullableType() && checkSingleLowerNullabilityConstraint(filteredConstraints)) return false
if (isReified(variableWithConstraints.typeVariable)) return false
}
return true
// if resultType is not Nothing
if (trivialConstraintTypeInferenceOracle.isSuitableResultedType(resultType)) return true
// Nothing and Nothing? is not allowed for reified parameters
if (isReified(variableWithConstraints.typeVariable)) return false
// It's ok to fix result to non-nullable Nothing and parameter is not reified
if (!resultType.isNullableType()) return true
return isNullableNothingMayBeConsideredAsSuitableResultType(filteredConstraints)
}
private fun checkSingleLowerNullabilityConstraint(constraints: List<Constraint>): Boolean {
private fun Context.isNullableNothingMayBeConsideredAsSuitableResultType(constraints: List<Constraint>): Boolean = when {
isK2 ->
// There might be an assertion for green code that if `allUpperConstraintsAreFromBounds(constraints) == true` then
// the single `Nothing?` lower bound constraint has Constraint::isNullabilityConstraint is set to false
// because otherwise we would not start fixing the variable since it has no proper constraints.
allUpperConstraintsAreFromBounds(constraints)
else -> !isThereSingleLowerNullabilityConstraint(constraints)
}
private fun allUpperConstraintsAreFromBounds(constraints: List<Constraint>): Boolean =
constraints.all {
// Actually, at least for green code that should be an assertion that lower constraints (!isUpper) has `Nothing?` type
// Because otherwise if we had `Nothing? <: T` and `SomethingElse <: T` than it would end with `SomethingElse? <: T`
!it.kind.isUpper() || isFromTypeParameterUpperBound(it)
}
private fun isFromTypeParameterUpperBound(constraint: Constraint): Boolean =
constraint.position.isFromDeclaredUpperBound || constraint.position.from is DeclaredUpperBoundConstraintPosition<*>
private fun isThereSingleLowerNullabilityConstraint(constraints: List<Constraint>): Boolean {
return constraints.singleOrNull { it.kind.isLower() }?.isNullabilityConstraint ?: false
}
@@ -86,6 +86,10 @@ class Constraint(
val position: IncorporationConstraintPosition,
val typeHashCode: Int = type.hashCode(),
val derivedFrom: Set<TypeVariableMarker>,
// This value is true for constraints of the form `Nothing? <: Tv`
// that have been created during incorporation phase of the constraint of the form `Kv? <: Tv` (where `Kv` another type variable).
// The main idea behind that parameter is that we don't consider such constraints as proper (signifying that variable is ready for completion).
// And also, there is additional logic in K1 that doesn't allow to fix variable into `Nothing?` if we had only that kind of lower constraints
val isNullabilityConstraint: Boolean,
val inputTypePositionBeforeIncorporation: OnlyInputTypeConstraintPosition? = null
) {