[FIR] Implement warnings for java nullability upper bound violation

This commit is contained in:
Kirill Rakhman
2023-09-21 10:29:45 +02:00
committed by Space Team
parent 2df1e9dde6
commit a63ec9efdc
18 changed files with 150 additions and 26 deletions
@@ -5063,6 +5063,22 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirJvmErrors.UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS) { firDiagnostic ->
UpperBoundViolatedBasedOnJavaAnnotationsImpl(
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b),
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJvmErrors.UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS) { firDiagnostic ->
UpperBoundViolatedInTypealiasExpansionBasedOnJavaAnnotationsImpl(
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b),
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirJvmErrors.STRICTFP_ON_CLASS) { firDiagnostic ->
StrictfpOnClassImpl(
firDiagnostic as KtPsiDiagnostic,
@@ -3523,6 +3523,18 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = UpperBoundCannotBeArray::class
}
interface UpperBoundViolatedBasedOnJavaAnnotations : KtFirDiagnostic<PsiElement> {
override val diagnosticClass get() = UpperBoundViolatedBasedOnJavaAnnotations::class
val expectedUpperBound: KtType
val actualUpperBound: KtType
}
interface UpperBoundViolatedInTypealiasExpansionBasedOnJavaAnnotations : KtFirDiagnostic<PsiElement> {
override val diagnosticClass get() = UpperBoundViolatedInTypealiasExpansionBasedOnJavaAnnotations::class
val expectedUpperBound: KtType
val actualUpperBound: KtType
}
interface StrictfpOnClass : KtFirDiagnostic<KtAnnotationEntry> {
override val diagnosticClass get() = StrictfpOnClass::class
}
@@ -4250,6 +4250,20 @@ internal class UpperBoundCannotBeArrayImpl(
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.UpperBoundCannotBeArray
internal class UpperBoundViolatedBasedOnJavaAnnotationsImpl(
override val expectedUpperBound: KtType,
override val actualUpperBound: KtType,
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.UpperBoundViolatedBasedOnJavaAnnotations
internal class UpperBoundViolatedInTypealiasExpansionBasedOnJavaAnnotationsImpl(
override val expectedUpperBound: KtType,
override val actualUpperBound: KtType,
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.UpperBoundViolatedInTypealiasExpansionBasedOnJavaAnnotations
internal class StrictfpOnClassImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
@@ -64,6 +64,14 @@ object JVM_DIAGNOSTICS_LIST : DiagnosticList("FirJvmErrors") {
val TYPE_PARAMETERS by object : DiagnosticGroup("Type parameters") {
val UPPER_BOUND_CANNOT_BE_ARRAY by error<PsiElement>()
val UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS by warning<PsiElement> {
parameter<ConeKotlinType>("expectedUpperBound")
parameter<ConeKotlinType>("actualUpperBound")
}
val UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS by warning<PsiElement> {
parameter<ConeKotlinType>("expectedUpperBound")
parameter<ConeKotlinType>("actualUpperBound")
}
}
val ANNOTATIONS by object : DiagnosticGroup("annotations") {
@@ -50,6 +50,8 @@ object FirJvmErrors {
// Type parameters
val UPPER_BOUND_CANNOT_BE_ARRAY by error0<PsiElement>()
val UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS by warning2<PsiElement, ConeKotlinType, ConeKotlinType>()
val UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS by warning2<PsiElement, ConeKotlinType, ConeKotlinType>()
// annotations
val STRICTFP_ON_CLASS by error0<KtAnnotationEntry>()
@@ -80,6 +80,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.SYNCHRONIZ
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.SYNCHRONIZED_ON_INLINE
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.SYNCHRONIZED_ON_SUSPEND
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.UPPER_BOUND_CANNOT_BE_ARRAY
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.VALUE_CLASS_WITHOUT_JVM_INLINE_ANNOTATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.WRONG_NULLABILITY_FOR_JAVA_OVERRIDE
@@ -108,6 +110,19 @@ object FirJvmErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
SYMBOL,
)
map.put(
UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS,
"Type argument is not within its bounds: should be subtype of ''{0}''.",
RENDER_TYPE,
RENDER_TYPE
)
map.put(
UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS,
"Type argument is not within its bounds: should be subtype of ''{0}''.",
RENDER_TYPE,
RENDER_TYPE
)
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.")
@@ -0,0 +1,29 @@
/*
* 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
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory2
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.FirPlatformUpperBoundsProvider
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors
import org.jetbrains.kotlin.fir.java.enhancement.EnhancedForWarningConeSubstitutor
import org.jetbrains.kotlin.fir.java.enhancement.enhancedTypeForWarning
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.typeContext
class FirJavaNullabilityWarningUpperBoundsProvider(session: FirSession) : FirPlatformUpperBoundsProvider {
private val substitutor: EnhancedForWarningConeSubstitutor = EnhancedForWarningConeSubstitutor(session.typeContext)
override val diagnostic: KtDiagnosticFactory2<ConeKotlinType, ConeKotlinType>
get() = FirJvmErrors.UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS
override val diagnosticForTypeAlias: KtDiagnosticFactory2<ConeKotlinType, ConeKotlinType>
get() = FirJvmErrors.UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION_BASED_ON_JAVA_ANNOTATIONS
override fun getAdditionalUpperBound(coneKotlinType: ConeKotlinType): ConeKotlinType? {
return substitutor.substituteOrNull(coneKotlinType)
}
}
@@ -7,8 +7,10 @@ package org.jetbrains.kotlin.fir.analysis.checkers
import org.jetbrains.kotlin.KtSourceElement
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.FirSessionComponent
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
@@ -179,6 +181,7 @@ fun checkUpperBoundViolated(
) {
val count = minOf(typeParameters.size, typeArguments.size)
val typeSystemContext = context.session.typeContext
val additionalUpperBoundsProvider = context.session.platformUpperBoundsProvider
for (index in 0 until count) {
val argument = typeArguments[index]
@@ -204,6 +207,22 @@ fun checkUpperBoundViolated(
else -> FirErrors.UPPER_BOUND_VIOLATED
}
reporter.reportOn(argumentSource, factory, upperBound, argumentType.type, context)
} else {
// Only check if the original check was successful to prevent duplicate diagnostics
val additionalUpperBound = additionalUpperBoundsProvider?.getAdditionalUpperBound(upperBound)
if (additionalUpperBound != null && !AbstractTypeChecker.isSubtypeOf(
typeSystemContext,
argumentType,
additionalUpperBound,
stubTypesEqualToAnything = true
)
) {
val factory = when {
isReportExpansionError && argumentTypeRef == null -> additionalUpperBoundsProvider.diagnosticForTypeAlias
else -> additionalUpperBoundsProvider.diagnostic
}
reporter.reportOn(argumentSource, factory, upperBound, argumentType.type, context)
}
}
}
@@ -262,3 +281,12 @@ fun ConeTypeProjection.withSource(source: FirTypeRefSource?): ConeTypeProjection
}
}
}
interface FirPlatformUpperBoundsProvider : FirSessionComponent {
val diagnostic: KtDiagnosticFactory2<ConeKotlinType, ConeKotlinType>
val diagnosticForTypeAlias: KtDiagnosticFactory2<ConeKotlinType, ConeKotlinType>
fun getAdditionalUpperBound(coneKotlinType: ConeKotlinType): ConeKotlinType?
}
val FirSession.platformUpperBoundsProvider: FirPlatformUpperBoundsProvider? by FirSession.nullableSessionComponentAccessor()
@@ -12,10 +12,12 @@ import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.analysis.CheckersComponent
import org.jetbrains.kotlin.fir.analysis.FirOverridesBackwardCompatibilityHelper
import org.jetbrains.kotlin.fir.analysis.checkers.FirInlineCheckerPlatformSpecificComponent
import org.jetbrains.kotlin.fir.analysis.checkers.FirPlatformUpperBoundsProvider
import org.jetbrains.kotlin.fir.analysis.checkers.FirPrimaryConstructorSuperTypeCheckerPlatformComponent
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirNameConflictsTracker
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirGenericArrayClassLiteralSupport
import org.jetbrains.kotlin.fir.analysis.jvm.FirJvmOverridesBackwardCompatibilityHelper
import org.jetbrains.kotlin.fir.analysis.jvm.checkers.FirJavaNullabilityWarningUpperBoundsProvider
import org.jetbrains.kotlin.fir.analysis.jvm.checkers.FirJvmAnnotationsPlatformSpecificSupportComponent
import org.jetbrains.kotlin.fir.analysis.jvm.checkers.FirJvmInlineCheckerComponent
import org.jetbrains.kotlin.fir.analysis.jvm.checkers.FirJvmPrimaryConstructorSuperTypeCheckerPlatformComponent
@@ -162,6 +164,7 @@ fun FirSession.registerJavaComponents(
register(FirInlineCheckerPlatformSpecificComponent::class, FirJvmInlineCheckerComponent())
register(FirGenericArrayClassLiteralSupport::class, FirGenericArrayClassLiteralSupport.Enabled)
register(FirDelegatedMembersFilter::class, FirJvmDelegatedMembersFilter(this))
register(FirPlatformUpperBoundsProvider::class, FirJavaNullabilityWarningUpperBoundsProvider(this))
}
// -------------------------- Resolve components --------------------------
@@ -12,7 +12,8 @@ public class NullnessUnspecifiedTypeParameter<T> {
public class Test {}
// FILE: main.kt
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<Any?>, x: Test): Unit {
// jspecify_nullness_mismatch
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, x: Test): Unit {
// jspecify_nullness_mismatch
a1.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
a1.foo(1)
@@ -13,7 +13,8 @@ public class NullnessUnspecifiedTypeParameter<T> {
public class Test {}
// FILE: main.kt
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<Any?>, x: Test): Unit {
// jspecify_nullness_mismatch
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, x: Test): Unit {
// jspecify_nullness_mismatch
a1.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
a1.foo(1)
@@ -20,21 +20,26 @@ public class B<T> {
public class Test {}
// FILE: main.kt
fun <T : Test> main(a1: A<Any?>, a2: A<Test>, b1: B<Any?>, b2: B<Test>, x: T): Unit {
// jspecify_nullness_mismatch, jspecify_nullness_mismatch
fun <T : Test> main(a1: A<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, a2: A<Test>, b1: B<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, b2: B<Test>, x: T): Unit {
a1.foo(null)
a1.bar<T?>(null)
// jspecify_nullness_mismatch
a1.bar<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>T?<!>>(null)
a1.bar<T>(x)
a2.foo(null)
a2.bar<T?>(null)
// jspecify_nullness_mismatch
a2.bar<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>T?<!>>(null)
a2.bar<T>(x)
b1.foo(null)
b1.bar<T?>(null)
// jspecify_nullness_mismatch
b1.bar<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>T?<!>>(null)
b1.bar<T>(x)
// jspecify_nullness_mismatch
b2.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
b2.bar<T?>(null)
// jspecify_nullness_mismatch
b2.bar<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>T?<!>>(null)
b2.bar<T>(x)
}
@@ -13,7 +13,7 @@ public class Test {}
// FILE: main.kt
// jspecify_nullness_mismatch
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<Any?>, x: Test): Unit {
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, x: Test): Unit {
// jspecify_nullness_mismatch
a1.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
a1.foo(1)
@@ -14,7 +14,7 @@ public class Test {}
// FILE: main.kt
// jspecify_nullness_mismatch
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<Any?>, x: Test): Unit {
fun main(a1: NullnessUnspecifiedTypeParameter<Any>, a2: NullnessUnspecifiedTypeParameter<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, x: Test): Unit {
// jspecify_nullness_mismatch
a1.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
a1.foo(1)
@@ -21,26 +21,26 @@ public class Test {}
// FILE: main.kt
// jspecify_nullness_mismatch, jspecify_nullness_mismatch
fun <T : Test> main(a1: A<Any?>, a2: A<Test>, b1: B<Any?>, b2: B<Test>, x: T): Unit {
fun <T : Test> main(a1: A<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, a2: A<Test>, b1: B<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>Any?<!>>, b2: B<Test>, x: T): Unit {
a1.foo(null)
// jspecify_nullness_mismatch
a1.bar<T?>(null)
a1.bar<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>T?<!>>(null)
a1.bar<T>(x)
a2.foo(null)
// jspecify_nullness_mismatch
a2.bar<T?>(null)
a2.bar<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>T?<!>>(null)
a2.bar<T>(x)
// jspecify_nullness_mismatch
b1.foo(null)
// jspecify_nullness_mismatch
b1.bar<T?>(null)
b1.bar<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>T?<!>>(null)
b1.bar<T>(x)
// jspecify_nullness_mismatch
b2.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)
// jspecify_nullness_mismatch
b2.bar<T?>(null)
b2.bar<<!UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS!>T?<!>>(null)
b2.bar<T>(x)
}
@@ -1,12 +0,0 @@
// MUTE_FOR_PSI_CLASS_FILES_READING
// SKIP_TXT
// FILE: J1.java
import io.reactivex.rxjava3.annotations.*;
public class J1<@NonNull T> {}
// FILE: main.kt
fun main() {
J1<Any?>() // violated nullability, no warnings; but there is an error with -Xtype-enhancement-improvements-strict-mode
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// MUTE_FOR_PSI_CLASS_FILES_READING
// SKIP_TXT
@@ -57,6 +57,7 @@ internal val diagnosticsToJspecifyMarks = mapOf(
FirJvmErrors.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS to "jspecify_nullness_mismatch",
FirJvmErrors.RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS to "jspecify_nullness_mismatch",
FirJvmErrors.UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS to "jspecify_nullness_mismatch",
),
ReportLevel.STRICT to mapOf(
Errors.TYPE_MISMATCH to "jspecify_nullness_mismatch",