Reporting USELESS_ELVIS and UNNECESSARY_NOT_NULL_ASSERTION on Java's @NotNull values
This commit is contained in:
+38
-3
@@ -59,6 +59,9 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
|||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||||
import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext
|
import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability
|
import org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability
|
||||||
|
import org.jetbrains.kotlin.psi.JetPostfixExpression
|
||||||
|
import org.jetbrains.kotlin.psi.JetBinaryExpression
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
|
||||||
public object KotlinJvmCheckerProvider : AdditionalCheckerProvider(
|
public object KotlinJvmCheckerProvider : AdditionalCheckerProvider(
|
||||||
annotationCheckers = listOf(PlatformStaticAnnotationChecker(), LocalFunInlineChecker(), ReifiedTypeParameterAnnotationChecker(), NativeFunChecker()),
|
annotationCheckers = listOf(PlatformStaticAnnotationChecker(), LocalFunInlineChecker(), ReifiedTypeParameterAnnotationChecker(), NativeFunChecker()),
|
||||||
@@ -193,6 +196,39 @@ public class JavaNullabilityWarningsChecker : AdditionalTypeChecker {
|
|||||||
actualMayBeNull ->
|
actualMayBeNull ->
|
||||||
c.trace.report(ErrorsJvm.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS.on(expression, expectedMustNotBeNull, actualMayBeNull))
|
c.trace.report(ErrorsJvm.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS.on(expression, expectedMustNotBeNull, actualMayBeNull))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (expression) {
|
||||||
|
is JetPostfixExpression ->
|
||||||
|
if (expression.getOperationToken() == JetTokens.EXCLEXCL) {
|
||||||
|
val baseExpression = expression.getBaseExpression()
|
||||||
|
val baseExpressionType = c.trace.get(BindingContext.EXPRESSION_TYPE, baseExpression) ?: return
|
||||||
|
warnIfNotNull(
|
||||||
|
DataFlowValueFactory.createDataFlowValue(baseExpression, baseExpressionType, c.trace.getBindingContext()),
|
||||||
|
c
|
||||||
|
) {
|
||||||
|
c.trace.report(Errors.UNNECESSARY_NOT_NULL_ASSERTION.on(expression.getOperationReference(), baseExpressionType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is JetBinaryExpression ->
|
||||||
|
if (expression.getOperationToken() == JetTokens.ELVIS) {
|
||||||
|
val baseExpression = expression.getLeft()
|
||||||
|
val baseExpressionType = c.trace.get(BindingContext.EXPRESSION_TYPE, baseExpression) ?: return
|
||||||
|
warnIfNotNull(
|
||||||
|
DataFlowValueFactory.createDataFlowValue(baseExpression, baseExpressionType, c.trace.getBindingContext()),
|
||||||
|
c
|
||||||
|
) {
|
||||||
|
c.trace.report(Errors.USELESS_ELVIS.on(expression.getOperationReference(), baseExpressionType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun warnIfNotNull(dataFlowValue: DataFlowValue, c: ResolutionContext<*>, reportWarning: () -> Unit) {
|
||||||
|
if (c.dataFlowInfo.getNullability(dataFlowValue).canBeNull()
|
||||||
|
&& dataFlowValue.getType().mustNotBeNull() == NullabilityInformationSource.JAVA) {
|
||||||
|
reportWarning()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkReceiver(
|
override fun checkReceiver(
|
||||||
@@ -224,10 +260,9 @@ public class JavaNullabilityWarningsChecker : AdditionalTypeChecker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (c.dataFlowInfo.getNullability(dataFlowValue).canBeNull()
|
// TODO: Compiler bug
|
||||||
&& receiverArgument.getType().mustNotBeNull() == NullabilityInformationSource.JAVA) {
|
warnIfNotNull(dataFlowValue, c as ResolutionContext<*>) {
|
||||||
c.trace.report(Errors.UNNECESSARY_SAFE_CALL.on(c.call.getCallOperationNode().getPsi(), receiverArgument.getType()))
|
c.trace.report(Errors.UNNECESSARY_SAFE_CALL.on(c.call.getCallOperationNode().getPsi(), receiverArgument.getType()))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-2
@@ -74,6 +74,7 @@ import static org.jetbrains.kotlin.lexer.JetTokens.AS_KEYWORD;
|
|||||||
import static org.jetbrains.kotlin.lexer.JetTokens.AS_SAFE;
|
import static org.jetbrains.kotlin.lexer.JetTokens.AS_SAFE;
|
||||||
import static org.jetbrains.kotlin.resolve.BindingContext.*;
|
import static org.jetbrains.kotlin.resolve.BindingContext.*;
|
||||||
import static org.jetbrains.kotlin.resolve.DescriptorUtils.getStaticNestedClassesScope;
|
import static org.jetbrains.kotlin.resolve.DescriptorUtils.getStaticNestedClassesScope;
|
||||||
|
import static org.jetbrains.kotlin.resolve.calls.context.ContextDependency.DEPENDENT;
|
||||||
import static org.jetbrains.kotlin.resolve.calls.context.ContextDependency.INDEPENDENT;
|
import static org.jetbrains.kotlin.resolve.calls.context.ContextDependency.INDEPENDENT;
|
||||||
import static org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory.createDataFlowValue;
|
import static org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory.createDataFlowValue;
|
||||||
import static org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue.NO_RECEIVER;
|
import static org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue.NO_RECEIVER;
|
||||||
@@ -792,7 +793,17 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
|||||||
DataFlowValue value = createDataFlowValue(baseExpression, baseType, context.trace.getBindingContext());
|
DataFlowValue value = createDataFlowValue(baseExpression, baseType, context.trace.getBindingContext());
|
||||||
dataFlowInfo = dataFlowInfo.disequate(value, DataFlowValue.NULL);
|
dataFlowInfo = dataFlowInfo.disequate(value, DataFlowValue.NULL);
|
||||||
}
|
}
|
||||||
return JetTypeInfo.create(TypeUtils.makeNotNullable(baseType), dataFlowInfo);
|
// The call to checkType() is only needed here to execute additionalTypeCheckers, hence the NO_EXPECTED_TYPE
|
||||||
|
JetType resultingType = TypeUtils.makeNotNullable(baseType);
|
||||||
|
if (context.contextDependency == DEPENDENT) {
|
||||||
|
return JetTypeInfo.create(resultingType, dataFlowInfo);
|
||||||
|
}
|
||||||
|
return DataFlowUtils.checkType(
|
||||||
|
resultingType,
|
||||||
|
expression,
|
||||||
|
context.replaceExpectedType(NO_EXPECTED_TYPE),
|
||||||
|
dataFlowInfo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1083,7 +1094,10 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor {
|
|||||||
if (!TypeUtils.isNullableType(rightType) && TypeUtils.isNullableType(type)) {
|
if (!TypeUtils.isNullableType(rightType) && TypeUtils.isNullableType(type)) {
|
||||||
type = TypeUtils.makeNotNullable(type);
|
type = TypeUtils.makeNotNullable(type);
|
||||||
}
|
}
|
||||||
return JetTypeInfo.create(type, dataFlowInfo);
|
if (context.contextDependency == DEPENDENT) {
|
||||||
|
return JetTypeInfo.create(type, dataFlowInfo);
|
||||||
|
}
|
||||||
|
return DataFlowUtils.checkType(type, expression, context, dataFlowInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// !DIAGNOSTICS: -UNUSED_VARIABLE
|
||||||
|
|
||||||
|
// FILE: p/J.java
|
||||||
|
package p;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.*;
|
||||||
|
|
||||||
|
public class J {
|
||||||
|
@NotNull
|
||||||
|
public static J staticNN;
|
||||||
|
@Nullable
|
||||||
|
public static J staticN;
|
||||||
|
public static J staticJ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: k.kt
|
||||||
|
|
||||||
|
import p.*
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
// @NotNull platform type
|
||||||
|
val platformNN = J.staticNN
|
||||||
|
// @Nullable platform type
|
||||||
|
val platformN = J.staticN
|
||||||
|
// platform type with no annotation
|
||||||
|
val platformJ = J.staticJ
|
||||||
|
|
||||||
|
val v0 = platformNN <!USELESS_ELVIS!>?:<!> J()
|
||||||
|
platformNN <!USELESS_ELVIS!>?:<!> J()
|
||||||
|
platformN ?: J()
|
||||||
|
platformJ ?: J()
|
||||||
|
|
||||||
|
if (platformNN != null) {
|
||||||
|
<!USELESS_ELVIS!>platformNN<!> ?: J()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platformN != null) {
|
||||||
|
<!USELESS_ELVIS!>platformN<!> ?: J()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platformJ != null) {
|
||||||
|
<!USELESS_ELVIS!>platformJ<!> ?: J()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
internal fun test(): kotlin.Unit
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
// !DIAGNOSTICS: -UNUSED_EXPRESSION -SENSELESS_COMPARISON -UNUSED_PARAMETER
|
||||||
|
|
||||||
|
// FILE: p/J.java
|
||||||
|
package p;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.*;
|
||||||
|
|
||||||
|
public class J {
|
||||||
|
@NotNull
|
||||||
|
public static J staticNN;
|
||||||
|
@Nullable
|
||||||
|
public static J staticN;
|
||||||
|
public static J staticJ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: k.kt
|
||||||
|
|
||||||
|
import p.*
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
// @NotNull platform type
|
||||||
|
val platformNN = J.staticNN
|
||||||
|
// @Nullable platform type
|
||||||
|
val platformN = J.staticN
|
||||||
|
// platform type with no annotation
|
||||||
|
val platformJ = J.staticJ
|
||||||
|
|
||||||
|
if (platformNN != null) {
|
||||||
|
platformNN<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platformN != null) {
|
||||||
|
platformN<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platformJ != null) {
|
||||||
|
platformJ<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
|
||||||
|
}
|
||||||
|
|
||||||
|
platformNN<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>
|
||||||
|
platformN!!
|
||||||
|
platformJ!!
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
internal fun test(): kotlin.Unit
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||||
|
|
||||||
|
// FILE: p/J.java
|
||||||
|
package p;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.*;
|
||||||
|
|
||||||
|
public class J {
|
||||||
|
@NotNull
|
||||||
|
public static J staticNN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: k.kt
|
||||||
|
|
||||||
|
import p.*
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
// @NotNull platform type
|
||||||
|
val platformNN = J.staticNN
|
||||||
|
|
||||||
|
foo(platformNN<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>)
|
||||||
|
val bar = Bar()
|
||||||
|
bar(platformNN<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(a: Any) {}
|
||||||
|
|
||||||
|
class Bar {
|
||||||
|
fun invoke(a: Any) {}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
internal fun foo(/*0*/ a: kotlin.Any): kotlin.Unit
|
||||||
|
internal fun test(): kotlin.Unit
|
||||||
|
|
||||||
|
internal final class Bar {
|
||||||
|
public constructor Bar()
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
internal final fun invoke(/*0*/ a: kotlin.Any): kotlin.Unit
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||||
|
|
||||||
|
// FILE: p/J.java
|
||||||
|
package p;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.*;
|
||||||
|
|
||||||
|
public class J {
|
||||||
|
@NotNull
|
||||||
|
public static J staticNN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: k.kt
|
||||||
|
|
||||||
|
import p.*
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
// @NotNull platform type
|
||||||
|
val platformNN = J.staticNN
|
||||||
|
|
||||||
|
foo(platformNN <!USELESS_ELVIS!>?:<!> "")
|
||||||
|
|
||||||
|
val bar = Bar()
|
||||||
|
bar(platformNN <!USELESS_ELVIS!>?:<!> "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(a: Any) {}
|
||||||
|
|
||||||
|
class Bar {
|
||||||
|
fun invoke(a: Any) {}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
internal fun foo(/*0*/ a: kotlin.Any): kotlin.Unit
|
||||||
|
internal fun test(): kotlin.Unit
|
||||||
|
|
||||||
|
internal final class Bar {
|
||||||
|
public constructor Bar()
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
internal final fun invoke(/*0*/ a: kotlin.Any): kotlin.Unit
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
@@ -8598,6 +8598,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("elvis.kt")
|
||||||
|
public void testElvis() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("expectedType.kt")
|
@TestMetadata("expectedType.kt")
|
||||||
public void testExpectedType() throws Exception {
|
public void testExpectedType() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/expectedType.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/expectedType.kt");
|
||||||
@@ -8628,6 +8634,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("notNullAssertion.kt")
|
||||||
|
public void testNotNullAssertion() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertion.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("notNullAssertionInCall.kt")
|
||||||
|
public void testNotNullAssertionInCall() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertionInCall.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("passToJava.kt")
|
@TestMetadata("passToJava.kt")
|
||||||
public void testPassToJava() throws Exception {
|
public void testPassToJava() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/passToJava.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/passToJava.kt");
|
||||||
@@ -8651,6 +8669,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
|||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/throw.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/throw.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("uselessElvisInCall.kt")
|
||||||
|
public void testUselessElvisInCall() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user