[FIR] Add CYCLIC_GENERIC_UPPER_BOUND check
This commit is contained in:
committed by
TeamCityServer
parent
03215f4e0a
commit
2b8c22c08a
@@ -9,9 +9,9 @@ interface B {
|
||||
}
|
||||
|
||||
interface G<X> {
|
||||
val <X> boo: Double where X : A, X : B
|
||||
val <A> bal: Double where A : B
|
||||
val <Y> bas: Double where Y : B, <!NAME_IN_CONSTRAINT_IS_NOT_A_TYPE_PARAMETER!>X<!> : B
|
||||
val <<!TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER!>X<!>> boo: Double where X : A, X : B
|
||||
val <<!TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER!>A<!>> bal: Double where A : B
|
||||
val <<!TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER!>Y<!>> bas: Double where Y : B, <!NAME_IN_CONSTRAINT_IS_NOT_A_TYPE_PARAMETER!>X<!> : B
|
||||
}
|
||||
|
||||
class C() : A(), B
|
||||
@@ -66,4 +66,4 @@ val t1 = test2<A>(<!ARGUMENT_TYPE_MISMATCH!>A()<!>)
|
||||
val t2 = test2<B>(<!ARGUMENT_TYPE_MISMATCH!>C()<!>)
|
||||
val t3 = test2<C>(C())
|
||||
|
||||
val <T, B : T> x : Int = 0
|
||||
val <<!TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER!>T<!>, <!TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER!>B : T<!>> x : Int = 0
|
||||
|
||||
+2
@@ -330,6 +330,8 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
||||
parameter<ConeKotlinType>("expected")
|
||||
parameter<ConeKotlinType>("actual")
|
||||
}
|
||||
|
||||
val CYCLIC_GENERIC_UPPER_BOUND by error<FirSourceElement, PsiElement>()
|
||||
}
|
||||
|
||||
val REFLECTION by object : DiagnosticGroup("Reflection") {
|
||||
|
||||
@@ -240,6 +240,7 @@ object FirErrors {
|
||||
val TYPE_PARAMETERS_NOT_ALLOWED by error0<FirSourceElement, KtDeclaration>()
|
||||
val TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER by error0<FirSourceElement, KtTypeParameter>()
|
||||
val RETURN_TYPE_MISMATCH by error2<FirSourceElement, KtExpression, ConeKotlinType, ConeKotlinType>(SourceElementPositioningStrategies.WHOLE_ELEMENT)
|
||||
val CYCLIC_GENERIC_UPPER_BOUND by error0<FirSourceElement, PsiElement>()
|
||||
|
||||
// Reflection
|
||||
val EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED by error1<FirSourceElement, KtExpression, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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 kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeCyclicTypeBound
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
object FirCyclicTypeBoundsChecker : FirMemberDeclarationChecker() {
|
||||
|
||||
override fun check(declaration: FirMemberDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (declaration is FirConstructor || declaration is FirTypeAlias) return
|
||||
|
||||
val processed = mutableSetOf<Name>()
|
||||
val cycles = mutableSetOf<Name>()
|
||||
val graph = declaration.typeParameters.map { param ->
|
||||
param.symbol.name to param.symbol.fir.bounds.flatMap { extractTypeParamNames(it) }.toSet()
|
||||
}.toMap()
|
||||
|
||||
declaration.typeParameters.forEach { param ->
|
||||
if (!processed.contains(param.symbol.name)) {
|
||||
findCycles(
|
||||
persistentListOf(),
|
||||
param.symbol.name,
|
||||
processed,
|
||||
mutableSetOf(),
|
||||
cycles
|
||||
) { name -> graph.getOrDefault(name, emptySet()) }
|
||||
}
|
||||
}
|
||||
|
||||
if (cycles.isNotEmpty()) {
|
||||
declaration.typeParameters
|
||||
.filter { cycles.contains(it.symbol.name) }
|
||||
.forEach { param ->
|
||||
val targets = if (declaration is FirRegularClass) {
|
||||
param.symbol.fir.originalBounds().filter { cycles.contains(extractTypeParamName(it.coneType)) }
|
||||
.mapNotNull { it.source }
|
||||
} else {
|
||||
listOf(param.source)
|
||||
}
|
||||
targets.forEach {
|
||||
reporter.reportOn(it, FirErrors.CYCLIC_GENERIC_UPPER_BOUND, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirTypeParameter.originalBounds() = bounds.flatMap { it.unwrapBound() }
|
||||
|
||||
private fun FirTypeRef.unwrapBound(): List<FirTypeRef> =
|
||||
if (this is FirErrorTypeRef && diagnostic is ConeCyclicTypeBound) {
|
||||
(diagnostic as ConeCyclicTypeBound).bounds
|
||||
} else {
|
||||
listOf(this)
|
||||
}
|
||||
|
||||
|
||||
private fun extractTypeParamNames(ref: FirTypeRef): Set<Name> =
|
||||
ref.unwrapBound().mapNotNull { extractTypeParamName(it.coneType) }.toSet()
|
||||
|
||||
private fun extractTypeParamName(type: ConeKotlinType): Name? = type.safeAs<ConeTypeParameterType>()?.lookupTag?.name
|
||||
|
||||
private fun findCycles(
|
||||
path: PersistentList<Name>,
|
||||
node: Name,
|
||||
processed: MutableSet<Name>,
|
||||
visited: MutableSet<Name>,
|
||||
cycles: MutableSet<Name>,
|
||||
graph: (Name) -> Set<Name>
|
||||
) {
|
||||
processed.add(node)
|
||||
if (visited.add(node)) {
|
||||
val newPath = path.add(node)
|
||||
graph(node).forEach { nextNode ->
|
||||
findCycles(newPath, nextNode, processed, visited, cycles, graph)
|
||||
}
|
||||
} else {
|
||||
cycles.addAll(path.dropWhile { it != node })
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -70,6 +70,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONST_VAL_WITH_DE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONST_VAL_WITH_GETTER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONST_VAL_WITH_NON_CONST_INITIALIZER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CYCLIC_CONSTRUCTOR_DELEGATION_CALL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CYCLIC_GENERIC_UPPER_BOUND
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DATA_CLASS_NOT_PROPERTY_PARAMETER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DATA_CLASS_VARARG_PARAMETER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DATA_CLASS_WITHOUT_PARAMETERS
|
||||
@@ -509,6 +510,8 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
|
||||
map.put(RETURN_TYPE_MISMATCH, "Return type mismatch: expected {0}, actual {1}", RENDER_TYPE, RENDER_TYPE)
|
||||
|
||||
map.put(CYCLIC_GENERIC_UPPER_BOUND, "Type parameter has cyclic upper bounds")
|
||||
|
||||
// Reflection
|
||||
map.put(
|
||||
EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED,
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
||||
FirExposedVisibilityDeclarationChecker,
|
||||
FirSealedSupertypeChecker,
|
||||
FirTypeAliasChecker,
|
||||
FirCyclicTypeBoundsChecker,
|
||||
)
|
||||
|
||||
override val functionCheckers: Set<FirFunctionChecker> = setOf(
|
||||
|
||||
+6
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.resolve.diagnostics
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
|
||||
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
|
||||
import org.jetbrains.kotlin.fir.render
|
||||
@@ -15,6 +16,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
|
||||
@@ -114,6 +116,10 @@ class ConeTypeParameterInQualifiedAccess(val symbol: FirTypeParameterSymbol) : C
|
||||
override val reason: String get() = "Type parameter ${symbol.fir.name} in qualified access"
|
||||
}
|
||||
|
||||
class ConeCyclicTypeBound(val symbol: FirTypeParameterSymbol, val bounds: ImmutableList<FirTypeRef>) : ConeDiagnostic() {
|
||||
override val reason: String get() = "Type parameter ${symbol.fir.name} has cyclic bounds"
|
||||
}
|
||||
|
||||
private fun describeSymbol(symbol: AbstractFirBasedSymbol<*>): String {
|
||||
return when (symbol) {
|
||||
is FirClassLikeSymbol<*> -> symbol.classId.asString()
|
||||
|
||||
+7
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.resolve.transformers
|
||||
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
|
||||
@@ -12,9 +13,11 @@ import org.jetbrains.kotlin.fir.expressions.FirBlock
|
||||
import org.jetbrains.kotlin.fir.expressions.FirDelegatedConstructorCall
|
||||
import org.jetbrains.kotlin.fir.expressions.FirStatement
|
||||
import org.jetbrains.kotlin.fir.resolve.ScopeSession
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeCyclicTypeBound
|
||||
import org.jetbrains.kotlin.fir.scopes.FirScope
|
||||
import org.jetbrains.kotlin.fir.scopes.createImportingScopes
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
|
||||
import org.jetbrains.kotlin.fir.types.impl.FirImplicitBuiltinTypeRef
|
||||
|
||||
class FirTypeResolveProcessor(
|
||||
@@ -140,9 +143,11 @@ class FirTypeResolveTransformer(
|
||||
for (typeParameter in typeParametersOwner.typeParameters) {
|
||||
if (typeParameter !is FirTypeParameter) continue
|
||||
if (hasSupertypePathToParameter(typeParameter, typeParameter, mutableSetOf())) {
|
||||
// TODO: Report diagnostic somewhere
|
||||
val errorType = buildErrorTypeRef {
|
||||
diagnostic = ConeCyclicTypeBound(typeParameter.symbol, typeParameter.bounds.toImmutableList())
|
||||
}
|
||||
typeParameter.replaceBounds(
|
||||
listOf(session.builtinTypes.nullableAnyType)
|
||||
listOf(errorType)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
fun <T : F?, F : T?> foo1() {}
|
||||
|
||||
fun <T : F?, F : E, E : F?> foo2() {}
|
||||
|
||||
fun <T, F> foo3() where T : F?, F : T {}
|
||||
|
||||
fun <T, F, E> foo4() where T : F?, F : E, E : F? {}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
fun <<!CYCLIC_GENERIC_UPPER_BOUND!>T : F?<!>, <!CYCLIC_GENERIC_UPPER_BOUND!>F : T?<!>> foo1() {}
|
||||
|
||||
fun <T : F?, <!CYCLIC_GENERIC_UPPER_BOUND!>F : E<!>, <!CYCLIC_GENERIC_UPPER_BOUND!>E : F?<!>> foo2() {}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
class A1<T : F?, F : T?>
|
||||
|
||||
class A2<T : F?, F : E, E : F?>
|
||||
|
||||
class A3<T, F> where T : F?, F : T?
|
||||
|
||||
class A4<T, F, E> where T : F?, F : E, E : F
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
class A1<T : <!CYCLIC_GENERIC_UPPER_BOUND!>F?<!>, F : <!CYCLIC_GENERIC_UPPER_BOUND!>T?<!>>
|
||||
|
||||
class A2<T : F?, F : <!CYCLIC_GENERIC_UPPER_BOUND!>E<!>, E : <!CYCLIC_GENERIC_UPPER_BOUND!>F?<!>>
|
||||
|
||||
Vendored
+1
-1
@@ -7,7 +7,7 @@ interface Foo<F> {
|
||||
|
||||
fun <S> select(vararg args: S): S = TODO()
|
||||
|
||||
class Bar<B : B> : Foo<B> {
|
||||
class Bar<B : <!CYCLIC_GENERIC_UPPER_BOUND!>B<!>> : Foo<B> {
|
||||
val v = <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Any?")!>select(
|
||||
getSum(),
|
||||
42
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// !DIAGNOSTICS: -MUST_BE_INITIALIZED -TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER
|
||||
fun <T: T?> foo() {}
|
||||
val <T: T?> prop
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -MUST_BE_INITIALIZED -TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER
|
||||
fun <<!CYCLIC_GENERIC_UPPER_BOUND!>T: T?<!>> foo() {}
|
||||
val <<!CYCLIC_GENERIC_UPPER_BOUND!>T: T?<!>> prop
|
||||
-1
@@ -1 +0,0 @@
|
||||
class MyClass<T: T?>
|
||||
@@ -1 +1,2 @@
|
||||
// FIR_IDENTICAL
|
||||
class MyClass<T: <!CYCLIC_GENERIC_UPPER_BOUND!>T?<!>>
|
||||
-1
@@ -1 +0,0 @@
|
||||
class MyClass<T: T>
|
||||
+1
@@ -1 +1,2 @@
|
||||
// FIR_IDENTICAL
|
||||
class MyClass<T: <!CYCLIC_GENERIC_UPPER_BOUND!>T<!>>
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
// !DIAGNOSTICS: -MUST_BE_INITIALIZED_OR_BE_ABSTRACT -TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER
|
||||
class My {
|
||||
fun <T: T?> foo() {}
|
||||
val <T: T?> prop: T
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -MUST_BE_INITIALIZED_OR_BE_ABSTRACT -TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER
|
||||
class My {
|
||||
fun <<!CYCLIC_GENERIC_UPPER_BOUND!>T: T?<!>> foo() {}
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
// !DIAGNOSTICS: -MUST_BE_INITIALIZED -TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER
|
||||
fun <T: T> foo() {}
|
||||
val <T: T?> prop: T
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !DIAGNOSTICS: -MUST_BE_INITIALIZED -TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER
|
||||
fun <<!CYCLIC_GENERIC_UPPER_BOUND!>T: T<!>> foo() {}
|
||||
val <<!CYCLIC_GENERIC_UPPER_BOUND!>T: T?<!>> prop: T
|
||||
@@ -2,7 +2,7 @@
|
||||
// !LANGUAGE: +NewInference
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||
|
||||
class Base<T : T> : HashSet<T>() {
|
||||
class Base<T : <!CYCLIC_GENERIC_UPPER_BOUND!>T<!>> : HashSet<T>() {
|
||||
fun foo() {
|
||||
super.remove(<!ARGUMENT_TYPE_MISMATCH!>""<!>)
|
||||
}
|
||||
|
||||
+6
@@ -1018,6 +1018,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.CYCLIC_GENERIC_UPPER_BOUND) { firDiagnostic ->
|
||||
CyclicGenericUpperBoundImpl(
|
||||
firDiagnostic as FirPsiDiagnostic<*>,
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED) { firDiagnostic ->
|
||||
ExtensionInClassReferenceNotAllowedImpl(
|
||||
firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.a as FirCallableDeclaration),
|
||||
|
||||
+4
@@ -723,6 +723,10 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
abstract val actual: KtType
|
||||
}
|
||||
|
||||
abstract class CyclicGenericUpperBound : KtFirDiagnostic<KtTypeParameter>() {
|
||||
override val diagnosticClass get() = CyclicGenericUpperBound::class
|
||||
}
|
||||
|
||||
abstract class ExtensionInClassReferenceNotAllowed : KtFirDiagnostic<KtExpression>() {
|
||||
override val diagnosticClass get() = ExtensionInClassReferenceNotAllowed::class
|
||||
abstract val referencedDeclaration: KtCallableSymbol
|
||||
|
||||
+7
@@ -1168,6 +1168,13 @@ internal class ReturnTypeMismatchImpl(
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class CyclicGenericUpperBoundImpl(
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.CyclicGenericUpperBound(), KtAbstractFirDiagnostic<KtTypeParameter> {
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class ExtensionInClassReferenceNotAllowedImpl(
|
||||
override val referencedDeclaration: KtCallableSymbol,
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
|
||||
Reference in New Issue
Block a user