[FIR] Add BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER diagnostic

This commit is contained in:
Andrey Zinovyev
2021-04-07 08:19:31 +00:00
committed by Space
parent b0f1ddc91e
commit 0f04fac5b2
10 changed files with 76 additions and 8 deletions
@@ -299,6 +299,8 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
} }
val UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE by error<FirSourceElement, PsiElement>() val UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE by error<FirSourceElement, PsiElement>()
val BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER by error<FirSourceElement, PsiElement>()
} }
val REFLECTION by object : DiagnosticGroup("Reflection") { val REFLECTION by object : DiagnosticGroup("Reflection") {
@@ -222,6 +222,7 @@ object FirErrors {
val TYPE_PARAMETER_AS_REIFIED by error1<FirSourceElement, PsiElement, FirTypeParameterSymbol>() val TYPE_PARAMETER_AS_REIFIED by error1<FirSourceElement, PsiElement, FirTypeParameterSymbol>()
val FINAL_UPPER_BOUND by warning1<FirSourceElement, PsiElement, ConeKotlinType>() val FINAL_UPPER_BOUND by warning1<FirSourceElement, PsiElement, ConeKotlinType>()
val UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE by error0<FirSourceElement, PsiElement>() val UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE by error0<FirSourceElement, PsiElement>()
val BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER by error0<FirSourceElement, PsiElement>()
// Reflection // Reflection
val EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED by error1<FirSourceElement, KtExpression, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) val EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED by error1<FirSourceElement, KtExpression, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
@@ -31,6 +31,8 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens 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.KtModifierList
import org.jetbrains.kotlin.psi.KtParameter.VAL_VAR_TOKEN_SET import org.jetbrains.kotlin.psi.KtParameter.VAL_VAR_TOKEN_SET
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType 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.types.model.TypeCheckerProviderContext
import org.jetbrains.kotlin.utils.addToStdlib.safeAs 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) = internal fun FirClass<*>.unsubstitutedScope(context: CheckerContext) =
this.unsubstitutedScope(context.sessionHolder.session, context.sessionHolder.scopeSession, withForcedTypeCalculator = false) this.unsubstitutedScope(context.sessionHolder.session, context.sessionHolder.scopeSession, withForcedTypeCalculator = false)
@@ -397,3 +401,5 @@ private fun lowerThanBound(context: ConeInferenceContext, argument: ConeKotlinTy
} }
return false return false
} }
fun FirMemberDeclaration.isInlineOnly(): Boolean = isInline && hasAnnotation(INLINE_ONLY_ANNOTATION_CLASS_ID)
@@ -5,14 +5,19 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration 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.canHaveSubtypes
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext 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.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.* 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.coneType
import org.jetbrains.kotlin.fir.types.isExtensionFunctionType import org.jetbrains.kotlin.fir.types.isExtensionFunctionType
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirTypeParameterBoundsChecker : FirTypeParameterChecker() { object FirTypeParameterBoundsChecker : FirTypeParameterChecker() {
@@ -30,5 +35,36 @@ object FirTypeParameterBoundsChecker : FirTypeParameterChecker() {
reporter.reportOn(bound.source, FirErrors.UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE, context) reporter.reportOn(bound.source, FirErrors.UPPER_BOUND_IS_EXTENSION_FUNCTION_TYPE, context)
} }
} }
if (containingDeclaration.safeAs<FirMemberDeclaration>()?.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
}
} }
@@ -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.ASSIGNED_VALUE_IS_NEVER_READ
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGN_OPERATOR_AMBIGUITY 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.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.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_LHS_NOT_A_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR 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(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 // Reflection
map.put( map.put(
EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED, EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED,
@@ -3,13 +3,13 @@ interface I2
open class C open class C
interface A1<K, V> where V : K, V : K interface A1<K, V> where V : K, V : K
interface A2<K, V, W> where W : K, W : V interface A2<K, V, W> where W : K, W : <!BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER!>V<!>
interface A3<K, V> where V : I1, V : K, V : I2 interface A3<K, <!BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER!>V<!>> where V : I1, V : K, V : I2
interface A4<K, V> where K : I1, K : I2, K : C, K : V, V : I2, V : I1 interface A4<<!BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER!>K<!>, V> where K : I1, K : I2, K : C, K : V, V : I2, V : I1
fun <K, V> f1() where V : K, V : K {} fun <K, V> f1() where V : K, V : K {}
fun <K, V, W> f2() where W : K, W : V { fun <K, V, W> f2() where W : K, W : <!BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER!>V<!> {
fun <T> f3() where T : K, T : V {} fun <T> f3() where T : K, T : <!BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER!>V<!> {}
fun <T> f4() where T : K, T : K {} fun <T> f4() where T : K, T : K {}
} }
fun <K, V, W> f3() where W : K, W : V, W : Any {} fun <K, V, <!BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER!>W<!>> f3() where W : K, W : V, W : Any {}
@@ -1,6 +1,6 @@
class Foo<T : Cloneable> where T : Comparable<T> { class Foo<T : Cloneable> where T : Comparable<T> {
fun <U : Cloneable> foo(u: U): U where U: Comparable<U> { fun <U : Cloneable> foo(u: U): U where U: Comparable<U> {
fun <T: Any> bar() where T: U {} fun <<!BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER!>T: Any<!>> bar() where T: U {}
return u return u
} }
@@ -10,4 +10,4 @@ class Foo<T : Cloneable> where T : Comparable<T> {
class Bar<T : Cloneable, U> where U: Comparable<T> { class Bar<T : Cloneable, U> where U: Comparable<T> {
} }
@@ -920,6 +920,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token, 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 -> add(FirErrors.EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED) { firDiagnostic ->
ExtensionInClassReferenceNotAllowedImpl( ExtensionInClassReferenceNotAllowedImpl(
firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.a as FirCallableDeclaration), firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.a as FirCallableDeclaration),
@@ -656,6 +656,10 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = UpperBoundIsExtensionFunctionType::class override val diagnosticClass get() = UpperBoundIsExtensionFunctionType::class
} }
abstract class BoundsNotAllowedIfBoundedByTypeParameter : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = BoundsNotAllowedIfBoundedByTypeParameter::class
}
abstract class ExtensionInClassReferenceNotAllowed : KtFirDiagnostic<KtExpression>() { abstract class ExtensionInClassReferenceNotAllowed : KtFirDiagnostic<KtExpression>() {
override val diagnosticClass get() = ExtensionInClassReferenceNotAllowed::class override val diagnosticClass get() = ExtensionInClassReferenceNotAllowed::class
abstract val referencedDeclaration: KtCallableSymbol abstract val referencedDeclaration: KtCallableSymbol
@@ -1056,6 +1056,13 @@ internal class UpperBoundIsExtensionFunctionTypeImpl(
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
} }
internal class BoundsNotAllowedIfBoundedByTypeParameterImpl(
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.BoundsNotAllowedIfBoundedByTypeParameter(), KtAbstractFirDiagnostic<PsiElement> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class ExtensionInClassReferenceNotAllowedImpl( internal class ExtensionInClassReferenceNotAllowedImpl(
override val referencedDeclaration: KtCallableSymbol, override val referencedDeclaration: KtCallableSymbol,
firDiagnostic: FirPsiDiagnostic<*>, firDiagnostic: FirPsiDiagnostic<*>,