org.jetbrains.kotlin.resolve.FiniteBoundRestrictionChecker.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.types.TypeConstructor
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.boundClosure
import org.jetbrains.kotlin.types.typeUtil.constituentTypes
import org.jetbrains.kotlin.utils.DFS
public object FiniteBoundRestrictionChecker {
@JvmStatic
fun check(
declaration: KtClass,
classDescriptor: ClassDescriptor,
diagnosticHolder: DiagnosticSink
) {
val typeConstructor = classDescriptor.typeConstructor
if (typeConstructor.parameters.isEmpty()) return
// For every projection type argument A in every generic type B<…> in the set of constituent types
// of every type in the B-closure the set of declared upper bounds of every type parameter T add an
// edge from T to U, where U is the type parameter of the declaration of B<…> corresponding to the type argument A.
// It is a compile-time error if the graph G has a cycle.
val graph = GraphBuilder(typeConstructor).build()
val problemNodes = graph.nodes.filter { graph.isInCycle(it) }
if (problemNodes.isEmpty()) return
for (typeParameter in typeConstructor.parameters) {
if (typeParameter in problemNodes) {
val element = DescriptorToSourceUtils.descriptorToDeclaration(typeParameter) ?: declaration
diagnosticHolder.report(Errors.FINITE_BOUNDS_VIOLATION.on(element))
return
}
}
if (problemNodes.any { it.source != SourceElement.NO_SOURCE }) return
val typeFqNames = problemNodes.map { it.containingDeclaration }.map { it.fqNameUnsafe.asString() }.toSortedSet()
diagnosticHolder.report(Errors.FINITE_BOUNDS_VIOLATION_IN_JAVA.on(declaration, typeFqNames.joinToString(", ")))
}
private class GraphBuilder(val typeConstructor: TypeConstructor) {
private val nodes: MutableSet = hashSetOf()
private val edgeLists = hashMapOf>()
private val processedTypeConstructors = hashSetOf()
fun build(): Graph {
buildGraph(typeConstructor)
return object : Graph {
override val nodes = [email protected]
override fun getNeighbors(node: TypeParameterDescriptor) = edgeLists[node] ?: emptyList()
}
}
private fun addEdge(from: TypeParameterDescriptor, to: TypeParameterDescriptor) = edgeLists.getOrPut(from) { arrayListOf() }.add(to)
private fun buildGraph(typeConstructor: TypeConstructor) {
typeConstructor.parameters.forEach { typeParameter ->
val boundClosure = boundClosure(typeParameter.upperBounds)
val constituentTypes = constituentTypes(boundClosure)
for (constituentType in constituentTypes) {
val constituentTypeConstructor = constituentType.constructor
if (constituentTypeConstructor !in processedTypeConstructors) {
processedTypeConstructors.add(constituentTypeConstructor)
buildGraph(constituentTypeConstructor)
}
if (constituentTypeConstructor.parameters.size != constituentType.arguments.size) continue
constituentType.arguments.forEachIndexed { i, typeProjection ->
if (typeProjection.projectionKind != Variance.INVARIANT) {
nodes.add(typeParameter)
nodes.add(constituentTypeConstructor.parameters[i])
addEdge(typeParameter, constituentTypeConstructor.parameters[i])
}
}
}
}
}
}
private interface Graph {
val nodes: Set
fun getNeighbors(node: T): List
}
private fun Graph.isInCycle(from: T): Boolean {
var result = false
val visited = object : DFS.VisitedWithSet() {
override fun checkAndMarkVisited(current: T): Boolean {
val added = super.checkAndMarkVisited(current)
if (!added && current == from) {
result = true
}
return added
}
}
val handler = object : DFS.AbstractNodeHandler() {
override fun result() {}
}
val neighbors = object : DFS.Neighbors {
override fun getNeighbors(current: T) = [email protected](current)
}
DFS.dfs(listOf(from), neighbors, visited, handler)
return result
}
}