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 bc791990cb9..8fdbb18ac6c 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 @@ -299,6 +299,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 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 1db1e0f74c1..f0987f6d17f 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 @@ -222,6 +222,7 @@ object FirErrors { val TYPE_PARAMETER_AS_REIFIED by error1() 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() // 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/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt index 215b763fb06..8befed43d24 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt @@ -31,6 +31,8 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtModifierList import org.jetbrains.kotlin.psi.KtParameter.VAL_VAR_TOKEN_SET import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType @@ -40,6 +42,8 @@ import org.jetbrains.kotlin.types.model.KotlinTypeMarker import org.jetbrains.kotlin.types.model.TypeCheckerProviderContext import org.jetbrains.kotlin.utils.addToStdlib.safeAs +private val INLINE_ONLY_ANNOTATION_CLASS_ID = ClassId.topLevel(FqName("kotlin.internal.InlineOnly")) + internal fun FirClass<*>.unsubstitutedScope(context: CheckerContext) = this.unsubstitutedScope(context.sessionHolder.session, context.sessionHolder.scopeSession, withForcedTypeCalculator = false) @@ -397,3 +401,5 @@ private fun lowerThanBound(context: ConeInferenceContext, argument: ConeKotlinTy } return false } + +fun FirMemberDeclaration.isInlineOnly(): Boolean = isInline && hasAnnotation(INLINE_ONLY_ANNOTATION_CLASS_ID) 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 5379b2de325..080f23af694 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 @@ -5,14 +5,19 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration +import org.jetbrains.kotlin.KtNodeTypes 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.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.types.ConeTypeParameterType +import org.jetbrains.kotlin.fir.types.FirTypeRef import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.isExtensionFunctionType +import org.jetbrains.kotlin.utils.addToStdlib.safeAs object FirTypeParameterBoundsChecker : FirTypeParameterChecker() { @@ -30,5 +35,36 @@ object FirTypeParameterBoundsChecker : FirTypeParameterChecker() { reporter.reportOn(bound.source, FirErrors.UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE, context) } } + + if (containingDeclaration.safeAs()?.isInlineOnly() != true) { + checkOnlyOneTypeParameterBound(declaration, context, reporter) + } } + + private fun checkOnlyOneTypeParameterBound(declaration: FirTypeParameter, context: CheckerContext, reporter: DiagnosticReporter) { + val bounds = declaration.bounds.distinctBy { it.coneType } + val (boundWithParam, otherBounds) = bounds.partition { it.coneType is ConeTypeParameterType } + if (boundWithParam.size > 1 || (boundWithParam.size == 1 && otherBounds.isNotEmpty())) { + // If there's only one problematic bound (either 2 type parameter bounds, or 1 type parameter bound + 1 other bound), + // report the diagnostic on that bound + + //take TypeConstraint bounds only to report on the same point as old FE + val constraintBounds = bounds.filter { it.isInTypeConstraint() }.toSet() + val reportOn = + if (bounds.size == 2) { + val boundDecl = otherBounds.firstOrNull() ?: boundWithParam.last() + if (constraintBounds.contains(boundDecl)) boundDecl.source + else declaration.source + } else { + declaration.source + } + reporter.reportOn(reportOn, FirErrors.BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER, context) + } + } + + private fun FirTypeRef.isInTypeConstraint(): Boolean { + val source = source ?: return false + return source.treeStructure.getParent(source.lighterASTNode)?.tokenType == KtNodeTypes.TYPE_CONSTRAINT + } + } 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 8754a6331b6..f03787dca02 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 @@ -42,6 +42,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ARRAY_EQUALITY_OP import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGNED_VALUE_IS_NEVER_READ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGN_OPERATOR_AMBIGUITY import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BACKING_FIELD_IN_INTERFACE +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BREAK_OR_CONTINUE_OUTSIDE_A_LOOP import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CALLABLE_REFERENCE_LHS_NOT_A_CLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR @@ -450,6 +451,11 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { ) map.put(UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE, "Extension function type can not be used as an upper bound") + map.put( + BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER, + "Type parameter cannot have any other bounds if it's bounded by another type parameter" + ) + // Reflection map.put( EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED, diff --git a/compiler/testData/diagnostics/tests/generics/TypeParametersInTypeParameterBounds.fir.kt b/compiler/testData/diagnostics/tests/generics/TypeParametersInTypeParameterBounds.fir.kt index 327e1d0d831..5dbe80868f8 100644 --- a/compiler/testData/diagnostics/tests/generics/TypeParametersInTypeParameterBounds.fir.kt +++ b/compiler/testData/diagnostics/tests/generics/TypeParametersInTypeParameterBounds.fir.kt @@ -3,13 +3,13 @@ interface I2 open class C interface A1 where V : K, V : K -interface A2 where W : K, W : V -interface A3 where V : I1, V : K, V : I2 -interface A4 where K : I1, K : I2, K : C, K : V, V : I2, V : I1 +interface A2 where W : K, W : V +interface A3V> where V : I1, V : K, V : I2 +interface A4<K, V> where K : I1, K : I2, K : C, K : V, V : I2, V : I1 fun f1() where V : K, V : K {} -fun f2() where W : K, W : V { - fun f3() where T : K, T : V {} +fun f2() where W : K, W : V { + fun f3() where T : K, T : V {} fun f4() where T : K, T : K {} } -fun f3() where W : K, W : V, W : Any {} +fun W> f3() where W : K, W : V, W : Any {} diff --git a/compiler/testData/diagnostics/tests/typeParameters/misplacedConstraints.fir.kt b/compiler/testData/diagnostics/tests/typeParameters/misplacedConstraints.fir.kt index 8e88088a0cc..9df4235784b 100644 --- a/compiler/testData/diagnostics/tests/typeParameters/misplacedConstraints.fir.kt +++ b/compiler/testData/diagnostics/tests/typeParameters/misplacedConstraints.fir.kt @@ -1,6 +1,6 @@ class Foo where T : Comparable { fun foo(u: U): U where U: Comparable { - fun bar() where T: U {} + fun <T: Any> bar() where T: U {} return u } @@ -10,4 +10,4 @@ class Foo where T : Comparable { class Bar where U: Comparable { -} \ No newline at end of file +} 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 0c04e662f8a..629a59171d4 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 @@ -920,6 +920,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER) { firDiagnostic -> + BoundsNotAllowedIfBoundedByTypeParameterImpl( + 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 61fb10dd4be..84e85a88ed1 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 @@ -656,6 +656,10 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = UpperBoundIsExtensionFunctionType::class } + abstract class BoundsNotAllowedIfBoundedByTypeParameter : KtFirDiagnostic() { + override val diagnosticClass get() = BoundsNotAllowedIfBoundedByTypeParameter::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 ffbaf969545..fe66a075dff 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 @@ -1056,6 +1056,13 @@ internal class UpperBoundIsExtensionFunctionTypeImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class BoundsNotAllowedIfBoundedByTypeParameterImpl( + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.BoundsNotAllowedIfBoundedByTypeParameter(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class ExtensionInClassReferenceNotAllowedImpl( override val referencedDeclaration: KtCallableSymbol, firDiagnostic: FirPsiDiagnostic<*>,