[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,
|
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 ->
|
add(FirErrors.DEPRECATED_TYPE_PARAMETER_SYNTAX) { firDiagnostic ->
|
||||||
DeprecatedTypeParameterSyntaxImpl(
|
DeprecatedTypeParameterSyntaxImpl(
|
||||||
firDiagnostic as KtPsiDiagnostic,
|
firDiagnostic as KtPsiDiagnostic,
|
||||||
|
|||||||
+9
@@ -1644,6 +1644,15 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
|
|||||||
override val diagnosticClass get() = CyclicGenericUpperBound::class
|
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> {
|
interface DeprecatedTypeParameterSyntax : KtFirDiagnostic<KtDeclaration> {
|
||||||
override val diagnosticClass get() = DeprecatedTypeParameterSyntax::class
|
override val diagnosticClass get() = DeprecatedTypeParameterSyntax::class
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -1977,6 +1977,17 @@ internal class CyclicGenericUpperBoundImpl(
|
|||||||
token: KtLifetimeToken,
|
token: KtLifetimeToken,
|
||||||
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.CyclicGenericUpperBound
|
) : 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(
|
internal class DeprecatedTypeParameterSyntaxImpl(
|
||||||
firDiagnostic: KtPsiDiagnostic,
|
firDiagnostic: KtPsiDiagnostic,
|
||||||
token: KtLifetimeToken,
|
token: KtLifetimeToken,
|
||||||
|
|||||||
+5
@@ -753,6 +753,11 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
|
|||||||
|
|
||||||
val CYCLIC_GENERIC_UPPER_BOUND by error<PsiElement>()
|
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 DEPRECATED_TYPE_PARAMETER_SYNTAX by error<KtDeclaration>(PositioningStrategy.TYPE_PARAMETERS_LIST)
|
||||||
|
|
||||||
val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning<KtTypeParameter>()
|
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_RETURN_TYPE by error0<PsiElement>(SourceElementPositioningStrategies.NAME_IDENTIFIER)
|
||||||
val IMPLICIT_NOTHING_PROPERTY_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 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 DEPRECATED_TYPE_PARAMETER_SYNTAX by error0<KtDeclaration>(SourceElementPositioningStrategies.TYPE_PARAMETERS_LIST)
|
||||||
val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning0<KtTypeParameter>()
|
val MISPLACED_TYPE_PARAMETER_CONSTRAINTS by warning0<KtTypeParameter>()
|
||||||
val DYNAMIC_SUPERTYPE by error0<KtTypeReference>()
|
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.cfa.FirControlFlowChecker
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
|
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirAnonymousFunctionParametersChecker
|
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.*
|
import org.jetbrains.kotlin.fir.analysis.checkers.syntax.*
|
||||||
|
|
||||||
object CommonDeclarationCheckers : DeclarationCheckers() {
|
object CommonDeclarationCheckers : DeclarationCheckers() {
|
||||||
@@ -129,6 +130,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
|||||||
FirPropertyInitializationChecker,
|
FirPropertyInitializationChecker,
|
||||||
FirDelegateFieldTypeMismatchChecker,
|
FirDelegateFieldTypeMismatchChecker,
|
||||||
FirMultipleDefaultsInheritedFromSupertypesChecker,
|
FirMultipleDefaultsInheritedFromSupertypesChecker,
|
||||||
|
FirFiniteBoundRestrictionChecker,
|
||||||
)
|
)
|
||||||
|
|
||||||
override val constructorCheckers: Set<FirConstructorChecker>
|
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.EXTENSION_PROPERTY_WITH_BACKING_FIELD
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FINAL_SUPERTYPE
|
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.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.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_BINARY_MOD
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_IDENTITY_EQUALS
|
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(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(DEPRECATED_TYPE_PARAMETER_SYNTAX, "Type parameters must be placed before the name of the function")
|
||||||
|
|
||||||
map.put(
|
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 A0<T : A0<T>>
|
||||||
interface A1<<!FINITE_BOUNDS_VIOLATION!>T : A1<*><!>>
|
interface A1<<!FINITE_BOUNDS_VIOLATION!>T : A1<*><!>>
|
||||||
interface A2<<!FINITE_BOUNDS_VIOLATION!>T : A2<out T><!>>
|
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
|
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
|
||||||
|
|
||||||
interface C0<T, S : C0<*, S>>
|
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
|
// FILE: A.java
|
||||||
public class A<T extends A> {}
|
public class A<T extends A> {}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ class A<T : Inv<in T>> {
|
|||||||
fun foo(): T = null!!
|
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<*>) {
|
fun main(a: A<*>, j: JavaClass<*>, i2: Inv2<*>) {
|
||||||
// Probably it's too restrictive to suppose star projection type here as Any?,
|
// 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
|
// KT-9633: SOE occurred before
|
||||||
interface A<<!FINITE_BOUNDS_VIOLATION!>T : A<in T><!>>
|
interface A<<!FINITE_BOUNDS_VIOLATION!>T : A<in T><!>>
|
||||||
Reference in New Issue
Block a user