[FIR] Add checker for finite bounds validation
Added a new FIR based checker for both FINITE_BOUNDS_VIOLATION and FINITE_BOUNDS_VIOLATION_IN_JAVA errors. Implementation copied from the existing descriptor based checker with some minor changes. #KT-59378 Fixed
This commit is contained in:
+15
@@ -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,
|
||||
|
||||
+9
@@ -1644,6 +1644,15 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
override val diagnosticClass get() = CyclicGenericUpperBound::class
|
||||
}
|
||||
|
||||
interface FiniteBoundsViolation : KtFirDiagnostic<PsiElement> {
|
||||
override val diagnosticClass get() = FiniteBoundsViolation::class
|
||||
}
|
||||
|
||||
interface FiniteBoundsViolationInJava : KtFirDiagnostic<PsiElement> {
|
||||
override val diagnosticClass get() = FiniteBoundsViolationInJava::class
|
||||
val containingTypes: List<KtSymbol>
|
||||
}
|
||||
|
||||
interface DeprecatedTypeParameterSyntax : KtFirDiagnostic<KtDeclaration> {
|
||||
override val diagnosticClass get() = DeprecatedTypeParameterSyntax::class
|
||||
}
|
||||
|
||||
+11
@@ -1977,6 +1977,17 @@ internal class CyclicGenericUpperBoundImpl(
|
||||
token: KtLifetimeToken,
|
||||
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.CyclicGenericUpperBound
|
||||
|
||||
internal class FiniteBoundsViolationImpl(
|
||||
firDiagnostic: KtPsiDiagnostic,
|
||||
token: KtLifetimeToken,
|
||||
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.FiniteBoundsViolation
|
||||
|
||||
internal class FiniteBoundsViolationInJavaImpl(
|
||||
override val containingTypes: List<KtSymbol>,
|
||||
firDiagnostic: KtPsiDiagnostic,
|
||||
token: KtLifetimeToken,
|
||||
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.FiniteBoundsViolationInJava
|
||||
|
||||
internal class DeprecatedTypeParameterSyntaxImpl(
|
||||
firDiagnostic: KtPsiDiagnostic,
|
||||
token: KtLifetimeToken,
|
||||
|
||||
+5
@@ -753,6 +753,11 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
|
||||
|
||||
val CYCLIC_GENERIC_UPPER_BOUND by error<PsiElement>()
|
||||
|
||||
val FINITE_BOUNDS_VIOLATION by error<PsiElement>()
|
||||
val FINITE_BOUNDS_VIOLATION_IN_JAVA by error<PsiElement> {
|
||||
parameter<List<Symbol>>("containingTypes")
|
||||
}
|
||||
|
||||
val DEPRECATED_TYPE_PARAMETER_SYNTAX by error<KtDeclaration>(PositioningStrategy.TYPE_PARAMETERS_LIST)
|
||||
|
||||
val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning<KtTypeParameter>()
|
||||
|
||||
@@ -448,6 +448,8 @@ object FirErrors {
|
||||
val IMPLICIT_NOTHING_RETURN_TYPE by error0<PsiElement>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
|
||||
val IMPLICIT_NOTHING_PROPERTY_TYPE by error0<PsiElement>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
|
||||
val CYCLIC_GENERIC_UPPER_BOUND by error0<PsiElement>()
|
||||
val FINITE_BOUNDS_VIOLATION by error0<PsiElement>()
|
||||
val FINITE_BOUNDS_VIOLATION_IN_JAVA by error1<PsiElement, List<FirBasedSymbol<*>>>()
|
||||
val DEPRECATED_TYPE_PARAMETER_SYNTAX by error0<KtDeclaration>(SourceElementPositioningStrategies.TYPE_PARAMETERS_LIST)
|
||||
val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning0<KtTypeParameter>()
|
||||
val DYNAMIC_SUPERTYPE by error0<KtTypeReference>()
|
||||
|
||||
+2
@@ -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<FirConstructorChecker>
|
||||
|
||||
+110
@@ -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<ConeKotlinType, Set<ConeKotlinType>> {
|
||||
val edges = mutableMapOf<ConeKotlinType, MutableSet<ConeKotlinType>>()
|
||||
|
||||
val visitedSymbols = mutableSetOf<FirClassifierSymbol<*>>()
|
||||
fun visit(coneType: ConeKotlinType) {
|
||||
val constituentTypes = mutableSetOf<ConeKotlinType>()
|
||||
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<ConeKotlinType, Set<ConeKotlinType>>): Boolean {
|
||||
var containsCycle = false
|
||||
|
||||
val dfsNeighbors = DFS.Neighbors<ConeKotlinType> { edges[it] ?: emptyList() }
|
||||
|
||||
val dfsVisited = object : DFS.VisitedWithSet<ConeKotlinType>() {
|
||||
override fun checkAndMarkVisited(current: ConeKotlinType): Boolean {
|
||||
val added = super.checkAndMarkVisited(current)
|
||||
if (!added && current == start) {
|
||||
containsCycle = true
|
||||
}
|
||||
return added
|
||||
}
|
||||
}
|
||||
|
||||
val dfsHandler = object : DFS.AbstractNodeHandler<ConeKotlinType, Unit>() {
|
||||
override fun result() {}
|
||||
}
|
||||
|
||||
DFS.dfs(listOf(start), dfsNeighbors, dfsVisited, dfsHandler)
|
||||
|
||||
return containsCycle
|
||||
}
|
||||
}
|
||||
+5
@@ -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(
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
interface A0<T : A0<T>>
|
||||
interface A1<T : A1<*>>
|
||||
interface A2<T : A2<out T>>
|
||||
interface A3<T : A3<in T>>
|
||||
interface A4<T : A4<*>?>
|
||||
|
||||
interface B0<T : B1<*>>
|
||||
interface B1<T : B0<*>>
|
||||
|
||||
interface AA<T: AA<*>>
|
||||
interface BB<S : List<AA<*>>>
|
||||
|
||||
interface A<T: List<!WRONG_NUMBER_OF_TYPE_ARGUMENTS!><T, T, T><!>>
|
||||
|
||||
class X<Y>
|
||||
class D<T : X<in X<out X<T>>>>
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
interface A0<T : A0<T>>
|
||||
interface A1<<!FINITE_BOUNDS_VIOLATION!>T : A1<*><!>>
|
||||
interface A2<<!FINITE_BOUNDS_VIOLATION!>T : A2<out T><!>>
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
|
||||
|
||||
interface C0<T, S : C0<*, S>>
|
||||
|
||||
interface C1<T : C1<T, *>, S : C1<S, *>> // T -> S, S -> S
|
||||
interface C2<T : C2<T, *>, 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<T, U> where T : A<U, T>, U: A<T, A<in U, T>>
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
|
||||
|
||||
interface C0<T, S : C0<*, S>>
|
||||
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
// FILE: A.java
|
||||
public class A<T extends A> {}
|
||||
|
||||
// FILE: 1.kt
|
||||
class B<S: A<*>>
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// FILE: A.java
|
||||
public class A<T extends A> {}
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ class A<T : Inv<in T>> {
|
||||
fun foo(): T = null!!
|
||||
}
|
||||
|
||||
class Inv2<T : Inv2<in T>>(val x: T)
|
||||
class Inv2<<!FINITE_BOUNDS_VIOLATION!>T : Inv2<in T><!>>(val x: T)
|
||||
|
||||
fun main(a: A<*>, j: JavaClass<*>, i2: Inv2<*>) {
|
||||
// Probably it's too restrictive to suppose star projection type here as Any?,
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// KT-9633: SOE occurred before
|
||||
interface A<T : A<in T>>
|
||||
@@ -1,2 +1,3 @@
|
||||
// FIR_IDENTICAL
|
||||
// KT-9633: SOE occurred before
|
||||
interface A<<!FINITE_BOUNDS_VIOLATION!>T : A<in T><!>>
|
||||
Reference in New Issue
Block a user