[FIR] Implement UNNECESSARY_SAFE_CALL checker for java warning level types

#KT-63528 Fixed
This commit is contained in:
Kirill Rakhman
2024-01-02 17:22:55 +01:00
committed by Space Team
parent 9c0ac27307
commit d6bd31b313
17 changed files with 66 additions and 288 deletions
@@ -72,4 +72,9 @@ object JvmExpressionCheckers : ExpressionCheckers() {
get() = setOf(
FirAssignmentJavaNullabilityWarningChecker,
)
override val safeCallExpressionCheckers: Set<FirSafeCallExpressionChecker>
get() = setOf(
FirJavaUnnecessarySafeCallChecker,
)
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2024 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.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.AbstractFirUnnecessarySafeCallChecker
import org.jetbrains.kotlin.fir.expressions.FirSafeCallExpression
import org.jetbrains.kotlin.fir.java.enhancement.EnhancedForWarningConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.types.resolvedType
import org.jetbrains.kotlin.fir.types.typeContext
object FirJavaUnnecessarySafeCallChecker : AbstractFirUnnecessarySafeCallChecker() {
override fun check(expression: FirSafeCallExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val receiverType = EnhancedForWarningConeSubstitutor(context.session.typeContext)
.substituteOrNull(expression.receiver.resolvedType)
?.fullyExpandedType(context.session) ?: return
checkSafeCallReceiverType(receiverType, expression.source, context, reporter)
}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
@@ -14,23 +15,35 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.expressions.FirSafeCallExpression
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.canBeNull
import org.jetbrains.kotlin.fir.types.resolvedType
object FirUnnecessarySafeCallChecker : FirSafeCallExpressionChecker() {
abstract class AbstractFirUnnecessarySafeCallChecker : FirSafeCallExpressionChecker() {
protected fun checkSafeCallReceiverType(
receiverType: ConeKotlinType,
source: KtSourceElement?,
context: CheckerContext,
reporter: DiagnosticReporter,
) {
if (!receiverType.canBeNull(context.session)) {
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
reporter.reportOn(source, FirErrors.UNNECESSARY_SAFE_CALL, receiverType, context)
}
if (!context.session.languageVersionSettings.supportsFeature(LanguageFeature.SafeCallsAreAlwaysNullable)) {
reporter.reportOn(source, FirErrors.SAFE_CALL_WILL_CHANGE_NULLABILITY, context)
}
}
}
}
object FirUnnecessarySafeCallChecker : AbstractFirUnnecessarySafeCallChecker() {
override fun check(expression: FirSafeCallExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val receiverType = expression.receiver.resolvedType.fullyExpandedType(context.session)
if (expression.receiver.source?.elementType == KtNodeTypes.SUPER_EXPRESSION) {
reporter.reportOn(expression.source, FirErrors.UNEXPECTED_SAFE_CALL, context)
return
}
if (!receiverType.canBeNull(context.session)) {
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
reporter.reportOn(expression.source, FirErrors.UNNECESSARY_SAFE_CALL, receiverType, context)
}
if (!context.session.languageVersionSettings.supportsFeature(LanguageFeature.SafeCallsAreAlwaysNullable)) {
reporter.reportOn(expression.source, FirErrors.SAFE_CALL_WILL_CHANGE_NULLABILITY, context)
}
}
checkSafeCallReceiverType(receiverType, expression.source, context, reporter)
}
}
@@ -43,10 +43,10 @@ fun main(a: A<String>, a1: A<String?>) {
a1.baz(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>)!!.length
a.baz2("").length
a.baz2("")?.length
a.baz2("")<!UNNECESSARY_SAFE_CALL!>?.<!>length
a.baz2("")!!.length
a.baz2(null).length
a.baz2(null)?.length
a.baz2(null)<!UNNECESSARY_SAFE_CALL!>?.<!>length
a.baz2(null)!!.length
}
@@ -1,38 +0,0 @@
// FILE: ClassWithExternalAnnotatedMembers.java
import org.jetbrains.annotations.NotNull;
public class ClassWithExternalAnnotatedMembers {
public String externalNotNullField;
@NotNull
public String explicitNotNullField;
public static String staticExternalNotNullField;
@NotNull
public static String staticExplicitNotNullField;
}
// FILE: usage.kt
fun test() {
val x = ClassWithExternalAnnotatedMembers()
x.externalNotNullField?.foo()
x.explicitNotNullField<!UNNECESSARY_SAFE_CALL!>?.<!>foo()
ClassWithExternalAnnotatedMembers.staticExternalNotNullField?.foo()
ClassWithExternalAnnotatedMembers.staticExplicitNotNullField<!UNNECESSARY_SAFE_CALL!>?.<!>foo()
}
fun String.foo() {
}
// FILE: annotations.xml
<root>
<item name='ClassWithExternalAnnotatedMembers externalNotNullField'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='ClassWithExternalAnnotatedMembers staticExternalNotNullField'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: ClassWithExternalAnnotatedMembers.java
import org.jetbrains.annotations.NotNull;
@@ -1,44 +0,0 @@
// FILE: one/two/ClassWithExternalAnnotatedMembers.java
package one.two;
import org.jetbrains.annotations.NotNull;
public class ClassWithExternalAnnotatedMembers {
public String externalNotNullField;
@NotNull
public String explicitNotNullField;
public static String staticExternalNotNullField;
@NotNull
public static String staticExplicitNotNullField;
}
// FILE: one/usage.kt
package one
import one.two.ClassWithExternalAnnotatedMembers
fun test() {
val x = ClassWithExternalAnnotatedMembers()
x.externalNotNullField?.foo()
x.explicitNotNullField<!UNNECESSARY_SAFE_CALL!>?.<!>foo()
ClassWithExternalAnnotatedMembers.staticExternalNotNullField?.foo()
ClassWithExternalAnnotatedMembers.staticExplicitNotNullField<!UNNECESSARY_SAFE_CALL!>?.<!>foo()
}
fun String.foo() {
}
// FILE: one/two/annotations.xml
<root>
<item name='one.two.ClassWithExternalAnnotatedMembers externalNotNullField'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='one.two.ClassWithExternalAnnotatedMembers staticExternalNotNullField'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: one/two/ClassWithExternalAnnotatedMembers.java
package one.two;
@@ -1,46 +0,0 @@
// FILE: ClassWithExternalAnnotatedMembers.java
import org.jetbrains.annotations.NotNull;
public class ClassWithExternalAnnotatedMembers {
public String externalNotNullMethod() {
return "";
}
@NotNull
public String explicitNotNullMethod() {
return "";
}
public static String staticExternalNotNullMethod() {
return "";
}
@NotNull
public static String staticExplicitNotNullMethod() {
return "";
}
}
// FILE: usage.kt
fun test() {
val x = ClassWithExternalAnnotatedMembers()
x.externalNotNullMethod()?.foo()
x.explicitNotNullMethod()<!UNNECESSARY_SAFE_CALL!>?.<!>foo()
ClassWithExternalAnnotatedMembers.staticExternalNotNullMethod()?.foo()
ClassWithExternalAnnotatedMembers.staticExplicitNotNullMethod()<!UNNECESSARY_SAFE_CALL!>?.<!>foo()
}
fun String.foo() {
}
// FILE: annotations.xml
<root>
<item name='ClassWithExternalAnnotatedMembers java.lang.String externalNotNullMethod()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='ClassWithExternalAnnotatedMembers java.lang.String staticExternalNotNullMethod()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: ClassWithExternalAnnotatedMembers.java
import org.jetbrains.annotations.NotNull;
@@ -1,71 +0,0 @@
// MODULE: javaModule
// FILE: one/two/FirstModuleClass.java
package one.two;
import org.jetbrains.annotations.NotNull;
public class FirstModuleClass {
public String externalNotNullMethod() {
return "";
}
@NotNull
public String explicitNotNullMethod() {
return "";
}
}
// FILE: one/two/annotations.xml
<root>
<item name='one.two.FirstModuleClass java.lang.String externalNotNullMethod()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
// FILE: usage.kt
package usage1
import one.two.FirstModuleClass
fun test() {
val x = FirstModuleClass()
x.externalNotNullMethod()?.foo()
x.explicitNotNullMethod()<!UNNECESSARY_SAFE_CALL!>?.<!>foo()
}
fun String.foo() {
}
// MODULE: javaModule2
// FILE: three/SecondModuleClass.java
package three;
import org.jetbrains.annotations.NotNull;
public class SecondModuleClass {
public static String staticExternalNotNullMethod() {
return "";
}
@NotNull
public static String staticExplicitNotNullMethod() {
return "";
}
}
// FILE: three/annotations.xml
<root>
<item name='three.SecondModuleClass java.lang.String staticExternalNotNullMethod()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
// FILE: my/pack/usage.kt
package my.pack
import three.SecondModuleClass
fun test() {
SecondModuleClass.staticExternalNotNullMethod()?.foo()
SecondModuleClass.staticExplicitNotNullMethod()<!UNNECESSARY_SAFE_CALL!>?.<!>foo()
}
fun String.foo() {
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// MODULE: javaModule
// FILE: one/two/FirstModuleClass.java
package one.two;
@@ -1,72 +0,0 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE
// JSR305_GLOBAL_REPORT: warn
// FILE: MyNotNull.java
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierNickname;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Nonnull
@TypeQualifierNickname
@Retention(RetentionPolicy.RUNTIME)
public @interface MyNotNull {}
// FILE: AnnotatedWithJsr.java
public class AnnotatedWithJsr {
@MyNotNull
public String getString() {
return null;
}
public void consumeString(@MyNotNull String s) { }
}
// FILE: AnnotatedWithJB.java
import org.jetbrains.annotations.NotNull;
public class AnnotatedWithJB {
public @NotNull String getString() {
return "hello";
}
public void consumeString(@NotNull String s) { }
}
// FILE: PlainJava.java
public class PlainJava {
public String getString() {
return null;
}
public void consumeString(String s) { }
}
// FILE: main.kt
val jsr: AnnotatedWithJsr = AnnotatedWithJsr()
val jsrNullable: AnnotatedWithJsr? = null
val jb: AnnotatedWithJB = AnnotatedWithJB()
val jbNullable: AnnotatedWithJB? = null
val platform: PlainJava = PlainJava()
val platformNullable: PlainJava? = null
fun safeCalls() {
val a = jsr.string?.length
val b = jsrNullable?.string?.length
val c = jb.string<!UNNECESSARY_SAFE_CALL!>?.<!>length
val d = jbNullable?.string?.length
val e = platform.string?.length
val f = platformNullable?.string?.length
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_VARIABLE
// JSR305_GLOBAL_REPORT: warn
@@ -126,7 +126,7 @@ public class A {
// FILE: main.kt
fun main(a: A, b: A.B, c: A.C) {
a.foo("", null)?.length
a.foo("", null)<!UNNECESSARY_SAFE_CALL!>?.<!>length
a.foo("", null).length
a.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, "").length
@@ -136,7 +136,7 @@ fun main(a: A, b: A.B, c: A.C) {
a.bar().length
a.bar()!!.length
a.field?.length
a.field<!UNNECESSARY_SAFE_CALL!>?.<!>length
a.field.length
a.baz()<!UNSAFE_CALL!>.<!>get(0)
@@ -149,7 +149,7 @@ fun main(a: A, b: A.B, c: A.C) {
<!RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>b.foo(null, "")<!>.length
b.foobar(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, "").length
b.foobar("", null)?.length
b.foobar("", null)<!UNNECESSARY_SAFE_CALL!>?.<!>length
<!RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>b.bar()<!>.length
b.bar()!!.length
@@ -162,7 +162,7 @@ fun main(a: A, b: A.B, c: A.C) {
b.baz()!!.get(0)?.get(0)
// c
c.foo("", null)?.length
c.foo("", null)<!UNNECESSARY_SAFE_CALL!>?.<!>length
c.foo("", null).length
c.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, "").length
@@ -63,7 +63,7 @@ public class A {
// FILE: main.kt
fun main(a: A) {
a.foo("", null)?.length
a.foo("", null)<!UNNECESSARY_SAFE_CALL!>?.<!>length
a.foo("", null).length
a.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, "").length
@@ -68,7 +68,7 @@ public class A {
// FILE: main.kt
fun main(a: test.A) {
a.foo("", null)?.length
a.foo("", null)<!UNNECESSARY_SAFE_CALL!>?.<!>length
a.foo("", null).length
a.foo(<!NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS!>null<!>, "").length