FIR checker: report nullable expression as LHS of class literals

This commit is contained in:
Jinseong Jeon
2021-03-04 16:38:05 -08:00
committed by Mikhail Glukhikh
parent 33c5b49632
commit 5ba5b63dee
12 changed files with 68 additions and 37 deletions
@@ -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)
@@ -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 -> {
@@ -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")
@@ -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
}
}
@@ -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
@@ -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),
@@ -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
@@ -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<*>,