From fcac449c70822be481d976728bc680a906adb657 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Tue, 3 Feb 2015 18:18:41 +0300 Subject: [PATCH] Nullability-related warnings for receivers #KT-6723 In Progress --- .../load/kotlin/KotlinJvmCheckerProvider.kt | 69 +++++++++++++++++-- .../resolve/jvm/diagnostics/ErrorsJvm.java | 4 +- .../resolve/calls/CandidateResolver.java | 3 + .../calls/checkers/AdditionalTypeChecker.kt | 20 ++++++ .../calls/checkers/TypeApproximator.kt | 12 +++- .../nullabilityWarnings/arithmetic.kt | 12 ++-- .../delegatedProperties.kt | 2 +- .../nullabilityWarnings/derefenceExtension.kt | 17 ++++- .../derefenceExtension.txt | 1 + .../nullabilityWarnings/derefenceMember.kt | 19 ++++- .../nullabilityWarnings/derefenceMember.txt | 1 + .../platformTypes/nullabilityWarnings/for.kt | 2 +- .../nullabilityWarnings/invoke.kt | 2 +- .../nullabilityWarnings/multiDeclaration.kt | 2 +- 14 files changed, 143 insertions(+), 23 deletions(-) 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 d080c4899ca..b9092056a66 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 @@ -52,6 +52,13 @@ import org.jetbrains.kotlin.load.java.lazy.types.isMarkedNotNull import org.jetbrains.kotlin.types.isFlexible import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.NullabilityInformationSource +import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue +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 public object KotlinJvmCheckerProvider : AdditionalCheckerProvider( annotationCheckers = listOf(PlatformStaticAnnotationChecker(), LocalFunInlineChecker(), ReifiedTypeParameterAnnotationChecker(), NativeFunChecker()), @@ -152,16 +159,68 @@ public class JavaNullabilityWarningsChecker : AdditionalTypeChecker { return null } - override fun checkType(expression: JetExpression, expressionType: JetType, c: ResolutionContext<*>) { - if (TypeUtils.noExpectedType(c.expectedType)) return + private fun doCheckType( + expressionType: JetType, + expectedType: JetType, + dataFlowValue: DataFlowValue, + dataFlowInfo: DataFlowInfo, + reportWarning: (expectedMustNotBeNull: NullabilityInformationSource, actualMayBeNull: NullabilityInformationSource) -> Unit + ) { + if (TypeUtils.noExpectedType(expectedType)) return - val expectedMustNotBeNull = c.expectedType.mustNotBeNull() - val actualNullabilityInKotlin = c.dataFlowInfo.getNullability(DataFlowValueFactory.createDataFlowValue(expression, expressionType, c.trace.getBindingContext())) - val actualMayBeNull = if (!actualNullabilityInKotlin.canBeNull()) null else expressionType.mayBeNull() + val expectedMustNotBeNull = expectedType.mustNotBeNull() + val actualNullabilityInKotlin = dataFlowInfo.getNullability(dataFlowValue) + val actualMayBeNull = if (actualNullabilityInKotlin == Nullability.NOT_NULL) null else expressionType.mayBeNull() + + if (expectedMustNotBeNull == NullabilityInformationSource.KOTLIN && actualMayBeNull == NullabilityInformationSource.KOTLIN) { + // a type mismatch error will be reported elsewhere + return; + } if (expectedMustNotBeNull != null && actualMayBeNull != null) { + reportWarning(expectedMustNotBeNull, actualMayBeNull) + } + } + + override fun checkType(expression: JetExpression, expressionType: JetType, c: ResolutionContext<*>) { + doCheckType( + expressionType, + c.expectedType, + DataFlowValueFactory.createDataFlowValue(expression, expressionType, c.trace.getBindingContext()), + c.dataFlowInfo + ) { + expectedMustNotBeNull, + actualMayBeNull -> c.trace.report(ErrorsJvm.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS.on(expression, expectedMustNotBeNull, actualMayBeNull)) } } + override fun checkReceiver( + receiverParameter: ReceiverParameterDescriptor, + receiverArgument: ReceiverValue, + safeAccess: Boolean, + c: CallResolutionContext<*> + ) { + if (!safeAccess) { + doCheckType( + receiverArgument.getType(), + receiverParameter.getType(), + DataFlowValueFactory.createDataFlowValue(receiverArgument, c.trace.getBindingContext()), + c.dataFlowInfo + ) { + expectedMustNotBeNull, + actualMayBeNull -> + val reportOn = + if (receiverArgument is ExpressionReceiver) + receiverArgument.getExpression() + else + c.call.getCalleeExpression() ?: c.call.getCallElement() + + c.trace.report(ErrorsJvm.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS.on( + reportOn, expectedMustNotBeNull, actualMayBeNull + )) + + } + } + } } \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index 4fceaf418f7..4f7a82b32a3 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -24,7 +24,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2; import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.psi.JetDeclaration; -import org.jetbrains.kotlin.psi.JetExpression; +import org.jetbrains.kotlin.psi.JetElement; import static org.jetbrains.kotlin.diagnostics.PositioningStrategies.*; import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; @@ -63,7 +63,7 @@ public interface ErrorsJvm { }; } - DiagnosticFactory2 NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS = DiagnosticFactory2.create(WARNING); + DiagnosticFactory2 NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS = DiagnosticFactory2.create(WARNING); @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.java index d23c15b9213..c4bcd1e043c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.java @@ -644,6 +644,9 @@ public class CandidateResolver { if (safeAccess && !context.dataFlowInfo.getNullability(receiverValue).canBeNull()) { context.tracing.unnecessarySafeCall(trace, receiverArgumentType); } + + context.additionalTypeChecker.checkReceiver(receiverParameter, receiverArgument, safeAccess, context); + return SUCCESS; } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AdditionalTypeChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AdditionalTypeChecker.kt index 8d0b1845249..63433a8efdc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AdditionalTypeChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AdditionalTypeChecker.kt @@ -19,6 +19,9 @@ package org.jetbrains.kotlin.resolve.calls.checkers import org.jetbrains.kotlin.psi.JetExpression import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext +import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue +import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext public trait AdditionalTypeChecker { @@ -28,7 +31,24 @@ public trait AdditionalTypeChecker { checker.checkType(expression, expressionType, c) } } + + override fun checkReceiver( + receiverParameter: ReceiverParameterDescriptor, + receiverArgument: ReceiverValue, + safeAccess: Boolean, + c: CallResolutionContext<*> + ) { + for (checker in checkers) { + checker.checkReceiver(receiverParameter, receiverArgument, safeAccess, c) + } + } } fun checkType(expression: JetExpression, expressionType: JetType, c: ResolutionContext<*>) + fun checkReceiver( + receiverParameter: ReceiverParameterDescriptor, + receiverArgument: ReceiverValue, + safeAccess: Boolean, + c: CallResolutionContext<*> + ) } \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/TypeApproximator.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/TypeApproximator.kt index 4a679f6bdf8..fa6309c16b8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/TypeApproximator.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/TypeApproximator.kt @@ -24,10 +24,11 @@ import org.jetbrains.kotlin.types.TypeUtils.noExpectedType import org.jetbrains.kotlin.types.getApproximationTo import org.jetbrains.kotlin.types.Approximation import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory -import org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.resolve.BindingContext -import kotlin.properties.Delegates +import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue +import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext public class TypeApproximator : AdditionalTypeChecker { override fun checkType(expression: JetExpression, expressionType: JetType, c: ResolutionContext<*>) { @@ -51,4 +52,11 @@ public class TypeApproximator : AdditionalTypeChecker { c.trace.record(BindingContext.EXPRESSION_RESULT_APPROXIMATION, expression, approximationInfo) } } + + override fun checkReceiver( + receiverParameter: ReceiverParameterDescriptor, + receiverArgument: ReceiverValue, + safeAccess: Boolean, + c: CallResolutionContext<*> + ) { } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/arithmetic.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/arithmetic.kt index 9f0893f8617..43aa5abd3d0 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/arithmetic.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/arithmetic.kt @@ -24,15 +24,15 @@ fun test() { var platformJ = J.staticJ +platformNN - +platformN + +platformN +platformJ ++platformNN - ++platformN + ++platformN ++platformJ platformNN++ - platformN++ + platformN++ platformJ++ 1 + platformNN @@ -40,7 +40,7 @@ fun test() { 1 + platformJ platformNN + 1 - platformN + 1 + platformN + 1 platformJ + 1 1 plus platformNN @@ -48,11 +48,11 @@ fun test() { 1 plus platformJ platformNN plus 1 - platformN plus 1 + platformN plus 1 platformJ plus 1 platformNN += 1 - platformN += 1 + platformN += 1 platformJ += 1 } diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/delegatedProperties.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/delegatedProperties.kt index 779de1aa89a..19bf516c533 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/delegatedProperties.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/delegatedProperties.kt @@ -22,5 +22,5 @@ public class J { import p.* var A by J.staticNN -var B by J.staticN +var B by J.staticN var C by J.staticJ \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceExtension.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceExtension.kt index f347587985e..0c61dbac4c3 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceExtension.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceExtension.kt @@ -1,3 +1,5 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + // FILE: p/J.java package p; @@ -24,13 +26,24 @@ fun test() { val platformJ = J.staticJ platformNN.foo() - platformN.foo() + platformN.foo() platformJ.foo() + with(platformNN) { + foo() + } + with(platformN) { + foo() + } + with(platformJ) { + foo() + } + platformNN.bar() platformN.bar() platformJ.bar() } fun J.foo() {} -fun J?.bar() {} \ No newline at end of file +fun J?.bar() {} +fun with(t: T, f: T.() -> Unit) {} diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceExtension.txt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceExtension.txt index 7a159d150bd..e909556b47c 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceExtension.txt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceExtension.txt @@ -1,5 +1,6 @@ package internal fun test(): kotlin.Unit +internal fun with(/*0*/ t: T, /*1*/ f: T.() -> kotlin.Unit): kotlin.Unit internal fun p.J?.bar(): kotlin.Unit internal fun p.J.foo(): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceMember.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceMember.kt index ab50a82968a..0ccc1a0b2fc 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceMember.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceMember.kt @@ -1,3 +1,5 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + // FILE: p/J.java package p; @@ -26,6 +28,19 @@ fun test() { val platformJ = J.staticJ platformNN.foo() - platformN.foo() + platformN.foo() platformJ.foo() -} \ No newline at end of file + + with(platformNN) { + foo() + } + with(platformN) { + foo() + } + with(platformJ) { + foo() + } +} + +fun with(t: T, f: T.() -> Unit) {} + diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceMember.txt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceMember.txt index b8def46715c..7e19ffea500 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceMember.txt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/derefenceMember.txt @@ -1,3 +1,4 @@ package internal fun test(): kotlin.Unit +internal fun with(/*0*/ t: T, /*1*/ f: T.() -> kotlin.Unit): kotlin.Unit diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/for.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/for.kt index 61019d75c5b..bb5589537e6 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/for.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/for.kt @@ -25,7 +25,7 @@ fun test() { val platformJ = J.staticJ for (x in platformNN) {} - for (x in platformN) {} + for (x in platformN) {} for (x in platformJ) {} } diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/invoke.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/invoke.kt index d7805c85fff..d7fdcac56eb 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/invoke.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/invoke.kt @@ -21,7 +21,7 @@ import p.* fun test() { J.staticNN() - J.staticN() + J.staticN() J.staticJ() } diff --git a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/multiDeclaration.kt b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/multiDeclaration.kt index dd4b48f3ed1..c7ddde2a2b6 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/multiDeclaration.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/nullabilityWarnings/multiDeclaration.kt @@ -30,7 +30,7 @@ fun test() { val platformJ = J.staticJ val (a1, b1) = platformNN - val (a2, b2) = platformN + val (a2, b2) = platformN val (a3, b3) = platformJ }