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,
)