From 1c36090b6dd07e0376771312df0905f36d207613 Mon Sep 17 00:00:00 2001 From: Michael Nedzelsky Date: Sat, 17 Oct 2015 13:07:28 +0300 Subject: [PATCH] check for violation of Finite Bound Restriction and Non-Expansive Inheritance Restriction --- .../jetbrains/kotlin/diagnostics/Errors.java | 2 + .../rendering/DefaultErrorMessages.java | 2 + .../kotlin/resolve/DeclarationsChecker.java | 2 + .../resolve/FiniteBoundRestrictionChecker.kt | 136 ++++++++++++++ ...nExpansiveInheritanceRestrictionChecker.kt | 166 ++++++++++++++++++ .../CasesWithOneTypeParameter.kt | 17 ++ .../CasesWithOneTypeParameter.txt | 69 ++++++++ .../CasesWithTwoTypeParameters.kt | 12 ++ .../CasesWithTwoTypeParameters.txt | 43 +++++ .../finiteBoundRestriction/JavaSuperType.kt | 5 + .../finiteBoundRestriction/JavaSuperType.txt | 15 ++ .../JavaWithKotlin.kt | 14 ++ .../JavaWithKotlin.txt | 25 +++ .../JavaWithKotlin2.kt | 16 ++ .../JavaWithKotlin2.txt | 31 ++++ .../PureKotlin.kt | 28 +++ .../PureKotlin.txt | 121 +++++++++++++ .../checkers/JetDiagnosticsTestGenerated.java | 54 ++++++ .../org/jetbrains/kotlin/types/TypeUtils.kt | 42 ++++- 19 files changed, 797 insertions(+), 3 deletions(-) create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/resolve/FiniteBoundRestrictionChecker.kt create mode 100644 compiler/frontend/src/org/jetbrains/kotlin/resolve/NonExpansiveInheritanceRestrictionChecker.kt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.kt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.txt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.kt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.txt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.kt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.txt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin.kt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin.txt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin2.kt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin2.txt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/PureKotlin.kt create mode 100644 compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/PureKotlin.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 0cd9a0c4d93..d42f9fe8235 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -99,6 +99,8 @@ public interface Errors { DiagnosticFactory1 REDUNDANT_PROJECTION = DiagnosticFactory1.create(WARNING, VARIANCE_IN_PROJECTION); DiagnosticFactory1 TYPE_VARIANCE_CONFLICT = DiagnosticFactory1.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); + DiagnosticFactory1 FINITE_BOUNDS_VIOLATION = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 EXPANSIVE_INHERITANCE = DiagnosticFactory1.create(ERROR); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index f276d26b42f..a72e0ca0a59 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -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); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.java index 1073bf92da2..505f327a721 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.java @@ -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); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/FiniteBoundRestrictionChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/FiniteBoundRestrictionChecker.kt new file mode 100644 index 00000000000..f67279fbbb6 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/FiniteBoundRestrictionChecker.kt @@ -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 = hashSetOf() + private val edgeLists = hashMapOf>() + private val processedTypeConstructors = hashSetOf() + + fun build(): Graph { + buildGraph(typeConstructor) + + return object : Graph { + override val nodes = this@GraphBuilder.nodes + 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) = this@isInCycle.getNeighbors(current) + } + + DFS.dfs(listOf(from), neighbors, visited, handler) + + return result + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/NonExpansiveInheritanceRestrictionChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/NonExpansiveInheritanceRestrictionChecker.kt new file mode 100644 index 00000000000..03f2406b2ad --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/NonExpansiveInheritanceRestrictionChecker.kt @@ -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() + private val expansiveEdges = hashSetOf>() + private val edgeLists = hashMapOf>() + + fun build(): Graph { + doBuildGraph(typeConstructor) + + return object : Graph { + override fun getNeighbors(node: TypeParameterDescriptor) = edgeLists[node] ?: emptyList() + 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() + + 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(val from: T, val to: T) + + private interface Graph { + fun getNeighbors(node: T): Collection + val expansiveEdges: Set> + } + + private fun Graph.isEdgeInCycle(edge: ExpansiveEdge) = edge.from in collectReachable(edge.to) + + private fun Graph.collectReachable(from: T): List { + val handler = object : DFS.NodeHandlerWithListResult() { + override fun afterChildren(current: T?) { + result.add(current) + } + } + + val neighbors = object : DFS.Neighbors { + override fun getNeighbors(current: T): Iterable = this@collectReachable.getNeighbors(current) + } + + DFS.dfs(listOf(from), neighbors, handler) + + return handler.result() + } +} diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.kt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.kt new file mode 100644 index 00000000000..ee06ecb1f27 --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.kt @@ -0,0 +1,17 @@ +interface A0> +interface A1<T : A1<*>> +interface A2<T : A2> +// StackOverflowError +//interface A3> +interface A4<T : A4<*>?> + +interface B0<T : B1<*>> +interface B1<T : B0<*>> + +interface AA<T: AA<*>> +interface BB>> + +interface A> + +class X +class D>>> \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.txt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.txt new file mode 100644 index 00000000000..8b9cf32486a --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.txt @@ -0,0 +1,69 @@ +package + +public interface A> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface A0> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface A1> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface A2> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface A4?> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface AA> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface B0> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface B1> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface BB>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class D>>> { + public constructor D>>>() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class X { + public constructor X() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.kt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.kt new file mode 100644 index 00000000000..fc37115ddc8 --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.kt @@ -0,0 +1,12 @@ +// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED + +interface C0> + +interface C1, S : C1> // T -> S, S -> S +interface C2<T : C2, S : C2<*, S>> // T -> S, S -> T + +interface D1<T, U> where T : U, U: D1<*, U> +interface D2<T, U> where T : U?, U: D2<*, *> +interface D3<T, U, V> where T : U, U : V, V: D3<*, *, V> + +interface A where T : A, U: A> \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.txt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.txt new file mode 100644 index 00000000000..ea71749d2c5 --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.txt @@ -0,0 +1,43 @@ +package + +public interface A, /*1*/ U : A>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface C0> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface C1, /*1*/ S : C1> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface C2, /*1*/ S : C2<*, S>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface D1> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface D2> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface D3> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.kt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.kt new file mode 100644 index 00000000000..8a144d2649b --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.kt @@ -0,0 +1,5 @@ +// FILE: A.java +public class A {} + +// FILE: 1.kt +class B> \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.txt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.txt new file mode 100644 index 00000000000..2f959f5adb0 --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.txt @@ -0,0 +1,15 @@ +package + +public open class A>!> { + public constructor A>!>() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class B> { + public constructor B>() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin.kt b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin.kt new file mode 100644 index 00000000000..42f0721e23d --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin.kt @@ -0,0 +1,14 @@ +// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED + +//// FILE: D.java +public interface D {} + +// FILE: Q.java +public interface Q {} + +// FILE: C.java +public interface C extends D> {} + +// FILE: 1.kt +interface PY2> : Q, C>> + diff --git a/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin.txt b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin.txt new file mode 100644 index 00000000000..54eee1ed9c7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin.txt @@ -0,0 +1,25 @@ +package + +public interface C : D!> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface D { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface P : Q, C>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface Q { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin2.kt b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin2.kt new file mode 100644 index 00000000000..a5174b63a56 --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin2.kt @@ -0,0 +1,16 @@ +// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED + +//// FILE: D.java +public interface D {} + +// FILE: Q.java +public interface Q {} + +// FILE: C.java +public interface C extends D> {} + +// FILE: P.java +public interface P extends Q, C>> {} + +// FILE: 1.kt +interface P1 : P \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin2.txt b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin2.txt new file mode 100644 index 00000000000..85b72240990 --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin2.txt @@ -0,0 +1,31 @@ +package + +public interface C : D!> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface D { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface P : Q!, C!>!> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface P1 : P { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface Q { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/PureKotlin.kt b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/PureKotlin.kt new file mode 100644 index 00000000000..0f06efabcf3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/PureKotlin.kt @@ -0,0 +1,28 @@ +// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED + +interface A +interface B : A> + +interface N0 +interface C0<X> : N0>>> + +interface N1 +interface C1<X> : N1>>> +interface C2 : C1> + +interface C<X> : D> +interface PY2> : Q, C>> +interface Q +interface D + +interface E0 +interface E1 +interface E2 : E0> + +interface F0 +interface F1, U : F2<*>> +interface F2 : F0, T>> + +interface G0 +interface G1> +interface G2 : G0, T>> \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/PureKotlin.txt b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/PureKotlin.txt new file mode 100644 index 00000000000..5d416b0e778 --- /dev/null +++ b/compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/PureKotlin.txt @@ -0,0 +1,121 @@ +package + +public interface A { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface B : A> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface C : D> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface C0 : N0>>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface C1 : N1>>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface C2 : C1> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface D { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface E0 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface E1 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface E2 : E0> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface F0 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface F1, /*1*/ U : F2<*>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface F2 : F0, T>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface G0 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface G1> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface G2 : G0, T>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface N0 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface N1 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface P : Q, C>> { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface Q { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index 4835dc51a1f..44cc2c1e217 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -4206,6 +4206,33 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class FiniteBoundRestriction extends AbstractJetDiagnosticsTest { + public void testAllFilesPresentInFiniteBoundRestriction() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("CasesWithOneTypeParameter.kt") + public void testCasesWithOneTypeParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.kt"); + doTest(fileName); + } + + @TestMetadata("CasesWithTwoTypeParameters.kt") + public void testCasesWithTwoTypeParameters() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.kt"); + doTest(fileName); + } + + @TestMetadata("JavaSuperType.kt") + public void testJavaSuperType() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/tests/declarationChecks/multiDeclarations") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) @@ -4268,6 +4295,33 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } } + + @TestMetadata("compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class NonExpansiveInheritanceRestriction extends AbstractJetDiagnosticsTest { + public void testAllFilesPresentInNonExpansiveInheritanceRestriction() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("JavaWithKotlin.kt") + public void testJavaWithKotlin() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin.kt"); + doTest(fileName); + } + + @TestMetadata("JavaWithKotlin2.kt") + public void testJavaWithKotlin2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/JavaWithKotlin2.kt"); + doTest(fileName); + } + + @TestMetadata("PureKotlin.kt") + public void testPureKotlin() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/declarationChecks/nonExpansiveInheritanceRestriction/PureKotlin.kt"); + doTest(fileName); + } + } } @TestMetadata("compiler/testData/diagnostics/tests/defaultArguments") diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt index 26dd0ee9bd8..0cb5b16ba61 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt @@ -25,9 +25,7 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.checker.KotlinTypeChecker import org.jetbrains.kotlin.utils.toReadOnlyList -import java.util.ArrayDeque -import java.util.ArrayList -import java.util.LinkedHashSet +import java.util.* public enum class TypeNullability { NOT_NULL, @@ -142,3 +140,41 @@ public fun KotlinType.isDefaultBound(): Boolean = KotlinBuiltIns.isDefaultBound( public fun createProjection(type: KotlinType, projectionKind: Variance, typeParameterDescriptor: TypeParameterDescriptor?): TypeProjection = TypeProjectionImpl(if (typeParameterDescriptor?.variance == projectionKind) Variance.INVARIANT else projectionKind, type) + +fun Collection.closure(f: (KtType) -> Collection): Collection { + if (size == 0) return this + + val result = HashSet(this) + var elementsToCheck = result + var oldSize = 0 + while (result.size > oldSize) { + oldSize = result.size + val toAdd = hashSetOf() + elementsToCheck.forEach { toAdd.addAll(f(it)) } + result.addAll(toAdd) + elementsToCheck = toAdd + } + + return result +} + +fun boundClosure(types: Collection): Collection = + types.closure { type -> TypeUtils.getTypeParameterDescriptorOrNull(type)?.upperBounds ?: emptySet() } + +fun constituentTypes(types: Collection): Collection { + val result = hashSetOf() + constituentTypes(result, types) + return result +} + +private fun constituentTypes(result: MutableSet, types: Collection) { + result.addAll(types) + for (type in types) { + if (type.isFlexible()) { + with (type.flexibility()) { constituentTypes(result, setOf(lowerBound, upperBound)) } + } + else { + constituentTypes(result, type.arguments.filterNot { it.isStarProjection }.map { it.type }) + } + } +}