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 3222510871e..b43fcd8fa05 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 @@ -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() val INNER_CLASS_OF_GENERIC_THROWABLE_SUBCLASS by error(PositioningStrategy.DECLARATION_NAME) + val KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE by error(PositioningStrategy.DECLARATION_NAME) { + parameter("typeParameter") + } + val TYPE_PARAMETER_AS_REIFIED by error { parameter("typeParameter") } 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 155106e13f4..95822c4da12 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 @@ -193,6 +193,7 @@ object FirErrors { val TYPE_PARAMETER_IN_CATCH_CLAUSE by error0() val GENERIC_THROWABLE_SUBCLASS by error0() val INNER_CLASS_OF_GENERIC_THROWABLE_SUBCLASS by error0(SourceElementPositioningStrategies.DECLARATION_NAME) + val KCLASS_WITH_NULLABLE_TYPE_PARAMETER_IN_SIGNATURE by error1(SourceElementPositioningStrategies.DECLARATION_NAME) val TYPE_PARAMETER_AS_REIFIED by error1() // Reflection 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 fb8b6a1a972..aadb6f898d8 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 @@ -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() == "" +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) = diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirKClassWithIncorrectTypeArgumentChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirKClassWithIncorrectTypeArgumentChecker.kt new file mode 100644 index 00000000000..c18e9434629 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirKClassWithIncorrectTypeArgumentChecker.kt @@ -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 test1() = T::class + // okay: fun 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 + +} 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 11402f0ce34..4e4ffed95d8 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 @@ -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 diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonDeclarationCheckers.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonDeclarationCheckers.kt index 580629752c7..8ebc705f4f2 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonDeclarationCheckers.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/checkers/CommonDeclarationCheckers.kt @@ -70,6 +70,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() { ) override val fileCheckers: Set = setOf( + FirKClassWithIncorrectTypeArgumentChecker, FirTopLevelFunctionsChecker, FirTopLevelPropertiesChecker, ) diff --git a/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.fir.kt b/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.fir.kt index 8f8337f8b74..77dc7958183 100644 --- a/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.fir.kt +++ b/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.fir.kt @@ -1,10 +1,10 @@ // !DIAGNOSTICS: -TYPE_PARAMETER_AS_REIFIED -TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER -UNUSED_VARIABLE -UNUSED_PARAMETER -fun test1() = T::class +fun test1() = T::class fun test2() = T::class -val test3 = T::class -val test4 get() = T::class +val test3 = T::class +val test4 get() = T::class fun test5() = listOf(T::class) @@ -12,6 +12,8 @@ fun test6(): kotlin.reflect.KClass = T::class fun test7(): kotlin.reflect.KClass<*> = T::class fun test8() = String?::class +fun test9() where T : Any?, T : Comparable = T::class + fun listOf(e: T): List = null!! fun locals() { @@ -21,3 +23,8 @@ fun locals() { val test3 = L::class fun test4() = L::class } + +class Foo { + val p = T::class + fun f() = T::class +} diff --git a/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.kt b/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.kt index d94409063a6..20c1a573561 100644 --- a/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.kt +++ b/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.kt @@ -12,6 +12,8 @@ fun test6(): kotlin.reflect.KClass<T> = T::class fun test7(): kotlin.reflect.KClass<*> = T::class fun test8() = String?::class +fun test9() where T : Any?, T : Comparable = T::class + fun listOf(e: T): List = null!! fun locals() { @@ -21,3 +23,8 @@ fun locals() { val test3 = L::class fun test4() = L::class } + +class Foo { + val p = T::class + fun f() = T::class +} diff --git a/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.txt b/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.txt index 59e5eb5d553..ccefa98daaa 100644 --- a/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.txt +++ b/compiler/testData/diagnostics/tests/declarationChecks/kClassInSignature.txt @@ -10,3 +10,14 @@ public fun test5(): kotlin.collections.List> public fun test6(): kotlin.reflect.KClass public fun test7(): kotlin.reflect.KClass<*> public fun test8(): kotlin.reflect.KClass +public fun test9(): kotlin.reflect.KClass where T : kotlin.Comparable + +public final class Foo { + public constructor Foo() + public final val p: kotlin.reflect.KClass + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun f(): kotlin.reflect.KClass + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + 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 5a02d7cf4c0..d36c7bb1c9b 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 @@ -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), 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 2bc4ba84cdb..0aa468e0625 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 @@ -564,6 +564,11 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = InnerClassOfGenericThrowableSubclass::class } + abstract class KclassWithNullableTypeParameterInSignature : KtFirDiagnostic() { + override val diagnosticClass get() = KclassWithNullableTypeParameterInSignature::class + abstract val typeParameter: KtTypeParameterSymbol + } + abstract class TypeParameterAsReified : KtFirDiagnostic() { override val diagnosticClass get() = TypeParameterAsReified::class abstract val typeParameter: KtTypeParameterSymbol 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 89b750c0d8d..ad898d5d55f 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 @@ -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 { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class TypeParameterAsReifiedImpl( override val typeParameter: KtTypeParameterSymbol, firDiagnostic: FirPsiDiagnostic<*>,