FIR checker: report KClass with incorrect type argument

This commit is contained in:
Jinseong Jeon
2021-03-05 01:42:06 -08:00
committed by Mikhail Glukhikh
parent bc2228d434
commit 7fff4108b6
12 changed files with 138 additions and 4 deletions
@@ -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) =
@@ -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
}
@@ -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
@@ -70,6 +70,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
)
override val fileCheckers: Set<FirFileChecker> = setOf(
FirKClassWithIncorrectTypeArgumentChecker,
FirTopLevelFunctionsChecker,
FirTopLevelPropertiesChecker,
)
@@ -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
}
@@ -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),
@@ -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
@@ -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<*>,