[NI] Introduce lambda result dependency edges
Given a lambda result type R_L with constraint R_L <: T, for each constituent type variable V in T with variance matching approximation direction for V, consider a dependency edge V -> R (lambda-result-dependency). E.g., given a constraint: R <: Out<V> where V is approximated down (to sub-type). After R is fixed, we obtain constraint Out<T> <: Out<V> which is incorporated as T <: V which is a relevant constraint for V.
This commit is contained in:
committed by
Stanislav Erokhin
parent
13e8720ddc
commit
2bf252afe6
+118
-42
@@ -16,17 +16,16 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.resolve.calls.inference.components
|
package org.jetbrains.kotlin.resolve.calls.inference.components
|
||||||
|
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
|
import org.jetbrains.kotlin.resolve.calls.inference.model.*
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintKind
|
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.model.LambdaTypeVariable
|
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
|
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedLambdaArgument
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedLambdaArgument
|
||||||
import org.jetbrains.kotlin.types.*
|
import org.jetbrains.kotlin.types.*
|
||||||
import org.jetbrains.kotlin.types.checker.NewKotlinTypeChecker
|
import org.jetbrains.kotlin.types.checker.NewKotlinTypeChecker
|
||||||
import org.jetbrains.kotlin.types.checker.isIntersectionType
|
import org.jetbrains.kotlin.types.checker.isIntersectionType
|
||||||
import org.jetbrains.kotlin.utils.DFS
|
import org.jetbrains.kotlin.utils.DFS
|
||||||
import org.jetbrains.kotlin.utils.SmartList
|
import org.jetbrains.kotlin.utils.SmartList
|
||||||
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
private typealias Variable = VariableWithConstraints
|
private typealias Variable = VariableWithConstraints
|
||||||
|
|
||||||
@@ -51,27 +50,84 @@ class FixationOrderCalculator {
|
|||||||
topReturnType: UnwrappedType
|
topReturnType: UnwrappedType
|
||||||
): List<NodeWithDirection> = DependencyGraph(c).getCompletionOrder(topReturnType)
|
): List<NodeWithDirection> = DependencyGraph(c).getCompletionOrder(topReturnType)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* U depends-on V if one of the following conditions is met:
|
||||||
|
*
|
||||||
|
* LAMBDA
|
||||||
|
* result type U depends-on all parameters types V of the corresponding lambda
|
||||||
|
*
|
||||||
|
* LAMBDA-RESULT
|
||||||
|
* V is a lambda result type variable,
|
||||||
|
* V <: T constraint exists for V,
|
||||||
|
* U is a constituent type of T in position matching approximation direction for U
|
||||||
|
*
|
||||||
|
* STRONG-CONSTRAINT
|
||||||
|
* 'U <op> T' constraint exists for U,
|
||||||
|
* <op> is a constraint operator relevant to U approximation direction,
|
||||||
|
* V is a proper constituent type of T
|
||||||
|
*
|
||||||
|
* WEAK-CONSTRAINT
|
||||||
|
* 'U <op> V' constraint exists for U,
|
||||||
|
* <op> is a constraint operator relevant to U approximation direction
|
||||||
|
*/
|
||||||
private class DependencyGraph(val c: Context) {
|
private class DependencyGraph(val c: Context) {
|
||||||
private val directions = HashMap<Variable, ResolveDirection>()
|
private val directions = HashMap<Variable, ResolveDirection>()
|
||||||
|
|
||||||
|
private val lambdaResultDependencyEdges = HashMap<Variable, MutableList<Variable>>()
|
||||||
|
|
||||||
// first in the list -- first fix
|
// first in the list -- first fix
|
||||||
fun getCompletionOrder(topReturnType: UnwrappedType): List<NodeWithDirection> {
|
fun getCompletionOrder(topReturnType: UnwrappedType): List<NodeWithDirection> {
|
||||||
setupDirections(topReturnType)
|
setupDirections(topReturnType)
|
||||||
|
|
||||||
|
computeLambdaResultDependencyEdges()
|
||||||
|
|
||||||
return topologicalOrderWith0Priority().map { NodeWithDirection(it, directions[it] ?: ResolveDirection.UNKNOWN) }
|
return topologicalOrderWith0Priority().map { NodeWithDirection(it, directions[it] ?: ResolveDirection.UNKNOWN) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun computeLambdaResultDependencyEdges() {
|
||||||
|
val resolvedLambdaArguments = c.lambdaArguments.associateBy({ it.argument }, { it })
|
||||||
|
|
||||||
|
for (variable in c.notFixedTypeVariables.values) {
|
||||||
|
val lambdaResultVariable = variable.typeVariable.takeLambdaResultVariable() ?: continue
|
||||||
|
|
||||||
|
val lambdaArgument = lambdaResultVariable.lambdaArgument
|
||||||
|
|
||||||
|
if (resolvedLambdaArguments[lambdaArgument]?.analyzed ?: false) continue
|
||||||
|
|
||||||
|
for (constraint in variable.constraints) {
|
||||||
|
val initialDirection = when (constraint.kind) {
|
||||||
|
ConstraintKind.LOWER -> ResolveDirection.TO_SUBTYPE
|
||||||
|
ConstraintKind.UPPER -> ResolveDirection.TO_SUPERTYPE
|
||||||
|
ConstraintKind.EQUALITY -> ResolveDirection.UNKNOWN // ???
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint.type.visitType(initialDirection) { constituentVariable, direction ->
|
||||||
|
val constituentVariableDirection = directions.getOrElse(constituentVariable) { ResolveDirection.UNKNOWN }
|
||||||
|
|
||||||
|
val constituentTypeVariable = constituentVariable.typeVariable
|
||||||
|
if (constituentTypeVariable is LambdaTypeVariable) {
|
||||||
|
if (constituentTypeVariable.lambdaArgument == lambdaArgument) return@visitType
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction == ResolveDirection.UNKNOWN || direction == constituentVariableDirection) {
|
||||||
|
lambdaResultDependencyEdges.getOrPut(constituentVariable) { SmartList() }.add(variable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun topologicalOrderWith0Priority(): List<Variable> {
|
private fun topologicalOrderWith0Priority(): List<Variable> {
|
||||||
val handler = object : DFS.CollectingNodeHandler<Variable, Variable, LinkedHashSet<Variable>>(LinkedHashSet()) {
|
val handler = object : DFS.CollectingNodeHandler<Variable, Variable, LinkedHashSet<Variable>>(LinkedHashSet()) {
|
||||||
override fun afterChildren(current: Variable) {
|
override fun afterChildren(current: Variable) {
|
||||||
// we have guaranty that from end of 0 edge there is no other edges with priority 0
|
// LAMBDA dependency edges should always be satisfied
|
||||||
result.addAll(get0Edges(current))
|
result.addAll(getLambdaDependencies(current))
|
||||||
|
|
||||||
result.add(current)
|
result.add(current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (typeVariable in c.notFixedTypeVariables.values) {
|
for (typeVariable in c.notFixedTypeVariables.values.sortByTypeVariable()) {
|
||||||
DFS.doDfs(typeVariable, DFS.Neighbors(this::getEdges), DFS.VisitedWithSet<Variable>(), handler)
|
DFS.doDfs(typeVariable, DFS.Neighbors(this::getEdges), DFS.VisitedWithSet<Variable>(), handler)
|
||||||
}
|
}
|
||||||
return handler.result().toList()
|
return handler.result().toList()
|
||||||
@@ -106,57 +162,77 @@ class FixationOrderCalculator {
|
|||||||
|
|
||||||
directions[variable] = direction
|
directions[variable] = direction
|
||||||
|
|
||||||
for ((otherVariable, otherDirection) in get12Edges(variable, direction)) {
|
for ((otherVariable, otherDirection) in getConstraintDependencies(variable, direction)) {
|
||||||
enterToNode(otherVariable, otherDirection)
|
enterToNode(otherVariable, otherDirection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getEdges(variable: Variable): List<Variable> {
|
private fun getEdges(variable: Variable): List<Variable> {
|
||||||
val direction = directions[variable] ?: ResolveDirection.UNKNOWN
|
val direction = directions[variable] ?: ResolveDirection.UNKNOWN
|
||||||
return get12Edges(variable, direction).map(NodeWithDirection::variableWithConstraints) + get0Edges(variable)
|
val constraintEdges =
|
||||||
|
LinkedHashSet<Variable>().also { set ->
|
||||||
|
getConstraintDependencies(variable, direction).mapTo(set) { it.variableWithConstraints }
|
||||||
|
set.addAll(getLambdaResultDependencies(variable))
|
||||||
|
}.toList().sortByTypeVariable()
|
||||||
|
val lambdaEdges = getLambdaDependencies(variable).sortByTypeVariable()
|
||||||
|
return constraintEdges + lambdaEdges
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun Collection<Variable>.sortByTypeVariable() =
|
||||||
* Now we use only priority 0 and {1, 2}.
|
// TODO hack, provide some reasonable stable order
|
||||||
* Current vision of edge priority for type variable \alpha to variable \beta:
|
sortedBy { it.typeVariable.toString() }
|
||||||
* 0 -- { \beta -> \alpha } i.e. return type depend of all parameters types of lambda
|
|
||||||
* 1 -- \alpha <: Inv<\beta> or \alpha >: Pair<Inv<\beta & Any>, Int> ot \alpha <: \beta & Any
|
|
||||||
* 2 -- \alpha <: \beta or \alpha >: \beta?
|
|
||||||
*/
|
|
||||||
private fun get12Edges(variableWithConstraints: Variable, direction: ResolveDirection, include2: Boolean = true): List<NodeWithDirection> {
|
|
||||||
fun isNotInterestingConstraint(direction: ResolveDirection, constraint: Constraint): Boolean {
|
|
||||||
return (direction == ResolveDirection.TO_SUBTYPE && constraint.kind == ConstraintKind.UPPER) ||
|
|
||||||
(direction == ResolveDirection.TO_SUPERTYPE && constraint.kind == ConstraintKind.LOWER)
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = SmartList<NodeWithDirection>()
|
private enum class ConstraintDependencyKind { STRONG, WEAK }
|
||||||
|
|
||||||
for (constraint in variableWithConstraints.constraints) {
|
private fun getConstraintDependencies(
|
||||||
if (isNotInterestingConstraint(direction, constraint)) continue
|
variableWithConstraints: Variable,
|
||||||
|
direction: ResolveDirection,
|
||||||
|
filterByDependencyKind: ConstraintDependencyKind? = null
|
||||||
|
): List<NodeWithDirection> =
|
||||||
|
SmartList<NodeWithDirection>().also { result ->
|
||||||
|
for (constraint in variableWithConstraints.constraints) {
|
||||||
|
if (!isInterestingConstraint(direction, constraint)) continue
|
||||||
|
|
||||||
if (include2 || !c.notFixedTypeVariables.containsKey(constraint.type.constructor)) { // because we collect only type 1 of edges
|
if (filterByDependencyKind == null || filterByDependencyKind == getConstraintDependencyKind(constraint)) {
|
||||||
constraint.type.visitType(direction) { variable, direction ->
|
constraint.type.visitType(direction) { nodeVariable, nodeDirection ->
|
||||||
result.add(NodeWithDirection(variable, direction))
|
result.add(NodeWithDirection(nodeVariable, nodeDirection))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
private fun getConstraintDependencyKind(constraint: Constraint): ConstraintDependencyKind =
|
||||||
}
|
if (c.notFixedTypeVariables.containsKey(constraint.type.constructor))
|
||||||
|
ConstraintDependencyKind.WEAK
|
||||||
|
else
|
||||||
|
ConstraintDependencyKind.STRONG
|
||||||
|
|
||||||
private fun get0Edges(variable: Variable): List<Variable> {
|
private fun isInterestingConstraint(direction: ResolveDirection, constraint: Constraint): Boolean =
|
||||||
val typeVariable = variable.typeVariable
|
!(direction == ResolveDirection.TO_SUBTYPE && constraint.kind == ConstraintKind.UPPER) &&
|
||||||
if (typeVariable !is LambdaTypeVariable || typeVariable.kind != LambdaTypeVariable.Kind.RETURN_TYPE) return emptyList()
|
!(direction == ResolveDirection.TO_SUPERTYPE && constraint.kind == ConstraintKind.LOWER)
|
||||||
|
|
||||||
|
private fun getLambdaResultDependencies(variable: Variable): List<Variable> =
|
||||||
|
lambdaResultDependencyEdges.getOrElse(variable) { emptyList() }
|
||||||
|
|
||||||
|
private fun getLambdaDependencies(variable: Variable): List<Variable> {
|
||||||
|
val typeVariable = variable.typeVariable.takeLambdaResultVariable() ?: return emptyList()
|
||||||
|
|
||||||
val resolvedLambdaArgument = c.lambdaArguments.find { it.argument == typeVariable.lambdaArgument } ?:
|
val resolvedLambdaArgument = c.lambdaArguments.find { it.argument == typeVariable.lambdaArgument } ?:
|
||||||
error("Missing resolved lambda argument for ${typeVariable.lambdaArgument}")
|
error("Missing resolved lambda argument for ${typeVariable.lambdaArgument}")
|
||||||
|
|
||||||
return resolvedLambdaArgument.myTypeVariables.mapNotNull {
|
return buildVariablesList {
|
||||||
if (it.kind == LambdaTypeVariable.Kind.RETURN_TYPE) return@mapNotNull null
|
for (lambdaTypeVariable in resolvedLambdaArgument.myTypeVariables) {
|
||||||
c.notFixedTypeVariables[it.freshTypeConstructor]
|
if (lambdaTypeVariable.kind == LambdaTypeVariable.Kind.RETURN_TYPE) continue
|
||||||
|
addIfNotNull(c.notFixedTypeVariables[lambdaTypeVariable.freshTypeConstructor])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inline fun buildVariablesList(builder: MutableList<Variable>.() -> Unit): List<Variable> =
|
||||||
|
SmartList<Variable>().apply(builder).toList()
|
||||||
|
|
||||||
|
private fun NewTypeVariable.takeLambdaResultVariable(): LambdaTypeVariable? =
|
||||||
|
if (this is LambdaTypeVariable && this.kind == LambdaTypeVariable.Kind.RETURN_TYPE) this else null
|
||||||
|
|
||||||
private fun UnwrappedType.visitType(startDirection: ResolveDirection, action: (variable: Variable, direction: ResolveDirection) -> Unit) =
|
private fun UnwrappedType.visitType(startDirection: ResolveDirection, action: (variable: Variable, direction: ResolveDirection) -> Unit) =
|
||||||
when (this) {
|
when (this) {
|
||||||
@@ -185,12 +261,6 @@ class FixationOrderCalculator {
|
|||||||
val parameters = constructor.parameters
|
val parameters = constructor.parameters
|
||||||
if (parameters.size != arguments.size) return // incorrect type
|
if (parameters.size != arguments.size) return // incorrect type
|
||||||
|
|
||||||
fun ResolveDirection.opposite() = when (this) {
|
|
||||||
ResolveDirection.UNKNOWN -> ResolveDirection.UNKNOWN
|
|
||||||
ResolveDirection.TO_SUPERTYPE -> ResolveDirection.TO_SUBTYPE
|
|
||||||
ResolveDirection.TO_SUBTYPE -> ResolveDirection.TO_SUPERTYPE
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((argument, parameter) in arguments.zip(parameters)) {
|
for ((argument, parameter) in arguments.zip(parameters)) {
|
||||||
if (argument.isStarProjection) continue
|
if (argument.isStarProjection) continue
|
||||||
|
|
||||||
@@ -204,6 +274,12 @@ class FixationOrderCalculator {
|
|||||||
argument.type.unwrap().visitType(innerDirection, action)
|
argument.type.unwrap().visitType(innerDirection, action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ResolveDirection.opposite() = when (this) {
|
||||||
|
ResolveDirection.UNKNOWN -> ResolveDirection.UNKNOWN
|
||||||
|
ResolveDirection.TO_SUPERTYPE -> ResolveDirection.TO_SUBTYPE
|
||||||
|
ResolveDirection.TO_SUBTYPE -> ResolveDirection.TO_SUPERTYPE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+4
-4
@@ -84,14 +84,14 @@ class MutableVariableWithConstraints(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo may be we should use LinkedHasMap
|
|
||||||
class MutableConstraintStorage : ConstraintStorage {
|
class MutableConstraintStorage : ConstraintStorage {
|
||||||
override val allTypeVariables: MutableMap<TypeConstructor, NewTypeVariable> = HashMap()
|
override val allTypeVariables: MutableMap<TypeConstructor, NewTypeVariable> = LinkedHashMap()
|
||||||
override val notFixedTypeVariables: MutableMap<TypeConstructor, MutableVariableWithConstraints> = HashMap()
|
override val notFixedTypeVariables: MutableMap<TypeConstructor, MutableVariableWithConstraints> = LinkedHashMap()
|
||||||
override val initialConstraints: MutableList<InitialConstraint> = ArrayList()
|
override val initialConstraints: MutableList<InitialConstraint> = ArrayList()
|
||||||
override var maxTypeDepthFromInitialConstraints: Int = 1
|
override var maxTypeDepthFromInitialConstraints: Int = 1
|
||||||
override val errors: MutableList<KotlinCallDiagnostic> = ArrayList()
|
override val errors: MutableList<KotlinCallDiagnostic> = ArrayList()
|
||||||
override val fixedTypeVariables: MutableMap<TypeConstructor, UnwrappedType> = HashMap()
|
override val fixedTypeVariables: MutableMap<TypeConstructor, UnwrappedType> = LinkedHashMap()
|
||||||
override val lambdaArguments: MutableList<ResolvedLambdaArgument> = ArrayList()
|
override val lambdaArguments: MutableList<ResolvedLambdaArgument> = ArrayList()
|
||||||
override val innerCalls: MutableList<ResolvedKotlinCall.OnlyResolvedKotlinCall> = ArrayList()
|
override val innerCalls: MutableList<ResolvedKotlinCall.OnlyResolvedKotlinCall> = ArrayList()
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user