[FIR] Add CYCLIC_GENERIC_UPPER_BOUND check

This commit is contained in:
Andrey Zinovyev
2021-04-12 18:53:09 +03:00
committed by TeamCityServer
parent 03215f4e0a
commit 2b8c22c08a
27 changed files with 144 additions and 35 deletions
@@ -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
@@ -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)
@@ -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 })
}
}
}
@@ -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,
@@ -26,6 +26,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
FirExposedVisibilityDeclarationChecker,
FirSealedSupertypeChecker,
FirTypeAliasChecker,
FirCyclicTypeBoundsChecker,
)
override val functionCheckers: Set<FirFunctionChecker> = setOf(
@@ -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()
@@ -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?<!>>
@@ -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 +0,0 @@
class MyClass<T: T?>
@@ -1 +1,2 @@
// FIR_IDENTICAL
class MyClass<T: <!CYCLIC_GENERIC_UPPER_BOUND!>T?<!>>
@@ -1 +0,0 @@
class MyClass<T: T>
@@ -1 +1,2 @@
// FIR_IDENTICAL
class MyClass<T: <!CYCLIC_GENERIC_UPPER_BOUND!>T<!>>
@@ -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() {}
@@ -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!>""<!>)
}
@@ -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),
@@ -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
@@ -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<*>,