From 5ba5b63deef3877c79e808f36f3d872bd5c210ef Mon Sep 17 00:00:00 2001 From: Jinseong Jeon Date: Thu, 4 Mar 2021 16:38:05 -0800 Subject: [PATCH] FIR checker: report nullable expression as LHS of class literals --- .../diagnostics/FirDiagnosticsList.kt | 4 +++ .../fir/analysis/diagnostics/FirErrors.kt | 1 + .../expression/FirGetClassCallChecker.kt | 34 +++++++++++++++---- .../diagnostics/FirDefaultErrorMessages.kt | 6 ++++ .../bound/reservedExpressionSyntax.fir.kt | 6 ++-- .../expressionWithNullableType.fir.kt | 6 ++-- .../tests/classLiteral/nonClassesOnLHS.fir.kt | 4 +-- .../tests/classLiteral/smartCast.fir.kt | 23 ------------- .../tests/classLiteral/smartCast.kt | 1 + .../diagnostics/KtFirDataClassConverters.kt | 7 ++++ .../api/fir/diagnostics/KtFirDiagnostics.kt | 5 +++ .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 8 +++++ 12 files changed, 68 insertions(+), 37 deletions(-) delete mode 100644 compiler/testData/diagnostics/tests/classLiteral/smartCast.fir.kt 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 9dc3675f375..65a866d5d9b 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 @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.types.KotlinType @Suppress("UNUSED_VARIABLE", "LocalVariableName", "ClassName", "unused") @@ -253,6 +254,9 @@ object DIAGNOSTICS_LIST : DiagnosticList() { val CLASS_LITERAL_LHS_NOT_A_CLASS by error() val NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error() + val EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error { + parameter("lhsType") + } } val OVERRIDES by object : DiagnosticGroup("overrides") { 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 7176661165f..a1b0a6ca2f9 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 @@ -198,6 +198,7 @@ object FirErrors { val CALLABLE_REFERENCE_LHS_NOT_A_CLASS by error0() val CLASS_LITERAL_LHS_NOT_A_CLASS by error0() val NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error0() + val EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error1() // overrides val NOTHING_TO_OVERRIDE by error1(SourceElementPositioningStrategies.OVERRIDE_MODIFIER) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirGetClassCallChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirGetClassCallChecker.kt index a328e52f99c..1520463a7e4 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirGetClassCallChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirGetClassCallChecker.kt @@ -13,9 +13,9 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn import org.jetbrains.kotlin.fir.analysis.getChild import org.jetbrains.kotlin.fir.declarations.FirTypeParameter import org.jetbrains.kotlin.fir.declarations.FirTypeParameterRefsOwner -import org.jetbrains.kotlin.fir.expressions.FirGetClassCall -import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier -import org.jetbrains.kotlin.fir.expressions.FirStatement +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.lexer.KtTokens.QUEST @@ -25,7 +25,6 @@ object FirGetClassCallChecker : FirBasicExpressionChecker() { val source = expression.source ?: return if (source.kind is FirFakeSourceElementKind) return - val argument = expression.argument as? FirResolvedQualifier ?: return // Note that raw FIR drops marked nullability "?" in, e.g., `A?::class`, `A::class`, or `A?::class`. // That is, AST structures for those expressions have token type QUEST, whereas FIR element doesn't have any information about it. // @@ -36,10 +35,25 @@ object FirGetClassCallChecker : FirBasicExpressionChecker() { // // Only the 2nd example is valid, and we want to check if token type QUEST doesn't exist at the same level as COLONCOLON. val markedNullable = source.getChild(QUEST, depth = 1) != null - if (argument.isNullableLHSForCallableReference || markedNullable) { - reporter.reportOn(source, FirErrors.NULLABLE_TYPE_IN_CLASS_LITERAL_LHS, context) + val argument = expression.argument + val isNullable = markedNullable || + (argument as? FirResolvedQualifier)?.isNullableLHSForCallableReference == true || + argument.typeRef.coneType.isMarkedNullable + if (isNullable) { + if (argument.canBeDoubleColonLHSAsType) { + reporter.reportOn(source, FirErrors.NULLABLE_TYPE_IN_CLASS_LITERAL_LHS, context) + } else { + reporter.reportOn( + argument.source, + FirErrors.EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS, + argument.typeRef.coneType, + context + ) + } return } + + if (argument !is FirResolvedQualifier) return // TODO: differentiate RESERVED_SYNTAX_IN_CALLABLE_REFERENCE_LHS if (argument.typeArguments.isNotEmpty() && !argument.typeRef.coneType.isAllowedInClassLiteral(context)) { val typeParameters = (argument.symbol?.fir as? FirTypeParameterRefsOwner)?.typeParameters @@ -53,6 +67,14 @@ object FirGetClassCallChecker : FirBasicExpressionChecker() { } } + private val FirExpression.canBeDoubleColonLHSAsType: Boolean + get() { + return this is FirResolvedQualifier || + this is FirResolvedReifiedParameterReference || + ((this is FirQualifiedAccessExpression) && + (this.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol is FirTypeParameterSymbol) + } + private fun ConeKotlinType.isAllowedInClassLiteral(context: CheckerContext): Boolean = when (this) { is ConeClassLikeType -> { 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 603bb256d17..4d68c77f69b 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 @@ -82,6 +82,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_SUPER_CLA import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_SUPER_INTERFACE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_TYPEALIAS_EXPANDED_TYPE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_TYPE_PARAMETER_BOUND +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_PROPERTY_WITH_BACKING_FIELD import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_VARARG_PARAMETER_TYPE @@ -398,6 +399,11 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension { map.put(CLASS_LITERAL_LHS_NOT_A_CLASS, "Only classes are allowed on the left hand side of a class literal") map.put(NULLABLE_TYPE_IN_CLASS_LITERAL_LHS, "Type in a class literal must not be nullable") + map.put( + EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS, + "Expression in a class literal has a nullable type ''{0}'', use !! to make the type non-nullable", + RENDER_TYPE + ) // Inline and value classes map.put(INLINE_CLASS_NOT_TOP_LEVEL, "Inline classes cannot be local or inner") diff --git a/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax.fir.kt b/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax.fir.kt index e12b96fe1fc..c20100f6b22 100644 --- a/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax.fir.kt +++ b/compiler/testData/diagnostics/tests/callableReference/bound/reservedExpressionSyntax.fir.kt @@ -23,11 +23,11 @@ class Test { fun List.testCallable4(): () -> Unit = b?::foo fun List.testClassLiteral1() = a::class - fun List.testClassLiteral2() = b?::class - fun List.testClassLiteral3() = b::class + fun List.testClassLiteral2() = b?::class + fun List.testClassLiteral3() = b::class fun List.testUnresolved1() = unresolved::foo fun List.testUnresolved2() = a<unresolved>::foo fun List.testUnresolved3() = a<>::foo fun List.testUnresolved4() = unresolved?::foo -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/classLiteral/expressionWithNullableType.fir.kt b/compiler/testData/diagnostics/tests/classLiteral/expressionWithNullableType.fir.kt index 03b9ca0cab7..8aa29459a59 100644 --- a/compiler/testData/diagnostics/tests/classLiteral/expressionWithNullableType.fir.kt +++ b/compiler/testData/diagnostics/tests/classLiteral/expressionWithNullableType.fir.kt @@ -6,10 +6,10 @@ public interface J { // FILE: test.kt -fun f1(x: Int?): Any = x::class +fun f1(x: Int?): Any = x::class fun f2(t: T): Any = t::class fun f3(s: S): Any = s::class -fun f4(u: U?): Any = u::class -fun f5(c: List<*>): Any = c[0]::class +fun f4(u: U?): Any = u::class +fun f5(c: List<*>): Any = c[0]::class fun f6(j: J): Any = j.platformString()::class diff --git a/compiler/testData/diagnostics/tests/classLiteral/nonClassesOnLHS.fir.kt b/compiler/testData/diagnostics/tests/classLiteral/nonClassesOnLHS.fir.kt index de0e5ae1eb9..f3f2fa1aed4 100644 --- a/compiler/testData/diagnostics/tests/classLiteral/nonClassesOnLHS.fir.kt +++ b/compiler/testData/diagnostics/tests/classLiteral/nonClassesOnLHS.fir.kt @@ -10,11 +10,11 @@ val l2 = List?::class fun foo() { val t1 = T::class - val t2 = T?::class + val t2 = T?::class } inline fun bar() { - val t3 = T?::class + val t3 = T?::class } val m = Map::class diff --git a/compiler/testData/diagnostics/tests/classLiteral/smartCast.fir.kt b/compiler/testData/diagnostics/tests/classLiteral/smartCast.fir.kt deleted file mode 100644 index c398d443048..00000000000 --- a/compiler/testData/diagnostics/tests/classLiteral/smartCast.fir.kt +++ /dev/null @@ -1,23 +0,0 @@ -// KT-16291 Smart cast doesn't work when getting class of instance - -import kotlin.reflect.KClass - -class Foo { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other === null || other::class != this::class) return false - - return true - } -} - -fun test(f: Foo?): KClass? = if (f != null) f::class else null - -fun test2(): KClass? { - var f: Foo? = null - if (f != null) { - run { f = null } - return f::class - } - return null -} diff --git a/compiler/testData/diagnostics/tests/classLiteral/smartCast.kt b/compiler/testData/diagnostics/tests/classLiteral/smartCast.kt index 0bae9c28f6d..dd50fd47ea5 100644 --- a/compiler/testData/diagnostics/tests/classLiteral/smartCast.kt +++ b/compiler/testData/diagnostics/tests/classLiteral/smartCast.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // KT-16291 Smart cast doesn't work when getting class of instance import kotlin.reflect.KClass 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 9ba5e884745..1df7c00ddb1 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 @@ -815,6 +815,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS) { firDiagnostic -> + ExpressionOfNullableTypeInClassLiteralLhsImpl( + firSymbolBuilder.buildKtType(firDiagnostic.a), + firDiagnostic as FirPsiDiagnostic<*>, + token, + ) + } add(FirErrors.NOTHING_TO_OVERRIDE) { firDiagnostic -> NothingToOverrideImpl( firSymbolBuilder.buildSymbol(firDiagnostic.a as FirDeclaration), 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 2982aac2ee0..e781743719d 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 @@ -576,6 +576,11 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = NullableTypeInClassLiteralLhs::class } + abstract class ExpressionOfNullableTypeInClassLiteralLhs : KtFirDiagnostic() { + override val diagnosticClass get() = ExpressionOfNullableTypeInClassLiteralLhs::class + abstract val lhsType: KtType + } + abstract class NothingToOverride : KtFirDiagnostic() { override val diagnosticClass get() = NothingToOverride::class abstract val declaration: KtSymbol 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 ddaaa1c0ccd..22c9f401f10 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 @@ -934,6 +934,14 @@ internal class NullableTypeInClassLiteralLhsImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class ExpressionOfNullableTypeInClassLiteralLhsImpl( + override val lhsType: KtType, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.ExpressionOfNullableTypeInClassLiteralLhs(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class NothingToOverrideImpl( override val declaration: KtSymbol, firDiagnostic: FirPsiDiagnostic<*>,