diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt index 2be1415c51c..4f8f746bbcb 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinJvmCheckerProvider.kt @@ -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.calls.context.CallResolutionContext 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( annotationCheckers = listOf(PlatformStaticAnnotationChecker(), LocalFunInlineChecker(), ReifiedTypeParameterAnnotationChecker(), NativeFunChecker()), @@ -193,6 +196,39 @@ public class JavaNullabilityWarningsChecker : AdditionalTypeChecker { 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( @@ -224,10 +260,9 @@ public class JavaNullabilityWarningsChecker : AdditionalTypeChecker { } } else { - if (c.dataFlowInfo.getNullability(dataFlowValue).canBeNull() - && receiverArgument.getType().mustNotBeNull() == NullabilityInformationSource.JAVA) { + // TODO: Compiler bug + warnIfNotNull(dataFlowValue, c as ResolutionContext<*>) { c.trace.report(Errors.UNNECESSARY_SAFE_CALL.on(c.call.getCallOperationNode().getPsi(), receiverArgument.getType())) - } } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index 120639a08f3..c367d5ac54e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -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.resolve.BindingContext.*; 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.smartcasts.DataFlowValueFactory.createDataFlowValue; 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()); 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 @@ -1083,7 +1094,10 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { if (!TypeUtils.isNullableType(rightType) && TypeUtils.isNullableType(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 diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.kt new file mode 100644 index 00000000000..6223ccdd989 --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.kt @@ -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 ?: J() + platformNN ?: J() + platformN ?: J() + platformJ ?: J() + + if (platformNN != null) { + platformNN ?: J() + } + + if (platformN != null) { + platformN ?: J() + } + + if (platformJ != null) { + platformJ ?: J() + } +} + diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.txt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.txt new file mode 100644 index 00000000000..b8def46715c --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/elvis.txt @@ -0,0 +1,3 @@ +package + +internal fun test(): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertion.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertion.kt new file mode 100644 index 00000000000..8d882af3213 --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertion.kt @@ -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!! + } + + if (platformN != null) { + platformN!! + } + + if (platformJ != null) { + platformJ!! + } + + platformNN!! + platformN!! + platformJ!! +} diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertion.txt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertion.txt new file mode 100644 index 00000000000..b8def46715c --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertion.txt @@ -0,0 +1,3 @@ +package + +internal fun test(): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertionInCall.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertionInCall.kt new file mode 100644 index 00000000000..42d5c782d80 --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertionInCall.kt @@ -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!!) + val bar = Bar() + bar(platformNN!!) +} + +fun foo(a: Any) {} + +class Bar { + fun invoke(a: Any) {} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertionInCall.txt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertionInCall.txt new file mode 100644 index 00000000000..79f259ce9cb --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/notNullAssertionInCall.txt @@ -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 +} diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.kt new file mode 100644 index 00000000000..888940952c7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.kt @@ -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 ?: "") + + val bar = Bar() + bar(platformNN ?: "") +} + +fun foo(a: Any) {} + +class Bar { + fun invoke(a: Any) {} +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.txt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.txt new file mode 100644 index 00000000000..79f259ce9cb --- /dev/null +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.txt @@ -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 +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index ef6e8e64205..457ec5ffe00 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -8598,6 +8598,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { 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") public void testExpectedType() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/expectedType.kt"); @@ -8628,6 +8634,18 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { 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") public void testPassToJava() throws Exception { 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"); doTest(fileName); } + + @TestMetadata("uselessElvisInCall.kt") + public void testUselessElvisInCall() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/uselessElvisInCall.kt"); + doTest(fileName); + } } }