[FIR] Implement warnings for java nullability type mismatch

#KT-56989
This commit is contained in:
Kirill Rakhman
2023-09-20 15:34:48 +02:00
committed by Space Team
parent 1ecbc094ec
commit a6fdeeb7df
158 changed files with 741 additions and 2021 deletions
@@ -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>()
@@ -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.")
@@ -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
)
}
@@ -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,
)
}
@@ -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,
)
}
}
@@ -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
}