FIR checker: report KClass with incorrect type argument
This commit is contained in:
committed by
Mikhail Glukhikh
parent
bc2228d434
commit
7fff4108b6
+4
-1
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.fir.PrivateForInline
|
||||
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClass
|
||||
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
|
||||
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
@@ -247,6 +246,10 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
||||
val GENERIC_THROWABLE_SUBCLASS by error<FirSourceElement, KtTypeParameterList>()
|
||||
val INNER_CLASS_OF_GENERIC_THROWABLE_SUBCLASS by error<FirSourceElement, KtClassOrObject>(PositioningStrategy.DECLARATION_NAME)
|
||||
|
||||
val KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE by error<FirSourceElement, KtNamedDeclaration>(PositioningStrategy.DECLARATION_NAME) {
|
||||
parameter<FirTypeParameterSymbol>("typeParameter")
|
||||
}
|
||||
|
||||
val TYPE_PARAMETER_AS_REIFIED by error<FirSourceElement, PsiElement> {
|
||||
parameter<FirTypeParameterSymbol>("typeParameter")
|
||||
}
|
||||
|
||||
@@ -193,6 +193,7 @@ object FirErrors {
|
||||
val TYPE_PARAMETER_IN_CATCH_CLAUSE by error0<FirSourceElement, PsiElement>()
|
||||
val GENERIC_THROWABLE_SUBCLASS by error0<FirSourceElement, KtTypeParameterList>()
|
||||
val INNER_CLASS_OF_GENERIC_THROWABLE_SUBCLASS by error0<FirSourceElement, KtClassOrObject>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE by error1<FirSourceElement, KtNamedDeclaration, FirTypeParameterSymbol>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val TYPE_PARAMETER_AS_REIFIED by error1<FirSourceElement, PsiElement, FirTypeParameterSymbol>()
|
||||
|
||||
// Reflection
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.psi.KtModifierList
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtParameter.VAL_VAR_TOKEN_SET
|
||||
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
/**
|
||||
@@ -319,6 +320,10 @@ fun Modality.toToken(): KtModifierKeywordToken = when (this) {
|
||||
val FirFunctionCall.isIterator
|
||||
get() = this.calleeReference.name.asString() == "<iterator>"
|
||||
|
||||
fun ConeKotlinType.isSubtypeOfAny(session: FirSession): Boolean {
|
||||
return AbstractTypeChecker.isSubtypeOf(session.typeContext, this.fullyExpandedType(session), session.builtinTypes.anyType.type)
|
||||
}
|
||||
|
||||
internal fun throwableClassLikeType(session: FirSession) = session.builtinTypes.throwableType.type
|
||||
|
||||
fun ConeKotlinType.isSubtypeOfThrowable(session: FirSession) =
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.fir.FirFakeSourceElementKind
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isSubtypeOfAny
|
||||
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.ConeTypeParameterInQualifiedAccess
|
||||
import org.jetbrains.kotlin.fir.resolve.inference.isKClassType
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.toConeType
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
|
||||
// FE1.0 [KClassWithIncorrectTypeArgumentChecker]
|
||||
object FirKClassWithIncorrectTypeArgumentChecker : FirFileChecker() {
|
||||
override fun check(declaration: FirFile, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
for (topLevelDeclaration in declaration.declarations) {
|
||||
if (topLevelDeclaration is FirCallableMemberDeclaration<*>) {
|
||||
checkTopLevelDeclaration(topLevelDeclaration, context, reporter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When a type parameter is used as a type argument for KClass, it shouldn't be nullable.
|
||||
// bad: fun <T> test1() = T::class
|
||||
// okay: fun <T: Any> test2() = T::class
|
||||
private fun checkTopLevelDeclaration(
|
||||
declaration: FirCallableMemberDeclaration<*>,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
// prevent duplicate reporting
|
||||
if (declaration is FirPropertyAccessor) return
|
||||
|
||||
val source = declaration.source ?: return
|
||||
if (source.kind is FirFakeSourceElementKind) return
|
||||
|
||||
val returnType = declaration.returnTypeRef.coneType
|
||||
if (!returnType.isKClassWithBadArgument(context.session)) return
|
||||
|
||||
val typeArgument = (returnType.typeArguments[0] as ConeKotlinTypeProjection).type
|
||||
typeArgument.typeParameterFromError?.let {
|
||||
reporter.reportOn(source, FirErrors.KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE, it.symbol, context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConeKotlinType.isKClassWithBadArgument(session: FirSession): Boolean {
|
||||
if (!this.isKClassType()) return false
|
||||
val argumentType = typeArguments.toList().singleOrNull()?.let {
|
||||
when (it) {
|
||||
is ConeStarProjection -> null
|
||||
is ConeKotlinTypeProjection -> it.type
|
||||
}
|
||||
} ?: return false
|
||||
argumentType.typeParameterFromError?.let { typeParameter ->
|
||||
return !typeParameter.toConeType().isSubtypeOfAny(session)
|
||||
}
|
||||
return argumentType is ConeKotlinErrorType || !argumentType.isSubtypeOfAny(session)
|
||||
}
|
||||
|
||||
private val ConeKotlinType.typeParameterFromError: FirTypeParameter?
|
||||
get() = ((this as? ConeKotlinErrorType)?.diagnostic as? ConeTypeParameterInQualifiedAccess)?.symbol?.fir
|
||||
|
||||
}
|
||||
+8
@@ -111,6 +111,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INSTANCE_ACCESS_B
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INTERFACE_WITH_SUPERCLASS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVALID_IF_AS_EXPRESSION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.LEAKED_IN_PLACE_LAMBDA
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.LOCAL_ANNOTATION_CLASS_ERROR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.LOCAL_INTERFACE_NOT_ALLOWED
|
||||
@@ -395,6 +396,13 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
map.put(REIFIED_TYPE_IN_CATCH_CLAUSE, "Reified type is forbidden for catch parameter")
|
||||
map.put(TYPE_PARAMETER_IN_CATCH_CLAUSE, "Type parameter is forbidden for catch parameter")
|
||||
|
||||
map.put(
|
||||
KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE,
|
||||
"Declaration has an inconsistent return type. " +
|
||||
"Please add upper bound Any for type parameter ''{0}'' or specify return type explicitly",
|
||||
SYMBOL
|
||||
)
|
||||
|
||||
map.put(TYPE_PARAMETER_AS_REIFIED, "Cannot use ''{0}'' as reified type parameter. Use a class instead", SYMBOL)
|
||||
|
||||
// Reflection
|
||||
|
||||
+1
@@ -70,6 +70,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
||||
)
|
||||
|
||||
override val fileCheckers: Set<FirFileChecker> = setOf(
|
||||
FirKClassWithIncorrectTypeArgumentChecker,
|
||||
FirTopLevelFunctionsChecker,
|
||||
FirTopLevelPropertiesChecker,
|
||||
)
|
||||
|
||||
+10
-3
@@ -1,10 +1,10 @@
|
||||
// !DIAGNOSTICS: -TYPE_PARAMETER_AS_REIFIED -TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER -UNUSED_VARIABLE -UNUSED_PARAMETER
|
||||
|
||||
fun <T> test1() = T::class
|
||||
fun <T> <!KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE!>test1<!>() = T::class
|
||||
fun <T : Any> test2() = T::class
|
||||
|
||||
val <T> test3 = T::class
|
||||
val <T> test4 get() = T::class
|
||||
val <T> <!KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE!>test3<!> = T::class
|
||||
val <T> <!KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE!>test4<!> get() = T::class
|
||||
|
||||
fun <T> test5() = listOf(T::class)
|
||||
|
||||
@@ -12,6 +12,8 @@ fun <T> test6(): kotlin.reflect.KClass<T> = T::class
|
||||
fun <T> test7(): kotlin.reflect.KClass<*> = T::class
|
||||
fun test8() = <!NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!>String?::class<!>
|
||||
|
||||
fun <T> test9() where T : Any?, T : Comparable<T> = T::class
|
||||
|
||||
fun <T> listOf(e: T): List<T> = null!!
|
||||
|
||||
fun <L> locals() {
|
||||
@@ -21,3 +23,8 @@ fun <L> locals() {
|
||||
val test3 = L::class
|
||||
fun test4() = L::class
|
||||
}
|
||||
|
||||
class Foo<T> {
|
||||
val p = T::class
|
||||
fun f() = T::class
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ fun <T> test6(): kotlin.reflect.KClass<<!UPPER_BOUND_VIOLATED!>T<!>> = T::class
|
||||
fun <T> test7(): kotlin.reflect.KClass<*> = T::class
|
||||
fun test8() = <!NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!>String?::class<!>
|
||||
|
||||
fun <T> test9() where T : Any?, T : Comparable<T> = T::class
|
||||
|
||||
fun <T> listOf(e: T): List<T> = null!!
|
||||
|
||||
fun <L> locals() {
|
||||
@@ -21,3 +23,8 @@ fun <L> locals() {
|
||||
val test3 = L::class
|
||||
fun test4() = L::class
|
||||
}
|
||||
|
||||
class Foo<T> {
|
||||
val p = T::class
|
||||
fun f() = T::class
|
||||
}
|
||||
|
||||
@@ -10,3 +10,14 @@ public fun </*0*/ T> test5(): kotlin.collections.List<kotlin.reflect.KClass<T>>
|
||||
public fun </*0*/ T> test6(): kotlin.reflect.KClass<T>
|
||||
public fun </*0*/ T> test7(): kotlin.reflect.KClass<*>
|
||||
public fun test8(): kotlin.reflect.KClass<kotlin.String?>
|
||||
public fun </*0*/ T> test9(): kotlin.reflect.KClass<T> where T : kotlin.Comparable<T>
|
||||
|
||||
public final class Foo</*0*/ T> {
|
||||
public constructor Foo</*0*/ T>()
|
||||
public final val p: kotlin.reflect.KClass<T>
|
||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||
public final fun f(): kotlin.reflect.KClass<T>
|
||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -797,6 +797,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE) { firDiagnostic ->
|
||||
KclassWithNullableTypeParameterInSignatureImpl(
|
||||
firSymbolBuilder.buildTypeParameterSymbol(firDiagnostic.a.fir as FirTypeParameter),
|
||||
firDiagnostic as FirPsiDiagnostic<*>,
|
||||
token,
|
||||
)
|
||||
}
|
||||
add(FirErrors.TYPE_PARAMETER_AS_REIFIED) { firDiagnostic ->
|
||||
TypeParameterAsReifiedImpl(
|
||||
firSymbolBuilder.buildTypeParameterSymbol(firDiagnostic.a.fir as FirTypeParameter),
|
||||
|
||||
+5
@@ -564,6 +564,11 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
override val diagnosticClass get() = InnerClassOfGenericThrowableSubclass::class
|
||||
}
|
||||
|
||||
abstract class KclassWithNullableTypeParameterInSignature : KtFirDiagnostic<KtNamedDeclaration>() {
|
||||
override val diagnosticClass get() = KclassWithNullableTypeParameterInSignature::class
|
||||
abstract val typeParameter: KtTypeParameterSymbol
|
||||
}
|
||||
|
||||
abstract class TypeParameterAsReified : KtFirDiagnostic<PsiElement>() {
|
||||
override val diagnosticClass get() = TypeParameterAsReified::class
|
||||
abstract val typeParameter: KtTypeParameterSymbol
|
||||
|
||||
+8
@@ -913,6 +913,14 @@ internal class InnerClassOfGenericThrowableSubclassImpl(
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class KclassWithNullableTypeParameterInSignatureImpl(
|
||||
override val typeParameter: KtTypeParameterSymbol,
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
override val token: ValidityToken,
|
||||
) : KtFirDiagnostic.KclassWithNullableTypeParameterInSignature(), KtAbstractFirDiagnostic<KtNamedDeclaration> {
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class TypeParameterAsReifiedImpl(
|
||||
override val typeParameter: KtTypeParameterSymbol,
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
|
||||
Reference in New Issue
Block a user