[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:
Brian Norman
2023-08-04 14:06:14 -05:00
committed by Space Team
parent 6a886e4c32
commit e8b4550173
17 changed files with 164 additions and 36 deletions
@@ -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,
@@ -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
}
@@ -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,
@@ -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>()
@@ -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>
@@ -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
}
}
@@ -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(
@@ -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,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><!>>
@@ -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,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
interface C0<T, S : C0<*, S>>
@@ -1,5 +0,0 @@
// FILE: A.java
public class A<T extends A> {}
// FILE: 1.kt
class B<S: A<*>>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: A.java
public class A<T extends A> {}
@@ -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><!>>