[NI] Implement algorithm for completion mode selection

Current selection of completion mode for call is not always correct in case of full mode,
and sometimes too conservative in case of partial mode. Updated algorithm checks constraints
wrt position of type variables in return type and in other related constraints.
Full completion happens if proper constraint requirements are satisfied for variables.
This commit is contained in:
Pavel Kirpichenkov
2019-12-16 19:02:03 +03:00
parent a9391c8dfb
commit e9941f8c12
17 changed files with 735 additions and 64 deletions
@@ -0,0 +1,176 @@
/*
* Copyright 2010-2019 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.resolve.calls.components
import org.jetbrains.kotlin.resolve.calls.inference.components.KotlinConstraintSystemCompleter
import org.jetbrains.kotlin.resolve.calls.inference.components.KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.resolve.calls.model.KotlinResolutionCandidate
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.UnwrappedType
import org.jetbrains.kotlin.types.model.*
import java.util.*
typealias CsCompleterContext = KotlinConstraintSystemCompleter.Context
class CompletionModeCalculator {
companion object {
fun computeCompletionMode(
candidate: KotlinResolutionCandidate,
expectedType: UnwrappedType?,
returnType: UnwrappedType?
): ConstraintSystemCompletionMode = with(candidate) {
val csCompleterContext = getSystem().asConstraintSystemCompleterContext()
// Presence of expected type means that we are trying to complete outermost call => completion mode should be full
if (expectedType != null) return ConstraintSystemCompletionMode.FULL
// This is questionable as null return type can be only for error call
if (returnType == null) return ConstraintSystemCompletionMode.PARTIAL
// Full if return type for call has no type variables
if (csBuilder.isProperType(returnType)) return ConstraintSystemCompletionMode.FULL
// For nested call with variables in return type check possibility of full completion
return CalculatorForNestedCall(returnType, csCompleterContext).computeCompletionMode()
}
}
private class CalculatorForNestedCall(
private val returnType: UnwrappedType?,
private val csCompleterContext: CsCompleterContext
) {
private enum class FixationDirection {
TO_SUBTYPE, TO_SUPERTYPE, EQUALITY
}
private val fixationDirectionsForVariables = mutableMapOf<VariableWithConstraints, FixationDirection>()
private val variablesWithQueuedConstraints = mutableSetOf<TypeVariableMarker>()
private val typesToProcess: Queue<KotlinTypeMarker> = ArrayDeque()
fun computeCompletionMode(): ConstraintSystemCompletionMode = with(csCompleterContext) {
// Add fixation directions for variables based on effective variance in type
typesToProcess.add(returnType)
computeDirections()
// If all variables have required proper constraint, run full completion
if (directionRequirementsForVariablesHold())
return ConstraintSystemCompletionMode.FULL
return ConstraintSystemCompletionMode.PARTIAL
}
private fun CsCompleterContext.computeDirections() {
while (typesToProcess.isNotEmpty()) {
val type = typesToProcess.poll() ?: break
fixationRequirementForTopLevel(type)?.let { directionForVariable ->
updateDirection(directionForVariable)
enqueueTypesFromConstraints(directionForVariable.variable)
}
// find all variables in type and make requirements for them
type.contains { fromReturnType ->
for (directionForVariable in directionsForVariablesInTypeArguments(fromReturnType)) {
updateDirection(directionForVariable)
enqueueTypesFromConstraints(directionForVariable.variable)
}
false
}
}
}
private fun enqueueTypesFromConstraints(variableWithConstraints: VariableWithConstraints) {
val variable = variableWithConstraints.typeVariable
if (variable !in variablesWithQueuedConstraints) {
for (constraint in variableWithConstraints.constraints) {
typesToProcess.add(constraint.type)
}
variablesWithQueuedConstraints.add(variable)
}
}
private fun CsCompleterContext.directionRequirementsForVariablesHold(): Boolean {
for ((variable, fixationDirection) in fixationDirectionsForVariables) {
if (!hasProperConstraint(variable, fixationDirection))
return false
}
return true
}
private fun updateDirection(directionForVariable: FixationDirectionForVariable) {
val (variable, newDirection) = directionForVariable
fixationDirectionsForVariables[variable]?.let { oldDirection ->
// To sub and to super are merged into equality, old equality stays
if (oldDirection != FixationDirection.EQUALITY && oldDirection != newDirection)
fixationDirectionsForVariables[variable] = FixationDirection.EQUALITY
}
fixationDirectionsForVariables[variable] = newDirection
}
private data class FixationDirectionForVariable(val variable: VariableWithConstraints, val direction: FixationDirection)
private fun CsCompleterContext.fixationRequirementForTopLevel(type: KotlinTypeMarker): FixationDirectionForVariable? {
return notFixedTypeVariables[type.typeConstructor()]?.let {
FixationDirectionForVariable(it, FixationDirection.TO_SUBTYPE)
}
}
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()}"
}
val directionsForVariables = mutableListOf<FixationDirectionForVariable>()
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.OUT // Discuss
val direction = when (effectiveVariance) {
TypeVariance.IN -> FixationDirection.TO_SUPERTYPE
TypeVariance.OUT -> FixationDirection.TO_SUBTYPE
TypeVariance.INV -> FixationDirection.EQUALITY
}
val requirement = FixationDirectionForVariable(variableWithConstraints, direction)
directionsForVariables.add(requirement)
}
return directionsForVariables
}
private fun CsCompleterContext.hasProperConstraint(
variableWithConstraints: VariableWithConstraints,
direction: FixationDirection
): Boolean {
val constraints = variableWithConstraints.constraints
// todo check correctness for @Exact
return constraints.isNotEmpty() && constraints.any { constraint ->
constraint.hasRequiredKind(direction)
&& !constraint.type.typeConstructor().isIntegerLiteralTypeConstructor()
&& isProperType(constraint.type)
}
}
private fun Constraint.hasRequiredKind(direction: FixationDirection) = when (direction) {
FixationDirection.TO_SUBTYPE -> kind.isLower() || kind.isEqual()
FixationDirection.TO_SUPERTYPE -> kind.isUpper() || kind.isEqual()
FixationDirection.EQUALITY -> kind.isEqual()
}
}
}
@@ -53,7 +53,7 @@ class KotlinCallCompleter(
return if (resolutionCallbacks.inferenceSession.shouldRunCompletion(candidate))
candidate.runCompletion(
candidate.computeCompletionMode(expectedType, returnType),
CompletionModeCalculator.computeCompletionMode(candidate, expectedType, returnType),
diagnosticHolder,
resolutionCallbacks
)
@@ -208,69 +208,6 @@ class KotlinCallCompleter(
csBuilder.addSubtypeConstraint(returnType, expectedType, ExpectedTypeConstraintPosition(resolvedCall.atom))
}
private fun KotlinResolutionCandidate.computeCompletionMode(
expectedType: UnwrappedType?,
currentReturnType: UnwrappedType?
): ConstraintSystemCompletionMode {
// Presence of expected type means that we trying to complete outermost call => completion mode should be full
if (expectedType != null) return ConstraintSystemCompletionMode.FULL
// This is questionable as null return type can be only for error call
if (currentReturnType == null) return ConstraintSystemCompletionMode.PARTIAL
return when {
// Consider call foo(bar(x)), if return type of bar is a proper one, then we can complete resolve for bar => full completion mode
// Otherwise, we shouldn't complete bar until we process call foo
csBuilder.isProperType(currentReturnType) -> ConstraintSystemCompletionMode.FULL
// Nested call is connected with the outer one through the UPPER constraint (returnType <: expectedOuterType)
// This means that there will be no new LOWER constraints =>
// it's possible to complete call now if there are proper LOWER constraints
csBuilder.isTypeVariable(currentReturnType) ->
if (hasProperNonTrivialLowerConstraints(currentReturnType))
ConstraintSystemCompletionMode.FULL
else
ConstraintSystemCompletionMode.PARTIAL
// Return type has proper equal constraints => there is no need in the outer call
containsTypeVariablesWithProperEqualConstraints(currentReturnType) -> ConstraintSystemCompletionMode.FULL
else -> ConstraintSystemCompletionMode.PARTIAL
}
}
private fun KotlinResolutionCandidate.containsTypeVariablesWithProperEqualConstraints(type: UnwrappedType): Boolean {
for ((variableConstructor, variableWithConstraints) in csBuilder.currentStorage().notFixedTypeVariables) {
if (!type.contains { it.constructor == variableConstructor }) continue
val constraints = variableWithConstraints.constraints
val onlyProperEqualConstraints =
constraints.isNotEmpty() && constraints.any { it.kind.isEqual() && csBuilder.isProperType(it.type) }
if (!onlyProperEqualConstraints) return false
}
return true
}
private fun KotlinResolutionCandidate.hasProperNonTrivialLowerConstraints(typeVariable: UnwrappedType): Boolean {
assert(csBuilder.isTypeVariable(typeVariable)) { "$typeVariable is not a type variable" }
val context = getSystem() as TypeSystemInferenceExtensionContext
val constructor = typeVariable.constructor
val variableWithConstraints = csBuilder.currentStorage().notFixedTypeVariables[constructor] ?: return false
val constraints = variableWithConstraints.constraints
return constraints.isNotEmpty() && constraints.anyOrAll(requireAll = typeVariable.hasExactAnnotation()) {
!it.type.typeConstructor(context).isIntegerLiteralTypeConstructor(context) &&
(it.kind.isLower() || it.kind.isEqual()) &&
csBuilder.isProperType(it.type) &&
!trivialConstraintTypeInferenceOracle.isNotInterestingConstraint(it)
}
}
private inline fun <T> Iterable<T>.anyOrAll(requireAll: Boolean, p: (T) -> Boolean): Boolean =
if (requireAll) all(p) else any(p)
fun KotlinResolutionCandidate.asCallResolutionResult(
type: ConstraintSystemCompletionMode,
diagnosticsHolder: KotlinDiagnosticsHolder.SimpleHolder