diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index 7d999eb4c24..da0ab94691a 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -2311,6 +2311,21 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.FINITE_BOUNDS_VIOLATION) { firDiagnostic -> + FiniteBoundsViolationImpl( + firDiagnostic as KtPsiDiagnostic, + token, + ) + } + add(FirErrors.FINITE_BOUNDS_VIOLATION_IN_JAVA) { firDiagnostic -> + FiniteBoundsViolationInJavaImpl( + firDiagnostic.a.map { firBasedSymbol -> + firSymbolBuilder.buildSymbol(firBasedSymbol) + }, + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirErrors.DEPRECATED_TYPE_PARAMETER_SYNTAX) { firDiagnostic -> DeprecatedTypeParameterSyntaxImpl( firDiagnostic as KtPsiDiagnostic, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 77430c1d901..77956f1791a 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -1644,6 +1644,15 @@ sealed interface KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = CyclicGenericUpperBound::class } + interface FiniteBoundsViolation : KtFirDiagnostic { + override val diagnosticClass get() = FiniteBoundsViolation::class + } + + interface FiniteBoundsViolationInJava : KtFirDiagnostic { + override val diagnosticClass get() = FiniteBoundsViolationInJava::class + val containingTypes: List + } + interface DeprecatedTypeParameterSyntax : KtFirDiagnostic { override val diagnosticClass get() = DeprecatedTypeParameterSyntax::class } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index b378a3b6aeb..c89d293afee 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -1977,6 +1977,17 @@ internal class CyclicGenericUpperBoundImpl( token: KtLifetimeToken, ) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.CyclicGenericUpperBound +internal class FiniteBoundsViolationImpl( + firDiagnostic: KtPsiDiagnostic, + token: KtLifetimeToken, +) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.FiniteBoundsViolation + +internal class FiniteBoundsViolationInJavaImpl( + override val containingTypes: List, + firDiagnostic: KtPsiDiagnostic, + token: KtLifetimeToken, +) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.FiniteBoundsViolationInJava + internal class DeprecatedTypeParameterSyntaxImpl( firDiagnostic: KtPsiDiagnostic, token: KtLifetimeToken, diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 8d74dd99077..0a172362c85 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -753,6 +753,11 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val CYCLIC_GENERIC_UPPER_BOUND by error() + val FINITE_BOUNDS_VIOLATION by error() + val FINITE_BOUNDS_VIOLATION_IN_JAVA by error { + parameter>("containingTypes") + } + val DEPRECATED_TYPE_PARAMETER_SYNTAX by error(PositioningStrategy.TYPE_PARAMETERS_LIST) val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning() diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index c1a7b39b5e4..5b69f9db6ad 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -448,6 +448,8 @@ object FirErrors { val IMPLICIT_NOTHING_RETURN_TYPE by error0(SourceElementPositioningStrategies.NAME_IDENTIFIER) val IMPLICIT_NOTHING_PROPERTY_TYPE by error0(SourceElementPositioningStrategies.NAME_IDENTIFIER) val CYCLIC_GENERIC_UPPER_BOUND by error0() + val FINITE_BOUNDS_VIOLATION by error0() + val FINITE_BOUNDS_VIOLATION_IN_JAVA by error1>>() val DEPRECATED_TYPE_PARAMETER_SYNTAX by error0(SourceElementPositioningStrategies.TYPE_PARAMETERS_LIST) val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning0() val DYNAMIC_SUPERTYPE by error0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt index 3e6b29c343e..ca7f8ffc8af 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/CommonDeclarationCheckers.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.fir.analysis.cfa.FirPropertyInitializationAnalyzer import org.jetbrains.kotlin.fir.analysis.checkers.cfa.FirControlFlowChecker import org.jetbrains.kotlin.fir.analysis.checkers.declaration.* import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirAnonymousFunctionParametersChecker +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFiniteBoundRestrictionChecker import org.jetbrains.kotlin.fir.analysis.checkers.syntax.* object CommonDeclarationCheckers : DeclarationCheckers() { @@ -129,6 +130,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() { FirPropertyInitializationChecker, FirDelegateFieldTypeMismatchChecker, FirMultipleDefaultsInheritedFromSupertypesChecker, + FirFiniteBoundRestrictionChecker, ) override val constructorCheckers: Set diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFiniteBoundRestrictionChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFiniteBoundRestrictionChecker.kt new file mode 100644 index 00000000000..9b066aaf74e --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirFiniteBoundRestrictionChecker.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2010-2023 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.analysis.checkers.declaration + +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.analysis.checkers.collectUpperBounds +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.typeParameterSymbols +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.declarations.FirRegularClass +import org.jetbrains.kotlin.fir.scopes.impl.toConeType +import org.jetbrains.kotlin.fir.symbols.impl.FirClassifierSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.utils.DFS + +/** + * @see org.jetbrains.kotlin.resolve.FiniteBoundRestrictionChecker + */ +object FirFiniteBoundRestrictionChecker : FirRegularClassChecker() { + override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) { + if (declaration.typeParameters.isEmpty()) return + + // TODO, KT-61103: Improve documentation on finite bounds validation, especially B-closure and constituent types definition. + // For every projection type argument A in every generic type B<…> in the set of constituent types + // of every type in the B-closure of 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 edges = buildTypeEdges(declaration, context.session) + + val problemNodes = edges.keys.filterTo(mutableSetOf()) { isInCycle(it, edges) } + if (problemNodes.isEmpty()) return + + for (ref in declaration.typeParameters) { + if (problemNodes.remove(ref.toConeType())) { + reporter.reportOn(ref.source, FirErrors.FINITE_BOUNDS_VIOLATION, context) + return + } + } + + val problemSymbols = problemNodes.mapNotNullTo(mutableSetOf()) { it.toSymbol(context.session) as? FirTypeParameterSymbol } + if (problemSymbols.any { it.source != null }) return + + val containers = problemSymbols.map { it.containingDeclarationSymbol } + reporter.reportOn(declaration.source, FirErrors.FINITE_BOUNDS_VIOLATION_IN_JAVA, containers, context) + } + + private fun buildTypeEdges(declaration: FirRegularClass, session: FirSession): Map> { + val edges = mutableMapOf>() + + val visitedSymbols = mutableSetOf>() + fun visit(coneType: ConeKotlinType) { + val constituentTypes = mutableSetOf() + for (type in coneType.collectUpperBounds()) { + type.forEachType { constituentTypes.add(it) } + } + + for (type in constituentTypes) { + val symbol = type.toSymbol(session) + val parameters = symbol?.typeParameterSymbols ?: continue + + if (visitedSymbols.add(symbol)) { + parameters.forEach { visit(it.toConeType()) } + } + if (parameters.size != type.typeArguments.size) continue + + for (i in parameters.indices) { + if (type.typeArguments[i].kind != ProjectionKind.INVARIANT) { + val parameter = parameters[i].toConeType() + edges.getOrPut(coneType) { mutableSetOf() }.add(parameter) + edges.getOrPut(parameter) { mutableSetOf() } + } + } + } + } + + declaration.typeParameters.forEach { visit(it.toConeType()) } + + return edges + } + + private fun isInCycle(start: ConeKotlinType, edges: Map>): Boolean { + var containsCycle = false + + val dfsNeighbors = DFS.Neighbors { edges[it] ?: emptyList() } + + val dfsVisited = object : DFS.VisitedWithSet() { + override fun checkAndMarkVisited(current: ConeKotlinType): Boolean { + val added = super.checkAndMarkVisited(current) + if (!added && current == start) { + containsCycle = true + } + return added + } + } + + val dfsHandler = object : DFS.AbstractNodeHandler() { + override fun result() {} + } + + DFS.dfs(listOf(start), dfsNeighbors, dfsVisited, dfsHandler) + + return containsCycle + } +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt index 275a1a157ff..11ae089e1b0 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrorsDefaultMessages.kt @@ -243,6 +243,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_PROPERT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_PROPERTY_WITH_BACKING_FIELD import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FINAL_SUPERTYPE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FINAL_UPPER_BOUND +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FINITE_BOUNDS_VIOLATION +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FINITE_BOUNDS_VIOLATION_IN_JAVA import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FLOAT_LITERAL_OUT_OF_RANGE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_BINARY_MOD import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_IDENTITY_EQUALS @@ -1357,6 +1359,9 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() { map.put(CYCLIC_GENERIC_UPPER_BOUND, "Type parameter has cyclic upper bounds") + map.put(FINITE_BOUNDS_VIOLATION, "This type parameter violates the Finite Bound Restriction") + map.put(FINITE_BOUNDS_VIOLATION_IN_JAVA, "Violation of Finite Bound Restriction for {0}", commaSeparated(DECLARATION_NAME)) + map.put(DEPRECATED_TYPE_PARAMETER_SYNTAX, "Type parameters must be placed before the name of the function") map.put( diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.fir.kt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.fir.kt deleted file mode 100644 index 6c844042dee..00000000000 --- a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.fir.kt +++ /dev/null @@ -1,16 +0,0 @@ -interface A0> -interface A1> -interface A2> -interface A3> -interface A4?> - -interface B0> -interface B1> - -interface AA> -interface BB>> - -interface A> - -class X -class D>>> diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.kt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.kt index c08d999365c..167c0f6dc1f 100644 --- a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.kt +++ b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithOneTypeParameter.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL interface A0> interface A1<T : A1<*>> interface A2<T : A2> diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.fir.kt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.fir.kt deleted file mode 100644 index cb7f8d4438f..00000000000 --- a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.fir.kt +++ /dev/null @@ -1,12 +0,0 @@ -// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED - -interface C0> - -interface C1, S : C1> // T -> S, S -> S -interface C2, S : C2<*, S>> // T -> S, S -> T - -interface D1 where T : U, U: D1<*, U> -interface D2 where T : U?, U: D2<*, *> -interface D3 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.kt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.kt index fc37115ddc8..58e3464a2a5 100644 --- a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.kt +++ b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/CasesWithTwoTypeParameters.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // !DIAGNOSTICS: -UPPER_BOUND_VIOLATED interface C0> diff --git a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.fir.kt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.fir.kt deleted file mode 100644 index 48fa31cfc5c..00000000000 --- a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.fir.kt +++ /dev/null @@ -1,5 +0,0 @@ -// 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.kt b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.kt index 4975f00c566..c9ca3c5fcf4 100644 --- a/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.kt +++ b/compiler/testData/diagnostics/tests/declarationChecks/finiteBoundRestriction/JavaSuperType.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // FILE: A.java public class A {} diff --git a/compiler/testData/diagnostics/tests/generics/starProjections/invalid.fir.kt b/compiler/testData/diagnostics/tests/generics/starProjections/invalid.fir.kt index ca8fac5d78f..6de7750a44e 100644 --- a/compiler/testData/diagnostics/tests/generics/starProjections/invalid.fir.kt +++ b/compiler/testData/diagnostics/tests/generics/starProjections/invalid.fir.kt @@ -7,7 +7,7 @@ class A> { fun foo(): T = null!! } -class Inv2>(val x: T) +class Inv2<T : Inv2>(val x: T) fun main(a: A<*>, j: JavaClass<*>, i2: Inv2<*>) { // Probably it's too restrictive to suppose star projection type here as Any?, diff --git a/compiler/testData/diagnostics/tests/regressions/kt9633.fir.kt b/compiler/testData/diagnostics/tests/regressions/kt9633.fir.kt deleted file mode 100644 index 21f1bf4be71..00000000000 --- a/compiler/testData/diagnostics/tests/regressions/kt9633.fir.kt +++ /dev/null @@ -1,2 +0,0 @@ -// KT-9633: SOE occurred before -interface A> \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/regressions/kt9633.kt b/compiler/testData/diagnostics/tests/regressions/kt9633.kt index 69bb82c7e86..b7948566601 100644 --- a/compiler/testData/diagnostics/tests/regressions/kt9633.kt +++ b/compiler/testData/diagnostics/tests/regressions/kt9633.kt @@ -1,2 +1,3 @@ +// FIR_IDENTICAL // KT-9633: SOE occurred before interface A<T : A> \ No newline at end of file