[NI] Revise variance calculation method for completion mode

Before fix only one level of type arguments was used to determine variance of type variable to find out direction requirements.
This incorrect in general case, because outer variance affects subtyping deductions, for example:
Inv<Out<A>> <: Inv<Out<B>> => A <: B; B <: A, despite A and B are in covariant position if only one level is considered

^KT-36233 Fixed
This commit is contained in:
Pavel Kirpichenkov
2020-01-30 19:06:21 +03:00
parent e27b2990e3
commit 517688e163
22 changed files with 904 additions and 39 deletions
@@ -83,19 +83,16 @@ class CompletionModeCalculator {
while (typesToProcess.isNotEmpty()) {
val type = typesToProcess.poll() ?: break
fixationDirectionForTopLevel(type)?.let { directionForVariable ->
if (!type.contains { it.typeConstructor() in notFixedTypeVariables })
continue
val fixationDirectionsFromType = mutableSetOf<FixationDirectionForVariable>()
collectRequiredDirectionsForVariables(type, TypeVariance.OUT, fixationDirectionsFromType)
for (directionForVariable in fixationDirectionsFromType) {
updateDirection(directionForVariable)
enqueueTypesFromConstraints(directionForVariable.variable)
}
// find all variables in type and make requirements for them
type.contains { typePart ->
for (directionForVariable in directionsForVariablesInTypeArguments(typePart)) {
updateDirection(directionForVariable)
enqueueTypesFromConstraints(directionForVariable.variable)
}
false
}
}
}
@@ -130,42 +127,62 @@ class CompletionModeCalculator {
private data class FixationDirectionForVariable(val variable: VariableWithConstraints, val direction: FixationDirection)
private fun CsCompleterContext.fixationDirectionForTopLevel(type: KotlinTypeMarker): FixationDirectionForVariable? {
return notFixedTypeVariables[type.typeConstructor()]?.let {
FixationDirectionForVariable(it, FixationDirection.TO_SUBTYPE)
private fun CsCompleterContext.collectRequiredDirectionsForVariables(
type: KotlinTypeMarker, outerVariance: TypeVariance,
fixationDirectionsCollector: MutableSet<FixationDirectionForVariable>
) {
val typeArgumentsCount = type.argumentsCount()
if (typeArgumentsCount > 0) {
for (position in 0 until typeArgumentsCount) {
val argument = type.getArgument(position)
val parameter = type.typeConstructor().getParameter(position)
if (argument.isStarProjection())
continue
collectRequiredDirectionsForVariables(
argument.getType(),
compositeVariance(outerVariance, argument, parameter),
fixationDirectionsCollector
)
}
} else {
processTypeWithoutParameters(type, outerVariance, fixationDirectionsCollector)
}
}
private fun CsCompleterContext.directionsForVariablesInTypeArguments(type: KotlinTypeMarker): List<FixationDirectionForVariable> {
assert(type.argumentsCount() == type.typeConstructor().parametersCount()) {
"Arguments and parameters count don't match for type $type. " +
"Arguments: ${type.argumentsCount()}, parameters: ${type.typeConstructor().parametersCount()}"
private fun CsCompleterContext.compositeVariance(
outerVariance: TypeVariance,
argument: TypeArgumentMarker,
parameter: TypeParameterMarker
): TypeVariance {
val effectiveArgumentVariance = AbstractTypeChecker.effectiveVariance(parameter.getVariance(), argument.getVariance())
?: TypeVariance.INV // conflicting variance
return when (outerVariance) {
TypeVariance.INV -> TypeVariance.INV
TypeVariance.OUT -> effectiveArgumentVariance
TypeVariance.IN -> effectiveArgumentVariance.reversed()
}
}
val directionsForVariables = mutableListOf<FixationDirectionForVariable>()
private fun TypeVariance.reversed(): TypeVariance = when (this) {
TypeVariance.IN -> TypeVariance.OUT
TypeVariance.OUT -> TypeVariance.IN
TypeVariance.INV -> TypeVariance.INV
}
for (position in 0 until type.argumentsCount()) {
val argument = type.getArgument(position)
if (!argument.getType().mayBeTypeVariable())
continue
val variableWithConstraints = notFixedTypeVariables[argument.getType().typeConstructor()] ?: continue
val parameter = type.typeConstructor().getParameter(position)
val effectiveVariance = AbstractTypeChecker.effectiveVariance(parameter.getVariance(), argument.getVariance())
?: TypeVariance.INV
val direction = when (effectiveVariance) {
TypeVariance.IN -> FixationDirection.EQUALITY // Assuming that variables in contravariant positions are fixed to subtype
TypeVariance.OUT -> FixationDirection.TO_SUBTYPE
TypeVariance.INV -> FixationDirection.EQUALITY
}
val requirement = FixationDirectionForVariable(variableWithConstraints, direction)
directionsForVariables.add(requirement)
private fun CsCompleterContext.processTypeWithoutParameters(
type: KotlinTypeMarker, compositeVariance: TypeVariance,
newRequirementsCollector: MutableSet<FixationDirectionForVariable>
) {
val variableWithConstraints = notFixedTypeVariables[type.typeConstructor()] ?: return
val direction = when (compositeVariance) {
TypeVariance.IN -> FixationDirection.EQUALITY // Assuming that variables in contravariant positions are fixed to subtype
TypeVariance.OUT -> FixationDirection.TO_SUBTYPE
TypeVariance.INV -> FixationDirection.EQUALITY
}
return directionsForVariables
val requirement = FixationDirectionForVariable(variableWithConstraints, direction)
newRequirementsCollector.add(requirement)
}
private fun CsCompleterContext.hasProperConstraint(