FIR checker: report nullable expression as LHS of class literals
This commit is contained in:
committed by
Mikhail Glukhikh
parent
33c5b49632
commit
5ba5b63dee
+4
@@ -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<FirSourceElement, KtExpression>()
|
||||
val NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error<FirSourceElement, KtExpression>()
|
||||
val EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error<FirSourceElement, PsiElement> {
|
||||
parameter<ConeKotlinType>("lhsType")
|
||||
}
|
||||
}
|
||||
|
||||
val OVERRIDES by object : DiagnosticGroup("overrides") {
|
||||
|
||||
@@ -198,6 +198,7 @@ object FirErrors {
|
||||
val CALLABLE_REFERENCE_LHS_NOT_A_CLASS by error0<FirSourceElement, KtExpression>()
|
||||
val CLASS_LITERAL_LHS_NOT_A_CLASS by error0<FirSourceElement, KtExpression>()
|
||||
val NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error0<FirSourceElement, KtExpression>()
|
||||
val EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error1<FirSourceElement, PsiElement, ConeKotlinType>()
|
||||
|
||||
// overrides
|
||||
val NOTHING_TO_OVERRIDE by error1<FirSourceElement, KtModifierListOwner, FirMemberDeclaration>(SourceElementPositioningStrategies.OVERRIDE_MODIFIER)
|
||||
|
||||
+28
-6
@@ -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<T?>::class`, or `A<T?>?::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 -> {
|
||||
|
||||
+6
@@ -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")
|
||||
|
||||
Vendored
+3
-3
@@ -23,11 +23,11 @@ class Test {
|
||||
fun <T> List<T>.testCallable4(): () -> Unit = <!UNRESOLVED_REFERENCE!>b<T>?::foo<!>
|
||||
|
||||
fun <T> List<T>.testClassLiteral1() = a<T>::class
|
||||
fun <T> List<T>.testClassLiteral2() = b?::class
|
||||
fun <T> List<T>.testClassLiteral3() = b<T, Any>::class
|
||||
fun <T> List<T>.testClassLiteral2() = <!EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!>b<!>?::class
|
||||
fun <T> List<T>.testClassLiteral3() = <!EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!>b<T, Any><!>::class
|
||||
|
||||
fun <T> List<T>.testUnresolved1() = <!UNRESOLVED_REFERENCE!>unresolved<!><T>::foo
|
||||
fun <T> List<T>.testUnresolved2() = a<<!UNRESOLVED_REFERENCE!>unresolved<!>>::foo
|
||||
fun <T> List<T>.testUnresolved3() = a<<!SYNTAX!><!>>::foo
|
||||
fun <T> List<T>.testUnresolved4() = <!UNRESOLVED_REFERENCE!>unresolved<!>?::foo
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -6,10 +6,10 @@ public interface J {
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
fun f1(x: Int?): Any = x::class
|
||||
fun f1(x: Int?): Any = <!EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!>x<!>::class
|
||||
fun <T> f2(t: T): Any = t::class
|
||||
fun <S : String?> f3(s: S): Any = s::class
|
||||
fun <U : Any> f4(u: U?): Any = u::class
|
||||
fun f5(c: List<*>): Any = c[0]::class
|
||||
fun <U : Any> f4(u: U?): Any = <!EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!>u<!>::class
|
||||
fun f5(c: List<*>): Any = <!EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!>c[0]<!>::class
|
||||
|
||||
fun f6(j: J): Any = j.platformString()::class
|
||||
|
||||
@@ -10,11 +10,11 @@ val l2 = <!NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!>List?::class<!>
|
||||
|
||||
fun <T : Any> foo() {
|
||||
val t1 = <!TYPE_PARAMETER_IS_NOT_AN_EXPRESSION!>T<!>::class
|
||||
val t2 = <!TYPE_PARAMETER_IS_NOT_AN_EXPRESSION!>T<!>?::class
|
||||
val t2 = <!NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!><!TYPE_PARAMETER_IS_NOT_AN_EXPRESSION!>T<!>?::class<!>
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> bar() {
|
||||
val t3 = T?::class
|
||||
val t3 = <!NULLABLE_TYPE_IN_CLASS_LITERAL_LHS!>T?::class<!>
|
||||
}
|
||||
|
||||
val m = Map<String>::class
|
||||
|
||||
@@ -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<out Foo>? = if (f != null) f::class else null
|
||||
|
||||
fun test2(): KClass<out Foo>? {
|
||||
var f: Foo? = null
|
||||
if (f != null) {
|
||||
run { f = null }
|
||||
return f::class
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// KT-16291 Smart cast doesn't work when getting class of instance
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
+7
@@ -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),
|
||||
|
||||
+5
@@ -576,6 +576,11 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
|
||||
override val diagnosticClass get() = NullableTypeInClassLiteralLhs::class
|
||||
}
|
||||
|
||||
abstract class ExpressionOfNullableTypeInClassLiteralLhs : KtFirDiagnostic<PsiElement>() {
|
||||
override val diagnosticClass get() = ExpressionOfNullableTypeInClassLiteralLhs::class
|
||||
abstract val lhsType: KtType
|
||||
}
|
||||
|
||||
abstract class NothingToOverride : KtFirDiagnostic<KtModifierListOwner>() {
|
||||
override val diagnosticClass get() = NothingToOverride::class
|
||||
abstract val declaration: KtSymbol
|
||||
|
||||
+8
@@ -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<PsiElement> {
|
||||
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
|
||||
}
|
||||
|
||||
internal class NothingToOverrideImpl(
|
||||
override val declaration: KtSymbol,
|
||||
firDiagnostic: FirPsiDiagnostic<*>,
|
||||
|
||||
Reference in New Issue
Block a user