check for violation of Finite Bound Restriction and Non-Expansive Inheritance Restriction

This commit is contained in:
Michael Nedzelsky
2015-10-17 13:07:28 +03:00
parent 1413cab1e6
commit 1c36090b6d
19 changed files with 797 additions and 3 deletions
@@ -99,6 +99,8 @@ public interface Errors {
DiagnosticFactory1<KtTypeProjection, ClassifierDescriptor> REDUNDANT_PROJECTION = DiagnosticFactory1.create(WARNING, VARIANCE_IN_PROJECTION);
DiagnosticFactory1<PsiElement, VarianceConflictDiagnosticData> TYPE_VARIANCE_CONFLICT =
DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory1<PsiElement, String> FINITE_BOUNDS_VIOLATION = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> EXPANSIVE_INHERITANCE = DiagnosticFactory1.create(ERROR);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -510,6 +510,8 @@ public class DefaultErrorMessages {
}
});
MAP.put(FINITE_BOUNDS_VIOLATION, "{0}", STRING);
MAP.put(EXPANSIVE_INHERITANCE, "{0}", STRING);
MAP.put(REDUNDANT_PROJECTION, "Projection is redundant: the corresponding type parameter of {0} has the same variance", NAME);
MAP.put(CONFLICTING_PROJECTION, "Projection is conflicting with variance of the corresponding type parameter of {0}. Remove the projection or replace it with ''*''", NAME);
@@ -317,6 +317,8 @@ public class DeclarationsChecker {
checkOpenMembers(classDescriptor);
checkTypeParameters(aClass);
checkTypeParameterConstraints(aClass);
FiniteBoundRestrictionChecker.check(aClass, classDescriptor, trace);
NonExpansiveInheritanceRestrictionChecker.check(aClass, classDescriptor, trace);
if (aClass.isInterface()) {
checkConstructorInInterface(aClass);
@@ -0,0 +1,136 @@
/*
* 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, "Type argument is not within its bounds (violation of Finite Bound Restriction)"))
return
}
}
if (problemNodes.any { it.source != SourceElement.NO_SOURCE }) return
val superTypeFqNames = problemNodes.map { it.containingDeclaration }.map { it.fqNameUnsafe.asString() }.toSortedSet()
diagnosticHolder.report(Errors.FINITE_BOUNDS_VIOLATION.on(declaration, "Violation of Finite Bound Restriction for supertypes: " + superTypeFqNames.joinToString(", ")))
}
private class GraphBuilder(val typeConstructor: TypeConstructor) {
private val nodes: MutableSet<TypeParameterDescriptor> = hashSetOf()
private val edgeLists = hashMapOf<TypeParameterDescriptor, MutableList<TypeParameterDescriptor>>()
private val processedTypeConstructors = hashSetOf<TypeConstructor>()
fun build(): Graph<TypeParameterDescriptor> {
buildGraph(typeConstructor)
return object : Graph<TypeParameterDescriptor> {
override val nodes = this@GraphBuilder.nodes
override fun getNeighbors(node: TypeParameterDescriptor) = edgeLists[node] ?: emptyList<TypeParameterDescriptor>()
}
}
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<T> {
val nodes: Set<T>
fun getNeighbors(node: T): List<T>
}
private fun <T> Graph<T>.isInCycle(from: T): Boolean {
var result = false
val visited = object : DFS.VisitedWithSet<T>() {
override fun checkAndMarkVisited(current: T): Boolean {
val added = super.checkAndMarkVisited(current)
if (!added && current == from) {
result = true
}
return added
}
}
val handler = object : DFS.AbstractNodeHandler<T, Unit>() {
override fun result() {}
}
val neighbors = object : DFS.Neighbors<T> {
override fun getNeighbors(current: T) = this@isInCycle.getNeighbors(current)
}
DFS.dfs(listOf(from), neighbors, visited, handler)
return result
}
}
@@ -0,0 +1,166 @@
/*
* 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.KtType
import org.jetbrains.kotlin.types.TypeConstructor
import org.jetbrains.kotlin.types.TypeUtils
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 NonExpansiveInheritanceRestrictionChecker {
@JvmStatic
fun check(
declaration: KtClass,
classDescriptor: ClassDescriptor,
diagnosticHolder: DiagnosticSink
) {
val typeConstructor = classDescriptor.typeConstructor
if (typeConstructor.parameters.isEmpty()) return
val builder = GraphBuilder(typeConstructor)
val graph = builder.build()
val edgesInCycles = graph.expansiveEdges.filter { graph.isEdgeInCycle(it) }
if (edgesInCycles.isEmpty()) return
val problemNodes = edgesInCycles.flatMap { setOf(it.from, it.to) }
for (typeParameter in typeConstructor.parameters) {
if (typeParameter in problemNodes) {
val element = DescriptorToSourceUtils.descriptorToDeclaration(typeParameter) ?: declaration
diagnosticHolder.report(Errors.EXPANSIVE_INHERITANCE.on(element, "Type argument is not within its bounds (violation of Non-Expansive Inheritance Restriction"))
return
}
}
if (problemNodes.any { it.source != SourceElement.NO_SOURCE }) return
val superTypeFqNames = problemNodes.map { it.containingDeclaration }.map { it.fqNameUnsafe.asString() }.toSortedSet()
diagnosticHolder.report(Errors.EXPANSIVE_INHERITANCE.on(declaration, "Violation of Non-Expansive Inheritance Restriction for supertypes: " + superTypeFqNames.joinToString(", ")))
}
private class GraphBuilder(val typeConstructor: TypeConstructor) {
private val processedTypeConstructors = hashSetOf<TypeConstructor>()
private val expansiveEdges = hashSetOf<ExpansiveEdge<TypeParameterDescriptor>>()
private val edgeLists = hashMapOf<TypeParameterDescriptor, MutableSet<TypeParameterDescriptor>>()
fun build(): Graph<TypeParameterDescriptor> {
doBuildGraph(typeConstructor)
return object : Graph<TypeParameterDescriptor> {
override fun getNeighbors(node: TypeParameterDescriptor) = edgeLists[node] ?: emptyList<TypeParameterDescriptor>()
override val expansiveEdges = this@GraphBuilder.expansiveEdges
}
}
private fun addEdge(from: TypeParameterDescriptor, to: TypeParameterDescriptor, expansive: Boolean = false) {
edgeLists.getOrPut(from) { linkedSetOf() }.add(to)
if (expansive) {
expansiveEdges.add(ExpansiveEdge(from, to))
}
}
private fun doBuildGraph(typeConstructor: TypeConstructor) {
if (typeConstructor.parameters.isEmpty()) return
val typeParameters = typeConstructor.parameters
// For each type parameter T, let ST be the set of all constituent types of all immediate supertypes of the owner of T.
// If T appears as a constituent type of a simple type argument A in a generic type in ST, add an edge from T
// to U, where U is the type parameter corresponding to A, and where the edge is non-expansive if A has the form T or T?,
// the edge is expansive otherwise.
for (constituentType in constituentTypes(typeConstructor.supertypes)) {
val constituentTypeConstructor = constituentType.constructor
if (constituentTypeConstructor !in processedTypeConstructors) {
processedTypeConstructors.add(constituentTypeConstructor)
doBuildGraph(constituentTypeConstructor)
}
if (constituentTypeConstructor.parameters.size != constituentType.arguments.size) continue
constituentType.arguments.forEachIndexed { i, typeProjection ->
if (typeProjection.projectionKind == Variance.INVARIANT) {
val constituents = constituentTypes(setOf(typeProjection.type))
for (typeParameter in typeParameters) {
if (typeParameter.defaultType in constituents || TypeUtils.makeNullable(typeParameter.defaultType) in constituents) {
addEdge(typeParameter, constituentTypeConstructor.parameters[i], !TypeUtils.isTypeParameter(typeProjection.type))
}
}
}
else {
// Furthermore, if T appears as a constituent type of an element of the B-closure of the set of lower and
// upper bounds of a skolem type variable Q in a skolemization of a projected generic type in ST, add an
// expanding edge from T to V, where V is the type parameter corresponding to Q.
val originalTypeParameter = constituentTypeConstructor.parameters[i]
val bounds = hashSetOf<KtType>()
val substitutor = constituentType.substitution.buildSubstitutor()
val adaptedUpperBounds = originalTypeParameter.upperBounds.map { substitutor.substitute(it, Variance.INVARIANT) }.filterNotNull()
bounds.addAll(adaptedUpperBounds)
if (!typeProjection.isStarProjection) {
bounds.add(typeProjection.type)
}
val boundClosure = boundClosure(bounds)
val constituentTypes = constituentTypes(boundClosure)
for (typeParameter in typeParameters) {
if (typeParameter.defaultType in constituentTypes || TypeUtils.makeNullable(typeParameter.defaultType) in constituentTypes) {
addEdge(typeParameter, originalTypeParameter, true)
}
}
}
}
}
}
}
private data class ExpansiveEdge<T>(val from: T, val to: T)
private interface Graph<T> {
fun getNeighbors(node: T): Collection<T>
val expansiveEdges: Set<ExpansiveEdge<T>>
}
private fun <T> Graph<T>.isEdgeInCycle(edge: ExpansiveEdge<T>) = edge.from in collectReachable(edge.to)
private fun <T> Graph<T>.collectReachable(from: T): List<T> {
val handler = object : DFS.NodeHandlerWithListResult<T, T>() {
override fun afterChildren(current: T?) {
result.add(current)
}
}
val neighbors = object : DFS.Neighbors<T> {
override fun getNeighbors(current: T): Iterable<T> = this@collectReachable.getNeighbors(current)
}
DFS.dfs(listOf(from), neighbors, handler)
return handler.result()
}
}