[FIR] Implement new completion mode calculator

Note that `testDelegates` now fails due to KT-37638 and
    `testSimpleIn` fails due to problems with type parameters
    of inner classes
This commit is contained in:
Dmitriy Novozhilov
2020-03-20 15:20:09 +03:00
parent ea02855ba6
commit 5f9f01fe4e
14 changed files with 340 additions and 111 deletions
@@ -2,7 +2,7 @@ interface Out<out E>
fun <X> id(x: Out<X>): Out<X> = TODO()
fun <F : Any> foo(computable: Out<F?>)
fun <F : Any> foo(computable: Out<F?>) {}
fun <T : Any> bar(computable: Out<T?>) {
foo(id(computable))
@@ -4,7 +4,8 @@ FILE: definetelyNotNullForTypeParameter.kt
public final fun <X> id(x: R|Out<X>|): R|Out<X>| {
^id R|kotlin/TODO|()
}
public final fun <F : R|kotlin/Any|> foo(computable: R|Out<F?>|): R|kotlin/Unit|
public final fun <T : R|kotlin/Any|> bar(computable: R|Out<T?>|): R|kotlin/Unit| {
R|/foo|<R|T|>(R|/id|<R|T?|>(R|<local>/computable|))
public final fun <F : R|kotlin/Any|> foo(computable: R|Out<F?>|): R|kotlin/Unit| {
}
public final fun <T : R|kotlin/Any|> bar(computable: R|Out<T?>|): R|kotlin/Unit| {
R|/foo|<R|T?!!|>(R|/id|<R|T?|>(R|<local>/computable|))
}
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirArgumentList
import org.jetbrains.kotlin.fir.expressions.FirEmptyArgumentList
@@ -17,6 +16,7 @@ import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.DoubleColonLHS
import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStack
import org.jetbrains.kotlin.fir.resolve.inference.PostponedResolvedAtom
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemOperation
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl
import org.jetbrains.kotlin.resolve.calls.model.PostponedResolvedAtomMarker
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
data class CallInfo(
@@ -114,7 +113,7 @@ class Candidate(
var argumentMapping: Map<FirExpression, FirValueParameter>? = null
lateinit var typeArgumentMapping: TypeArgumentMapping
val postponedAtoms = mutableListOf<PostponedResolvedAtomMarker>()
val postponedAtoms = mutableListOf<PostponedResolvedAtom>()
val diagnostics: MutableList<ResolutionDiagnostic> = mutableListOf()
@@ -0,0 +1,231 @@
/*
* Copyright 2010-2020 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.fir.resolve.inference
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.types.ConeIntegerLiteralType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.resolve.calls.inference.components.KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.components.KotlinConstraintSystemCompleter.Context
import org.jetbrains.kotlin.resolve.calls.inference.components.TrivialConstraintTypeInferenceOracle
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.utils.newLinkedHashMapWithExpectedSize
import java.util.*
fun Candidate.computeCompletionMode(
components: InferenceComponents,
expectedType: FirTypeRef?,
currentReturnType: ConeKotlinType?
): ConstraintSystemCompletionMode {
return when {
// Presence of expected type means that we are trying to complete outermost call => completion mode should be full
expectedType != null -> ConstraintSystemCompletionMode.FULL
// This is questionable as null return type can be only for error call
currentReturnType == null || currentReturnType is ConeIntegerLiteralType -> ConstraintSystemCompletionMode.PARTIAL
// Full if return type for call has no type variables
csBuilder.isProperType(currentReturnType) -> ConstraintSystemCompletionMode.FULL
else -> CalculatorForNestedCall(
this, currentReturnType, csBuilder, components.trivialConstraintTypeInferenceOracle
).computeCompletionMode()
}
}
private typealias CsCompleterContext = Context
private class CalculatorForNestedCall(
private val candidate: Candidate,
private val returnType: ConeKotlinType?,
private val context: CsCompleterContext,
private val oracle: TrivialConstraintTypeInferenceOracle
) {
private enum class FixationDirection {
TO_SUBTYPE, EQUALITY
}
private val fixationDirectionsForVariables: MutableMap<VariableWithConstraints, FixationDirection> =
newLinkedHashMapWithExpectedSize(context.notFixedTypeVariables.size)
private val variablesWithQueuedConstraints = mutableSetOf<TypeVariableMarker>()
private val typesToProcess: Queue<KotlinTypeMarker> = ArrayDeque()
private val postponedAtoms: List<PostponedResolvedAtom> by lazy {
candidate.postponedAtoms.filterNot { it.analyzed }
}
fun computeCompletionMode(): ConstraintSystemCompletionMode = with(context) {
// 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
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)
}
}
}
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 ->
if (oldDirection != FixationDirection.EQUALITY && oldDirection != newDirection)
fixationDirectionsForVariables[variable] = FixationDirection.EQUALITY
} ?: run {
fixationDirectionsForVariables[variable] = newDirection
}
}
private data class FixationDirectionForVariable(val variable: VariableWithConstraints, val direction: FixationDirection)
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.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()
}
}
private fun TypeVariance.reversed(): TypeVariance = when (this) {
TypeVariance.IN -> TypeVariance.OUT
TypeVariance.OUT -> TypeVariance.IN
TypeVariance.INV -> TypeVariance.INV
}
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
}
val requirement = FixationDirectionForVariable(variableWithConstraints, direction)
newRequirementsCollector.add(requirement)
}
private fun CsCompleterContext.hasProperConstraint(
variableWithConstraints: VariableWithConstraints,
direction: FixationDirection
): Boolean {
val constraints = variableWithConstraints.constraints
val variable = variableWithConstraints.typeVariable
// ILT constraint tracking is necessary to prevent incorrect full completion from Nothing constraint
// Consider ILT <: T; Nothing <: T for T requiring lower constraint
// Nothing would trigger full completion, but resulting type would be Int
// Possible restrictions on integer constant from outer calls would be ignored
var iltConstraintPresent = false
var properConstraintPresent = false
var nonNothingProperConstraintPresent = false
for (constraint in constraints) {
if (!constraint.hasRequiredKind(direction) || !isProperType(constraint.type))
continue
if (constraint.type.typeConstructor().isIntegerLiteralTypeConstructor()) {
iltConstraintPresent = true
} else if (oracle.isSuitableResultedType(constraint.type)) {
properConstraintPresent = true
nonNothingProperConstraintPresent = true
} else if (!isLowerConstraintForPartiallyAnalyzedVariable(constraint, variable)) {
properConstraintPresent = true
}
}
if (!properConstraintPresent) return false
return !iltConstraintPresent || nonNothingProperConstraintPresent
}
private fun Constraint.hasRequiredKind(direction: FixationDirection) = when (direction) {
FixationDirection.TO_SUBTYPE -> kind.isLower() || kind.isEqual()
FixationDirection.EQUALITY -> kind.isEqual()
}
private fun CsCompleterContext.isLowerConstraintForPartiallyAnalyzedVariable(
constraint: Constraint,
variable: TypeVariableMarker
): Boolean {
val defaultType = variable.defaultType()
return constraint.kind.isLower() && postponedAtoms.any { atom ->
atom.expectedType?.contains { type -> defaultType == type } ?: false
}
}
}
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
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.components.TypeVariableDirectionCalculator
import org.jetbrains.kotlin.resolve.calls.inference.components.VariableFixationFinder
import org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl
@@ -22,85 +23,19 @@ import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraint
import org.jetbrains.kotlin.resolve.calls.model.PostponedResolvedAtomMarker
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.isIntegerLiteralTypeConstructor
import org.jetbrains.kotlin.types.model.typeConstructor
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
fun Candidate.computeCompletionMode(
components: InferenceComponents,
expectedType: FirTypeRef?,
currentReturnType: ConeKotlinType?
): KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode {
// Presence of expected type means that we trying to complete outermost call => completion mode should be full
if (expectedType != null) return KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode.FULL
// This is questionable as null return type can be only for error call
if (currentReturnType == null || currentReturnType is ConeIntegerLiteralType)
return KotlinConstraintSystemCompleter.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) -> KotlinConstraintSystemCompleter.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(components, currentReturnType))
KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode.FULL
else
KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode.PARTIAL
// Return type has proper equal constraints => there is no need in the outer call
containsTypeVariablesWithProperEqualConstraints(components, currentReturnType) -> KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode.FULL
else -> KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode.PARTIAL
}
}
val Candidate.csBuilder: NewConstraintSystemImpl get() = system.getBuilder()
private fun Candidate.containsTypeVariablesWithProperEqualConstraints(components: InferenceComponents, type: ConeKotlinType): Boolean =
with(components.ctx){
for ((variableConstructor, variableWithConstraints) in csBuilder.currentStorage().notFixedTypeVariables) {
if (!type.contains { it.typeConstructor() == 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 Candidate.hasProperNonTrivialLowerConstraints(components: InferenceComponents, typeVariable: ConeKotlinType): Boolean {
assert(csBuilder.isTypeVariable(typeVariable)) { "$typeVariable is not a type variable" }
val context = components.ctx
val constructor = typeVariable.typeConstructor(context)
val variableWithConstraints = csBuilder.currentStorage().notFixedTypeVariables[constructor] ?: return false
val constraints = variableWithConstraints.constraints
// TODO: support Exact annotation
// see KotlinCallCompleter:244
return constraints.isNotEmpty() && constraints.any {
!it.type.typeConstructor(context).isIntegerLiteralTypeConstructor(context) &&
(it.kind.isLower() || it.kind.isEqual()) && csBuilder.isProperType(it.type)
}
}
class ConstraintSystemCompleter(private val components: BodyResolveComponents) {
private val variableFixationFinder = VariableFixationFinder(components.inferenceComponents.trivialConstraintTypeInferenceOracle)
fun complete(
c: KotlinConstraintSystemCompleter.Context,
completionMode: KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode,
completionMode: ConstraintSystemCompletionMode,
topLevelAtoms: List<FirStatement>,
candidateReturnType: ConeKotlinType,
analyze: (PostponedResolvedAtom) -> Unit
@@ -117,13 +52,13 @@ class ConstraintSystemCompleter(private val components: BodyResolveComponents) {
) ?: break
if (
completionMode == KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode.FULL &&
completionMode == ConstraintSystemCompletionMode.FULL &&
resolveLambdaOrCallableReferenceWithTypeVariableAsExpectedType(c, variableForFixation, postponedAtoms, analyze)
) {
continue
}
if (variableForFixation.hasProperConstraint || completionMode == KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode.FULL) {
if (variableForFixation.hasProperConstraint || completionMode == ConstraintSystemCompletionMode.FULL) {
val variableWithConstraints = c.notFixedTypeVariables.getValue(variableForFixation.variable)
fixVariable(c, candidateReturnType, variableWithConstraints, emptyList())
@@ -137,7 +72,7 @@ class ConstraintSystemCompleter(private val components: BodyResolveComponents) {
break
}
if (completionMode == KotlinConstraintSystemCompleter.ConstraintSystemCompletionMode.FULL) {
if (completionMode == ConstraintSystemCompletionMode.FULL) {
// force resolution for all not-analyzed argument's
getOrderedNotAnalyzedPostponedArguments(topLevelAtoms).forEach(analyze)
//
@@ -1,4 +1,3 @@
// IGNORE_BACKEND_FIR: JVM_IR
// WITH_RUNTIME
// KJS_WITH_FULL_RUNTIME
-1
View File
@@ -1,7 +1,6 @@
// !LANGUAGE: +NewInference
// WITH_RUNTIME
// KJS_WITH_FULL_RUNTIME
// IGNORE_BACKEND_FIR: JVM_IR
class Holder(val list: List<String>?)
@@ -0,0 +1,2 @@
Failures detected in FirBodyResolveTransformerAdapter, file: /simpleIn.fir.kt
Cause: java.lang.RuntimeException: While resolving call R|<local>/outer|.set#(R?C|/infer|<R|kotlin/Any|>(String()))
@@ -1,30 +0,0 @@
// !LANGUAGE: +NewInference
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE
interface I {
fun foo()
}
data class Pair<X, Y>(val fst: X, val snd: Y)
class A(f: Pair<Int, (I) -> Unit>? = null)
class B(f: ((I) -> Unit)? = null)
fun main() {
val cond = true
A(
if (cond) {
Pair(1, { baz -> baz.<!UNRESOLVED_REFERENCE!>foo<!>() })
} else {
null
}
)
<!INAPPLICABLE_CANDIDATE!>B<!>(
if (cond) {
{ baz -> baz.<!UNRESOLVED_REFERENCE!>foo<!>() }
} else {
null
}
)
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !LANGUAGE: +NewInference
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE
@@ -0,0 +1,17 @@
Failures detected in FirImplicitTypeBodyResolveTransformerAdapter, file: /My.kt
Cause: java.lang.RuntimeException: While resolving call Q|Properties|.R?C|/Properties.calcVal|(<L> = calcVal@fun <implicit>.<anonymous>(): <implicit> {
lval y: <implicit> = x#.plus#(IntegerLiteral(1))
when () {
CMP(>, y#.compareTo#(IntegerLiteral(0))) -> {
MyBase#.derivedWrapper#()
}
CMP(<, x#.compareTo#(IntegerLiteral(0))) -> {
MyBase#.exoticWrapper#(x#)
}
else -> {
throw java#.lang#.NullPointerException#(String())
}
}
}
)
@@ -0,0 +1,76 @@
// !WITH_NEW_INFERENCE
// NI_EXPECTED_FILE
// JAVAC_EXPECTED_FILE
// FILE: Base.java
public interface Base {}
// FILE: Other.java
public interface Other {}
// FILE: Derived.java
public final class Derived<T> implements Base, Other {}
// FILE: Exotic.java
public final class Exotic implements Base, Other {
int x;
Exotic(int x) {
this.x = x;
}
}
// FILE: Properties.java
import kotlin.jvm.functions.Function0;
class Val<T> {
Function0<T> initializer;
Val(Function0<T> initializer) {
this.initializer = initializer;
}
T getValue(Object instance, Object metadata) {
return initializer.invoke();
}
}
class Properties {
static <T> Val<T> calcVal(Function0<T> initializer) {
return new Val<T>(initializer);
}
}
// FILE: My.kt
open class Wrapper<out T: Base>(val v: T)
class DerivedWrapper(v: Derived<*>): Wrapper<Derived<*>>(v)
class ExoticWrapper(v: Exotic): Wrapper<Exotic>(v)
object MyBase {
fun derived() = Derived<String>()
fun exotic(x: Int) = Exotic(x)
fun derivedWrapper() = DerivedWrapper(derived())
fun exoticWrapper(x: Int) = ExoticWrapper(exotic(x))
}
class My(val x: Int) {
val wrapper/*: Wrapper<*>*/ by Properties.calcVal {
val y = x + 1
when {
y > 0 -> MyBase.derivedWrapper()
x < 0 -> MyBase.exoticWrapper(x)
else -> throw java.lang.NullPointerException("")
}
}
}
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
// !WITH_NEW_INFERENCE
// NI_EXPECTED_FILE
// JAVAC_EXPECTED_FILE
@@ -3,7 +3,7 @@ FILE fqName:<root> fileName:/whenUnusedExpression.kt
VALUE_PARAMETER name:b index:0 type:kotlin.Boolean
VALUE_PARAMETER name:i index:1 type:kotlin.Int
BLOCK_BODY
WHEN type=kotlin.Int? origin=IF
WHEN type=kotlin.Int origin=IF
BRANCH
if: GET_VAR 'b: kotlin.Boolean declared in <root>.test' type=kotlin.Boolean origin=null
then: BLOCK type=kotlin.Int origin=WHEN