From fc8b75be80e7f579e6a1a81d736226cf52740eda Mon Sep 17 00:00:00 2001 From: Andrey Zinovyev Date: Wed, 7 Apr 2021 11:31:08 +0000 Subject: [PATCH] [FIR] Add ONLY_ONE_CLASS_BOUND_ALLOWED diagnostic --- .../diagnostics/FirDiagnosticsList.kt | 2 ++ .../fir/analysis/diagnostics/FirErrors.kt | 1 + .../FirTypeParameterBoundsChecker.kt | 22 +++++++++++++++++++ .../diagnostics/FirDefaultErrorMessages.kt | 3 +++ .../diagnostics/tests/MultipleBounds.fir.kt | 2 +- .../cannotHaveManyClassUpperBounds.fir.kt | 18 +++++++-------- .../diagnostics/KtFirDataClassConverters.kt | 6 +++++ .../api/fir/diagnostics/KtFirDiagnostics.kt | 4 ++++ .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 7 ++++++ idea/testData/checker/MultipleBounds.fir.kt | 2 +- 10 files changed, 56 insertions(+), 11 deletions(-) diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 8fdbb18ac6c..b4c7e1c91d1 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -301,6 +301,8 @@ object DIAGNOSTICS_LIST : DiagnosticList() { val UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE by error() val BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER by error() + + val ONLY_ONE_CLASS_BOUND_ALLOWED by error() } val REFLECTION by object : DiagnosticGroup("Reflection") { diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index f0987f6d17f..94d4c12323e 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -223,6 +223,7 @@ object FirErrors { val FINAL_UPPER_BOUND by warning1() val UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE by error0() val BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER by error0() + val ONLY_ONE_CLASS_BOUND_ALLOWED by error0() // Reflection val EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED by error1>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTypeParameterBoundsChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTypeParameterBoundsChecker.kt index 080f23af694..c9aa2c6c1cc 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTypeParameterBoundsChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTypeParameterBoundsChecker.kt @@ -6,9 +6,11 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.fir.analysis.checkers.canHaveSubtypes import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.isInlineOnly +import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClass import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn @@ -21,6 +23,12 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs object FirTypeParameterBoundsChecker : FirTypeParameterChecker() { + private val classKinds = setOf( + ClassKind.CLASS, + ClassKind.ENUM_CLASS, + ClassKind.OBJECT + ) + override fun check(declaration: FirTypeParameter, context: CheckerContext, reporter: DiagnosticReporter) { val containingDeclaration = context.containingDeclarations.lastOrNull() if (containingDeclaration is FirConstructor) return @@ -39,6 +47,8 @@ object FirTypeParameterBoundsChecker : FirTypeParameterChecker() { if (containingDeclaration.safeAs()?.isInlineOnly() != true) { checkOnlyOneTypeParameterBound(declaration, context, reporter) } + + checkOnlyOneClassBound(declaration, context, reporter) } private fun checkOnlyOneTypeParameterBound(declaration: FirTypeParameter, context: CheckerContext, reporter: DiagnosticReporter) { @@ -67,4 +77,16 @@ object FirTypeParameterBoundsChecker : FirTypeParameterChecker() { return source.treeStructure.getParent(source.lighterASTNode)?.tokenType == KtNodeTypes.TYPE_CONSTRAINT } + + private fun checkOnlyOneClassBound(declaration: FirTypeParameter, context: CheckerContext, reporter: DiagnosticReporter) { + val seenClasses = mutableSetOf() + val bounds = declaration.bounds.distinctBy { it.coneType } + bounds.forEach { bound -> + bound.coneType.toRegularClass(context.session)?.let { clazz -> + if (classKinds.contains(clazz.classKind) && seenClasses.add(clazz) && seenClasses.size > 1) { + reporter.reportOn(bound.source, FirErrors.ONLY_ONE_CLASS_BOUND_ALLOWED, context) + } + } + } + } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index f03787dca02..66e15f5e6f7 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -154,6 +154,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_THIS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_VALUE_FOR_PARAMETER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NULLABLE_TYPE_IN_CLASS_LITERAL_LHS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NULLABLE_TYPE_OF_ANNOTATION_MEMBER +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ONLY_ONE_CLASS_BOUND_ALLOWED import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.OTHER_ERROR import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.OVERLOAD_RESOLUTION_AMBIGUITY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.OVERRIDING_FINAL_MEMBER @@ -456,6 +457,8 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { "Type parameter cannot have any other bounds if it's bounded by another type parameter" ) + map.put(ONLY_ONE_CLASS_BOUND_ALLOWED,"Only one of the upper bounds can be a class") + // Reflection map.put( EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED, diff --git a/compiler/testData/diagnostics/tests/MultipleBounds.fir.kt b/compiler/testData/diagnostics/tests/MultipleBounds.fir.kt index 7b89a9b9f69..d0eb0603037 100644 --- a/compiler/testData/diagnostics/tests/MultipleBounds.fir.kt +++ b/compiler/testData/diagnostics/tests/MultipleBounds.fir.kt @@ -48,7 +48,7 @@ class BarFoo> class Buzz where T : Bar, T : nioho class XFoo> -class Y where T : Foo, T : Bar +class Y where T : Foo, T : Bar fun test2(t : T) where diff --git a/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.fir.kt b/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.fir.kt index e8bb451ab9f..520ebe9ab1d 100644 --- a/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.fir.kt +++ b/compiler/testData/diagnostics/tests/typeParameters/cannotHaveManyClassUpperBounds.fir.kt @@ -2,17 +2,17 @@ open class C1 open class C2 open class C3 : C2() -class A1 where T : C1, T : C2 -class A2 where T : C1, T : C2, T : C3 -class A3 where T : C2, T : C3 -class A4 where T : C3, T : C2 +class A1 where T : C1, T : C2 +class A2 where T : C1, T : C2, T : C3 +class A3 where T : C2, T : C3 +class A4 where T : C3, T : C2 -fun f1() where T : C1, T : C2, T : C3 {} -fun f2() where T : C2, T : C3 {} -fun f3() where T : C3, T : C2 {} +fun f1() where T : C1, T : C2, T : C3 {} +fun f2() where T : C2, T : C3 {} +fun f3() where T : C3, T : C2 {} enum class E1 -class A5 where T : C1, T : E1 +class A5 where T : C1, T : E1 object O1 -class A6 where T : O1, T : C2 +class A6 where T : O1, T : C2 diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index 629a59171d4..d52f35c5d24 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -926,6 +926,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.ONLY_ONE_CLASS_BOUND_ALLOWED) { firDiagnostic -> + OnlyOneClassBoundAllowedImpl( + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } add(FirErrors.EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED) { firDiagnostic -> ExtensionInClassReferenceNotAllowedImpl( firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.a as FirCallableDeclaration), diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 84e85a88ed1..842e03a35a6 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -660,6 +660,10 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = BoundsNotAllowedIfBoundedByTypeParameter::class } + abstract class OnlyOneClassBoundAllowed : KtFirDiagnostic() { + override val diagnosticClass get() = OnlyOneClassBoundAllowed::class + } + abstract class ExtensionInClassReferenceNotAllowed : KtFirDiagnostic() { override val diagnosticClass get() = ExtensionInClassReferenceNotAllowed::class abstract val referencedDeclaration: KtCallableSymbol diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index fe66a075dff..ef0328344d3 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -1063,6 +1063,13 @@ internal class BoundsNotAllowedIfBoundedByTypeParameterImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class OnlyOneClassBoundAllowedImpl( + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.OnlyOneClassBoundAllowed(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class ExtensionInClassReferenceNotAllowedImpl( override val referencedDeclaration: KtCallableSymbol, firDiagnostic: FirPsiDiagnostic<*>, diff --git a/idea/testData/checker/MultipleBounds.fir.kt b/idea/testData/checker/MultipleBounds.fir.kt index a9857333254..61869618ce5 100644 --- a/idea/testData/checker/MultipleBounds.fir.kt +++ b/idea/testData/checker/MultipleBounds.fir.kt @@ -42,7 +42,7 @@ class Bar class Buzz where T : Bar, T : nioho class X -class Y where T : Foo, T : Bar +class Y where T : Foo, T : Bar fun test2(t : T) where