[FIR] Implement warnings for java nullability type mismatch
#KT-56989
This commit is contained in:
committed by
Space Team
parent
1ecbc094ec
commit
a6fdeeb7df
+2
@@ -44,6 +44,8 @@ object FirJvmErrors {
|
||||
|
||||
// Types
|
||||
val JAVA_TYPE_MISMATCH by error2<KtExpression, ConeKotlinType, ConeKotlinType>()
|
||||
val RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS by warning2<PsiElement, ConeKotlinType, ConeKotlinType>()
|
||||
val NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS by warning2<PsiElement, ConeKotlinType, ConeKotlinType>()
|
||||
|
||||
// Type parameters
|
||||
val UPPER_BOUND_CANNOT_BE_ARRAY by error0<PsiElement>()
|
||||
|
||||
+17
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.fir.analysis.diagnostics.jvm
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
|
||||
import org.jetbrains.kotlin.diagnostics.KtDiagnosticRenderers.NOT_RENDERED
|
||||
import org.jetbrains.kotlin.diagnostics.KtDiagnosticRenderers.TO_STRING
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers.STRING
|
||||
@@ -52,6 +53,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.NON_DATA_C
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.NON_FINAL_JVM_RECORD
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.NON_SOURCE_REPEATED_ANNOTATION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.NO_REFLECTION_IN_CLASS_PATH
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.OVERLOADS_ABSTRACT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.OVERLOADS_ANNOTATION_CLASS_CONSTRUCTOR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.OVERLOADS_INTERFACE
|
||||
@@ -60,6 +62,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.OVERLOADS_
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.OVERLOADS_WITHOUT_DEFAULT_ARGUMENTS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.OVERRIDE_CANNOT_BE_STATIC
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.REDUNDANT_REPEATABLE_ANNOTATION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.REPEATABLE_ANNOTATION_HAS_NESTED_CLASS_NAMED_CONTAINER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.REPEATABLE_CONTAINER_HAS_NON_DEFAULT_PARAMETER
|
||||
@@ -82,6 +85,20 @@ object FirJvmErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
|
||||
|
||||
override val MAP = KtDiagnosticFactoryToRendererMap("FIR").also { map ->
|
||||
map.put(JAVA_TYPE_MISMATCH, "Java type mismatch: expected ''{0}'' but found ''{1}''. Use explicit cast.", RENDER_TYPE, RENDER_TYPE)
|
||||
|
||||
map.put(
|
||||
NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS,
|
||||
"Java type mismatch: inferred type is ''{1}'', but ''{0}'' was expected.",
|
||||
RENDER_TYPE,
|
||||
RENDER_TYPE
|
||||
)
|
||||
map.put(
|
||||
RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS,
|
||||
"Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type ''{0}''.",
|
||||
RENDER_TYPE,
|
||||
NOT_RENDERED
|
||||
)
|
||||
|
||||
map.put(UPPER_BOUND_CANNOT_BE_ARRAY, "Upper bound of type parameter cannot be an array.")
|
||||
map.put(STRICTFP_ON_CLASS, "'@Strictfp' annotation on classes is not yet supported.")
|
||||
map.put(SYNCHRONIZED_ON_ABSTRACT, "'@Synchronized' annotation cannot be used on abstract functions.")
|
||||
|
||||
+7
@@ -38,12 +38,14 @@ object JvmDeclarationCheckers : DeclarationCheckers() {
|
||||
get() = setOf(
|
||||
FirJvmFieldApplicabilityChecker,
|
||||
FirJvmSyntheticApplicabilityChecker,
|
||||
FirPropertyJavaNullabilityWarningChecker,
|
||||
)
|
||||
|
||||
override val functionCheckers: Set<FirFunctionChecker>
|
||||
get() = setOf(
|
||||
FirSynchronizedAnnotationChecker,
|
||||
FirOverloadsChecker,
|
||||
FirFunctionJavaNullabilityWarningChecker,
|
||||
)
|
||||
|
||||
override val typeParameterCheckers: Set<FirTypeParameterChecker>
|
||||
@@ -55,4 +57,9 @@ object JvmDeclarationCheckers : DeclarationCheckers() {
|
||||
get() = setOf(
|
||||
FirInlineBodySimpleFunctionChecker,
|
||||
)
|
||||
|
||||
override val valueParameterCheckers: Set<FirValueParameterChecker>
|
||||
get() = setOf(
|
||||
FirValueParameterJavaNullabilityWarningChecker
|
||||
)
|
||||
}
|
||||
|
||||
+26
@@ -19,6 +19,7 @@ object JvmExpressionCheckers : ExpressionCheckers() {
|
||||
get() = setOf(
|
||||
FirInterfaceDefaultMethodCallChecker,
|
||||
FirJavaSamInterfaceConstructorReferenceChecker,
|
||||
FirQualifiedAccessJavaNullabilityWarningChecker,
|
||||
)
|
||||
|
||||
override val callableReferenceAccessCheckers: Set<FirCallableReferenceAccessChecker>
|
||||
@@ -41,4 +42,29 @@ object JvmExpressionCheckers : ExpressionCheckers() {
|
||||
FirJvmPackageNameAnnotationsChecker,
|
||||
FirJvmSerializableLambdaChecker,
|
||||
)
|
||||
|
||||
override val loopExpressionCheckers: Set<FirLoopExpressionChecker>
|
||||
get() = setOf(
|
||||
FirLoopConditionJavaNullabilityWarningChecker,
|
||||
)
|
||||
|
||||
override val whenExpressionCheckers: Set<FirWhenExpressionChecker>
|
||||
get() = setOf(
|
||||
FirWhenConditionJavaNullabilityWarningChecker,
|
||||
)
|
||||
|
||||
override val logicExpressionCheckers: Set<FirLogicExpressionChecker>
|
||||
get() = setOf(
|
||||
FirLogicExpressionTypeJavaNullabilityWarningChecker,
|
||||
)
|
||||
|
||||
override val throwExpressionCheckers: Set<FirThrowExpressionChecker>
|
||||
get() = setOf(
|
||||
FirThrowJavaNullabilityWarningChecker,
|
||||
)
|
||||
|
||||
override val variableAssignmentCheckers: Set<FirVariableAssignmentChecker>
|
||||
get() = setOf(
|
||||
FirAssignmentJavaNullabilityWarningChecker,
|
||||
)
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.jvm.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFunctionChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirPropertyChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirValueParameterChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.hasExplicitReturnType
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.jvm.checkers.expression.checkExpressionForEnhancedTypeMismatch
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
||||
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
||||
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
|
||||
import org.jetbrains.kotlin.fir.expressions.FirReturnExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.impl.FirSingleExpressionBlock
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
|
||||
object FirPropertyJavaNullabilityWarningChecker : FirPropertyChecker() {
|
||||
override fun check(declaration: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (declaration.symbol.hasExplicitReturnType) {
|
||||
declaration.initializer?.checkExpressionForEnhancedTypeMismatch(
|
||||
declaration.returnTypeRef.coneType,
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object FirFunctionJavaNullabilityWarningChecker : FirFunctionChecker() {
|
||||
override fun check(declaration: FirFunction, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val body = declaration.body
|
||||
if (body is FirSingleExpressionBlock && declaration.symbol.hasExplicitReturnType) {
|
||||
(body.statement as? FirReturnExpression)?.result?.checkExpressionForEnhancedTypeMismatch(
|
||||
declaration.returnTypeRef.coneType,
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object FirValueParameterJavaNullabilityWarningChecker : FirValueParameterChecker() {
|
||||
override fun check(declaration: FirValueParameter, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
declaration.defaultValue?.checkExpressionForEnhancedTypeMismatch(
|
||||
declaration.returnTypeRef.coneType,
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS,
|
||||
)
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.jvm.checkers.expression
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory2
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition
|
||||
import org.jetbrains.kotlin.fir.java.enhancement.EnhancedForWarningConeSubstitutor
|
||||
import org.jetbrains.kotlin.fir.resolve.FirSamResolver
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import java.util.*
|
||||
|
||||
// TODO reimplement using AdditionalTypeChecker KT-62864
|
||||
object FirQualifiedAccessJavaNullabilityWarningChecker : FirQualifiedAccessExpressionChecker() {
|
||||
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val symbol = expression.toResolvedCallableSymbol() ?: return
|
||||
val substitutor = buildSubstitutor(expression, symbol, context.session)
|
||||
|
||||
expression.dispatchReceiver?.checkExpressionForEnhancedTypeMismatch(
|
||||
expectedType = symbol.dispatchReceiverType,
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
|
||||
)
|
||||
|
||||
val receiverType = symbol.receiverParameter?.typeRef?.coneType
|
||||
expression.extensionReceiver?.checkExpressionForEnhancedTypeMismatch(
|
||||
expectedType = receiverType?.let(substitutor::substituteOrSelf),
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
|
||||
)
|
||||
|
||||
for ((contextArgument, contextParameter) in expression.contextReceiverArguments.zip(symbol.resolvedContextReceivers)) {
|
||||
contextArgument.checkExpressionForEnhancedTypeMismatch(
|
||||
expectedType = substitutor.substituteOrSelf(contextParameter.typeRef.coneType),
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
|
||||
)
|
||||
}
|
||||
|
||||
if (expression is FirFunctionCall) {
|
||||
expression.resolvedArgumentMapping?.forEach { (argument, parameter) ->
|
||||
argument.checkExpressionForEnhancedTypeMismatch(
|
||||
expectedType = substitutor.substituteOrSelf(parameter.returnTypeRef.coneType),
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSubstitutor(
|
||||
expression: FirQualifiedAccessExpression,
|
||||
symbol: FirCallableSymbol<*>,
|
||||
session: FirSession,
|
||||
): ConeSubstitutor {
|
||||
if (expression.typeArguments.isEmpty()) return ConeSubstitutor.Empty
|
||||
|
||||
val substitutionMap = buildMap {
|
||||
for ((parameter, argument) in symbol.typeParameterSymbols.zip(expression.typeArguments)) {
|
||||
if (argument is FirTypeProjectionWithVariance) {
|
||||
put(parameter, argument.typeRef.coneType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ConeSubstitutorByMap(substitutionMap, session)
|
||||
}
|
||||
}
|
||||
|
||||
object FirThrowJavaNullabilityWarningChecker : FirThrowExpressionChecker() {
|
||||
override fun check(expression: FirThrowExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
expression.exception.checkExpressionForEnhancedTypeMismatch(
|
||||
expectedType = context.session.builtinTypes.throwableType.coneType,
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object FirAssignmentJavaNullabilityWarningChecker : FirVariableAssignmentChecker() {
|
||||
override fun check(expression: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
expression.rValue.checkExpressionForEnhancedTypeMismatch(
|
||||
expectedType = expression.lValue.resolvedType,
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object FirLogicExpressionTypeJavaNullabilityWarningChecker : FirLogicExpressionChecker() {
|
||||
override fun check(expression: FirBinaryLogicExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
expression.leftOperand.checkConditionForEnhancedTypeMismatch(context, reporter)
|
||||
expression.rightOperand.checkConditionForEnhancedTypeMismatch(context, reporter)
|
||||
}
|
||||
}
|
||||
|
||||
object FirLoopConditionJavaNullabilityWarningChecker : FirLoopExpressionChecker() {
|
||||
override fun check(expression: FirLoop, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (expression is FirErrorLoop) return
|
||||
val condition = expression.condition
|
||||
condition.checkConditionForEnhancedTypeMismatch(context, reporter)
|
||||
}
|
||||
}
|
||||
|
||||
object FirWhenConditionJavaNullabilityWarningChecker : FirWhenExpressionChecker() {
|
||||
override fun check(expression: FirWhenExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
for (branch in expression.branches) {
|
||||
val condition = branch.condition
|
||||
if (condition is FirElseIfTrueCondition) continue
|
||||
condition.checkConditionForEnhancedTypeMismatch(context, reporter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirExpression.checkConditionForEnhancedTypeMismatch(context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
checkExpressionForEnhancedTypeMismatch(
|
||||
context.session.builtinTypes.booleanType.type,
|
||||
reporter,
|
||||
context,
|
||||
FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS
|
||||
)
|
||||
}
|
||||
|
||||
internal fun FirExpression.checkExpressionForEnhancedTypeMismatch(
|
||||
expectedType: ConeKotlinType?,
|
||||
reporter: DiagnosticReporter,
|
||||
context: CheckerContext,
|
||||
factory: KtDiagnosticFactory2<ConeKotlinType, ConeKotlinType>,
|
||||
) {
|
||||
if (expectedType == null) return
|
||||
val actualType = resolvedType
|
||||
|
||||
val (actualTypeForComparison, expectedTypeForComparison) = getEnhancedTypesForComparison(actualType, expectedType, context)
|
||||
?: return
|
||||
|
||||
if (!actualTypeForComparison.isSubtypeOf(context.session.typeContext, expectedTypeForComparison)) {
|
||||
reporter.reportOn(source, factory, actualTypeForComparison, expectedTypeForComparison, context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEnhancedTypesForComparison(
|
||||
actualType: ConeKotlinType?,
|
||||
expectedType: ConeKotlinType?,
|
||||
context: CheckerContext,
|
||||
): Pair<ConeKotlinType, ConeKotlinType>? {
|
||||
if (actualType == null || expectedType == null) return null
|
||||
if (actualType is ConeErrorType || expectedType is ConeErrorType) return null
|
||||
|
||||
val substitutor = EnhancedForWarningConeSubstitutor(context.session.typeContext)
|
||||
|
||||
val enhancedActualType = substitutor.substituteOrNull(actualType)
|
||||
val enhancedExpectedType = substitutor.substituteOrNull(expectedType)
|
||||
|
||||
// No enhancement on either side, nothing to check.
|
||||
if (enhancedActualType == null && enhancedExpectedType == null) return null
|
||||
|
||||
val actualTypeForComparison = enhancedActualType ?: actualType
|
||||
val expectedTypeForComparison = enhancedExpectedType ?: expectedType
|
||||
|
||||
val expectedTypeAsFunctionTypeIfSam = if (
|
||||
actualTypeForComparison.isSomeFunctionType(context.session) &&
|
||||
!expectedTypeForComparison.isSomeFunctionType(context.session)
|
||||
) {
|
||||
// TODO remove after KT-62847
|
||||
val samResolver = FirSamResolver(context.session, context.scopeSession)
|
||||
samResolver.getFunctionTypeForPossibleSamType(expectedTypeForComparison) ?: expectedTypeForComparison
|
||||
} else {
|
||||
expectedTypeForComparison
|
||||
}
|
||||
|
||||
return actualTypeForComparison to expectedTypeAsFunctionTypeIfSam
|
||||
}
|
||||
Reference in New Issue
Block a user