[FIR] Add checker for EXPANSIVE_INHERITANCE

^KT-59402 Fixed
This commit is contained in:
Brian Norman
2023-10-20 15:10:28 -05:00
committed by Space Team
parent a991a13295
commit e9983a947f
19 changed files with 266 additions and 68 deletions
@@ -461,8 +461,7 @@ internal class KtSymbolByFirBuilder constructor(
// TODO this is a temporary hack to prevent FIR IDE from crashing on builder inference, see KT-50916
val typeVariable = coneType.constructor.variable as? ConeTypeParameterBasedTypeVariable
val typeParameterSymbol = typeVariable?.typeParameterSymbol ?: throwUnexpectedElementError(coneType)
val coneTypeParameterType = (typeParameterSymbol.toConeType() as ConeTypeParameterType)
.withNullability(coneType.nullability, rootSession.typeContext)
val coneTypeParameterType = typeParameterSymbol.toConeType(coneType.nullability.isNullable)
KtFirTypeParameterType(coneTypeParameterType, this@KtSymbolByFirBuilder)
}
@@ -2427,6 +2427,21 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.EXPANSIVE_INHERITANCE) { firDiagnostic ->
ExpansiveInheritanceImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.EXPANSIVE_INHERITANCE_IN_JAVA) { firDiagnostic ->
ExpansiveInheritanceInJavaImpl(
firDiagnostic.a.map { firBasedSymbol ->
firSymbolBuilder.buildSymbol(firBasedSymbol)
},
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.DEPRECATED_TYPE_PARAMETER_SYNTAX) { firDiagnostic ->
DeprecatedTypeParameterSyntaxImpl(
firDiagnostic as KtPsiDiagnostic,
@@ -1723,6 +1723,15 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
val containingTypes: List<KtSymbol>
}
interface ExpansiveInheritance : KtFirDiagnostic<PsiElement> {
override val diagnosticClass get() = ExpansiveInheritance::class
}
interface ExpansiveInheritanceInJava : KtFirDiagnostic<PsiElement> {
override val diagnosticClass get() = ExpansiveInheritanceInJava::class
val containingTypes: List<KtSymbol>
}
interface DeprecatedTypeParameterSyntax : KtFirDiagnostic<KtDeclaration> {
override val diagnosticClass get() = DeprecatedTypeParameterSyntax::class
}
@@ -2072,6 +2072,17 @@ internal class FiniteBoundsViolationInJavaImpl(
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.FiniteBoundsViolationInJava
internal class ExpansiveInheritanceImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.ExpansiveInheritance
internal class ExpansiveInheritanceInJavaImpl(
override val containingTypes: List<KtSymbol>,
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.ExpansiveInheritanceInJava
internal class DeprecatedTypeParameterSyntaxImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
@@ -792,7 +792,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> {
val FINITE_BOUNDS_VIOLATION_IN_JAVA by warning<PsiElement> {
parameter<List<Symbol>>("containingTypes")
}
val EXPANSIVE_INHERITANCE by error<PsiElement>()
val EXPANSIVE_INHERITANCE_IN_JAVA by warning<PsiElement> {
parameter<List<Symbol>>("containingTypes")
}
@@ -467,7 +467,9 @@ object FirErrors {
val ABBREVIATED_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 FINITE_BOUNDS_VIOLATION_IN_JAVA by warning1<PsiElement, List<FirBasedSymbol<*>>>()
val EXPANSIVE_INHERITANCE by error0<PsiElement>()
val EXPANSIVE_INHERITANCE_IN_JAVA by warning1<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>()
@@ -305,7 +305,7 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set<String> = setOf(
"IMPLICIT_NOTHING_PROPERTY_TYPE",
"CYCLIC_GENERIC_UPPER_BOUND",
"FINITE_BOUNDS_VIOLATION",
"FINITE_BOUNDS_VIOLATION_IN_JAVA",
"EXPANSIVE_INHERITANCE",
"DEPRECATED_TYPE_PARAMETER_SYNTAX",
"DYNAMIC_SUPERTYPE",
"DYNAMIC_UPPER_BOUND",
@@ -140,6 +140,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
FirDelegateFieldTypeMismatchChecker,
FirMultipleDefaultsInheritedFromSupertypesChecker,
FirFiniteBoundRestrictionChecker,
FirNonExpansiveInheritanceRestrictionChecker,
FirDefaultArgumentsInExpectActualizedByFakeOverrideChecker,
)
@@ -0,0 +1,175 @@
/*
* 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.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.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.SmartSet
/**
* @see org.jetbrains.kotlin.resolve.NonExpansiveInheritanceRestrictionChecker
*/
object FirNonExpansiveInheritanceRestrictionChecker : FirRegularClassChecker() {
override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.typeParameters.isEmpty()) return
val graph = buildTypeGraph(declaration, context.session)
val edgesInCycles = graph.expansiveEdges.filterTo(SmartSet.create()) { graph.isEdgeInCycle(it) }
if (edgesInCycles.isEmpty()) return
val problemNodes = edgesInCycles.flatMapTo(mutableSetOf()) { listOf(it.from, it.to) }
for (ref in declaration.typeParameters) {
if (problemNodes.remove(TypeParameterNode(declaration.symbol, ref.symbol))) {
reporter.reportOn(ref.source ?: declaration.source, FirErrors.EXPANSIVE_INHERITANCE, context)
return
}
}
if (problemNodes.any { it.typeParameter.source != null }) return
val containers = problemNodes.map { it.container }
reporter.reportOn(declaration.source, FirErrors.EXPANSIVE_INHERITANCE_IN_JAVA, containers, context)
}
private fun buildTypeGraph(
declaration: FirRegularClass,
session: FirSession,
): Graph<TypeParameterNode> {
// K1 treats type parameters inherited within inner classes from outer classes as being
// from different classes. However, in K2, the symbol for these type parameters are the
// same. So we have to store the pair of class symbol and type parameter symbol, so we do
// not report errors on inherited type parameters from outer classes.
val graph = Graph<TypeParameterNode>()
fun addEdges(
typeParameters: List<FirTypeParameterSymbol>,
constituentTypes: Set<ConeKotlinType>,
symbol: FirRegularClassSymbol,
constituentTypeSymbol: FirRegularClassSymbol,
constituentTypeParameterSymbol: FirTypeParameterSymbol,
expansive: Boolean,
) {
for (typeParameter in typeParameters) {
if (typeParameter.toConeType(isNullable = false) in constituentTypes ||
typeParameter.toConeType(isNullable = true) in constituentTypes
) {
graph.addEdge(
from = TypeParameterNode(symbol, typeParameter),
to = TypeParameterNode(constituentTypeSymbol, constituentTypeParameterSymbol),
expansive = expansive,
)
}
}
}
val visitedSymbols = SmartSet.create<FirClassifierSymbol<*>>()
fun visit(symbol: FirRegularClassSymbol) {
val typeParameters = symbol.typeParameterSymbols
// For each type parameter T, let ST be the set of all constituent types of all immediate supertypes of the owner of T.
// If T appears as a constituent type of a simple type argument A in a generic type in ST, add an edge from T
// to U, where U is the type parameter corresponding to A. The edge is non-expansive if A has the form T or T?,
// the edge is expansive otherwise.
for (constituentType in symbol.resolvedSuperTypes.flatMap { it.constituentTypes() }) {
val constituentTypeSymbol = constituentType.toRegularClassSymbol(session) ?: continue
if (visitedSymbols.add(constituentTypeSymbol)) visit(constituentTypeSymbol)
if (constituentTypeSymbol.typeParameterSymbols.size != constituentType.typeArguments.size) continue
for ((i, typeProjection) in constituentType.typeArguments.withIndex()) {
val constituentTypeParameterSymbol = constituentTypeSymbol.typeParameterSymbols[i]
if (typeProjection.kind == ProjectionKind.INVARIANT) {
val constituents = typeProjection.type!!.constituentTypes()
addEdges(
typeParameters = typeParameters,
constituentTypes = constituents,
symbol = symbol,
constituentTypeSymbol = constituentTypeSymbol,
constituentTypeParameterSymbol = constituentTypeParameterSymbol,
expansive = typeProjection.type!!.unwrapLowerBound() !is ConeTypeParameterType,
)
} else {
// Furthermore, if T appears as a constituent type of an element of the B-closure of the set of lower and
// upper bounds of a skolem type variable Q in a skolemization of a projected generic type in ST, add an
// expanding edge from T to V, where V is the type parameter corresponding to Q.
val bounds = SmartSet.create<ConeKotlinType>()
constituentTypeParameterSymbol.resolvedBounds.mapTo(bounds) { it.type }
typeProjection.type?.let(bounds::add)
val boundClosure = bounds.flatMapTo(SmartSet.create()) { it.collectUpperBounds() }
addEdges(
typeParameters = typeParameters,
constituentTypes = boundClosure.flatMapTo(SmartSet.create()) { it.constituentTypes() },
symbol = symbol,
constituentTypeSymbol = constituentTypeSymbol,
constituentTypeParameterSymbol = constituentTypeParameterSymbol,
expansive = true,
)
}
}
}
}
visitedSymbols.add(declaration.symbol)
visit(declaration.symbol)
return graph
}
private data class TypeParameterNode(
val container: FirRegularClassSymbol,
val typeParameter: FirTypeParameterSymbol,
)
private data class ExpansiveEdge<out T>(val from: T, val to: T)
private class Graph<T> {
val expansiveEdges = SmartSet.create<ExpansiveEdge<T>>()
private val edgeLists = mutableMapOf<T, MutableSet<T>>()
fun addEdge(from: T, to: T, expansive: Boolean = false) {
edgeLists.getOrPut(from) { SmartSet.create() }.add(to)
if (expansive) {
expansiveEdges.add(ExpansiveEdge(from, to))
}
}
fun isEdgeInCycle(edge: ExpansiveEdge<T>) = edge.from in collectReachable(edge.to)
private fun collectReachable(from: T): List<T> {
val handler = object : DFS.NodeHandlerWithListResult<T, T>() {
override fun afterChildren(current: T?) {
result.add(current)
}
}
val neighbors = DFS.Neighbors<T> { current -> edgeLists[current] ?: emptySet() }
DFS.dfs(listOf(from), neighbors, handler)
return handler.result()
}
}
}
private fun ConeKotlinType.constituentTypes(): Set<ConeKotlinType> {
val constituentTypes = SmartSet.create<ConeKotlinType>()
forEachType { constituentTypes.add(it) }
return constituentTypes
}
@@ -218,6 +218,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_FROM_JAVA_R
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_IN_CONTRACT_DESCRIPTION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ERROR_SUPPRESSION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPANDED_TYPE_CANNOT_BE_INHERITED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPANSIVE_INHERITANCE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPANSIVE_INHERITANCE_IN_JAVA
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_CLASS_CONSTRUCTOR_DELEGATION_CALL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_CLASS_CONSTRUCTOR_PROPERTY_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPECTED_CONDITION
@@ -1445,7 +1447,13 @@ 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(FINITE_BOUNDS_VIOLATION_IN_JAVA, "Violation of Finite Bound Restriction for {0}.", commaSeparated(DECLARATION_NAME))
map.put(EXPANSIVE_INHERITANCE, "This type parameter violates the Non-Expansive Inheritance Restriction.")
map.put(
EXPANSIVE_INHERITANCE_IN_JAVA,
"Violation of Non-Expansive Inheritance Restriction for {0}.",
commaSeparated(DECLARATION_NAME),
)
map.put(DEPRECATED_TYPE_PARAMETER_SYNTAX, "Type parameters must be placed before function name.")
@@ -53,6 +53,14 @@ private fun ConeKotlinType.contains(predicate: (ConeKotlinType) -> Boolean, visi
// ----------------------------------- Transformations -----------------------------------
fun ConeKotlinType.unwrapLowerBound(): ConeSimpleKotlinType {
return when(this) {
is ConeDefinitelyNotNullType -> original.unwrapLowerBound()
is ConeFlexibleType -> lowerBound.unwrapLowerBound()
is ConeSimpleKotlinType -> this
}
}
fun ConeKotlinType.upperBoundIfFlexible(): ConeSimpleKotlinType {
return when (this) {
is ConeSimpleKotlinType -> this
@@ -18,7 +18,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassifierSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeParameterType
import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled
@@ -91,6 +91,9 @@ class FirCompositeNestedClassifierScope(
}
}
fun FirTypeParameterRef.toConeType(): ConeKotlinType = symbol.toConeType()
fun FirTypeParameterRef.toConeType(): ConeTypeParameterType = symbol.toConeType()
fun FirTypeParameterSymbol.toConeType(): ConeKotlinType = ConeTypeParameterTypeImpl(ConeTypeParameterLookupTag(this), isNullable = false)
fun FirTypeParameterSymbol.toConeType(): ConeTypeParameterType = toConeType(false)
fun FirTypeParameterSymbol.toConeType(isNullable: Boolean): ConeTypeParameterType =
ConeTypeParameterTypeImpl(ConeTypeParameterLookupTag(this), isNullable)
@@ -1,14 +0,0 @@
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
// FILE: D.java
public interface D<W> {}
// FILE: Q.java
public interface Q<Z1, Z2> {}
// FILE: C.java
public interface C<X> extends D<P<X,X>> {}
// FILE: 1.kt
interface P<Y1, Y2> : Q<C<Y1>, C<D<Y2>>>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
// FILE: D.java
@@ -1,16 +0,0 @@
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
// FILE: D.java
public interface D<W> {}
// FILE: Q.java
public interface Q<Z1, Z2> {}
// FILE: C.java
public interface C<X> extends D<P<X,X>> {}
// FILE: P.java
public interface P<Y1, Y2> extends Q<C<Y1>, C<D<Y2>>> {}
// FILE: 1.kt
interface P1<YY1, YY2> : P<YY1, YY2>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
// FILE: D.java
@@ -1,28 +0,0 @@
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
interface A<T>
interface B<T> : A<A<*>>
interface N0<in T>
interface C0<X> : N0<N0<C0<C0<X>>>>
interface N1<in T>
interface C1<X> : N1<N1<C1<C1<X?>>>>
interface C2<T> : C1<C1<T>>
interface C<X> : D<P<X, X>>
interface P<Y1, Y2> : Q<C<Y1>, C<D<Y2>>>
interface Q<Z1, Z2>
interface D<W>
interface E0<T>
interface E1<T : E2>
interface E2 : E0<E1<out E2>>
interface F0<T>
interface F1<T : F2<*>, U : F2<*>>
interface F2<T> : F0<F1<out F2<*>, T>>
interface G0<T>
interface G1<T : U, U : G2<*>>
interface G2<T> : G0<G1<out G2<*>, T>>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UPPER_BOUND_VIOLATED
interface A<T>
@@ -25,4 +26,8 @@ interface F2<T> : F0<F1<out F2<*>, T>>
interface G0<T>
interface G1<T : U, U : G2<*>>
interface G2<T> : G0<G1<out G2<*>, T>>
interface G2<T> : G0<G1<out G2<*>, T>>
abstract class H0<X> {
inner abstract class H1<Y> : H0<H1<Y>>() // Outer(X) should not be reported as non-expansive
}
@@ -96,6 +96,20 @@ public interface G2</*0*/ T> : G0<G1<out G2<*>, T>> {
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public abstract class H0</*0*/ X> {
public constructor H0</*0*/ X>()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public abstract inner class H1</*0*/ Y> /*captured type parameters: /*1*/ X*/ : H0<H0<X>.H1<Y>> {
public constructor H1</*0*/ Y>()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
public interface N0</*0*/ in T> {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int