From 078acb1bfe33c0ae8b46125684931c8135d0ef96 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Mon, 28 Mar 2022 11:28:08 +0400 Subject: [PATCH] [FE] Fix positioning strategy for SAFE_CALL_WILL_CHANGE_NULLABILITY warning ^KT-46860 --- ...CompilerTestFE10TestdataTestGenerated.java | 6 ++++ ...irOldFrontendDiagnosticsTestGenerated.java | 6 ++++ ...DiagnosticsWithLightTreeTestGenerated.java | 6 ++++ .../diagnostics/FirDiagnosticsList.kt | 2 +- .../model/RegularDiagnosticData.kt | 2 +- .../fir/analysis/diagnostics/FirErrors.kt | 2 +- .../LightTreePositioningStrategies.kt | 24 +++++++++++++ .../diagnostics/PositioningStrategies.kt | 19 ++++++++++ .../SourceElementPositioningStrategies.kt | 5 +++ .../jetbrains/kotlin/diagnostics/Errors.java | 2 +- .../tests/SafeCallNonNullReceiver.kt | 2 +- .../controlFlowAnalysis/throwInLambda.fir.kt | 2 +- .../controlFlowAnalysis/throwInLambda.kt | 2 +- .../safeCallOnNotNullableType.kt | 35 +++++++++++++++++++ .../safeCallOnNotNullableType.txt | 13 +++++++ .../test/runners/DiagnosticTestGenerated.java | 6 ++++ 16 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.kt create mode 100644 compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.txt diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java index e33d91f510d..6262629775f 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/diagnostic/compiler/based/DiagnosisCompilerTestFE10TestdataTestGenerated.java @@ -21176,6 +21176,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag runTest("compiler/testData/diagnostics/tests/nullableTypes/safeAccessOnUnit.kt"); } + @Test + @TestMetadata("safeCallOnNotNullableType.kt") + public void testSafeCallOnNotNullableType() throws Exception { + runTest("compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.kt"); + } + @Test @TestMetadata("safeCallOnTypeWithNullableUpperBound.kt") public void testSafeCallOnTypeWithNullableUpperBound() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java index 2bb468aa910..efd9ed4976f 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsTestGenerated.java @@ -21176,6 +21176,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti runTest("compiler/testData/diagnostics/tests/nullableTypes/safeAccessOnUnit.kt"); } + @Test + @TestMetadata("safeCallOnNotNullableType.kt") + public void testSafeCallOnNotNullableType() throws Exception { + runTest("compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.kt"); + } + @Test @TestMetadata("safeCallOnTypeWithNullableUpperBound.kt") public void testSafeCallOnTypeWithNullableUpperBound() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java index 4c41f2c7fca..b3f63ddee01 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendDiagnosticsWithLightTreeTestGenerated.java @@ -21176,6 +21176,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac runTest("compiler/testData/diagnostics/tests/nullableTypes/safeAccessOnUnit.kt"); } + @Test + @TestMetadata("safeCallOnNotNullableType.kt") + public void testSafeCallOnNotNullableType() throws Exception { + runTest("compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.kt"); + } + @Test @TestMetadata("safeCallOnTypeWithNullableUpperBound.kt") public void testSafeCallOnTypeWithNullableUpperBound() throws Exception { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 3c0d8544940..573f9fbcfad 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -1113,7 +1113,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val UNNECESSARY_SAFE_CALL by warning(PositioningStrategy.SAFE_ACCESS) { parameter("receiverType") } - val SAFE_CALL_WILL_CHANGE_NULLABILITY by warning() + val SAFE_CALL_WILL_CHANGE_NULLABILITY by warning(PositioningStrategy.CALL_ELEMENT_WITH_DOT) val UNEXPECTED_SAFE_CALL by error(PositioningStrategy.SAFE_ACCESS) val UNNECESSARY_NOT_NULL_ASSERTION by warning(PositioningStrategy.OPERATOR) { parameter("receiverType") diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt index ab7b36060fe..20d8f48ba15 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt @@ -113,7 +113,7 @@ enum class PositioningStrategy(private val strategy: String? = null) { DECLARATION_START_TO_NAME, REDUNDANT_NULLABLE, INLINE_FUN_MODIFIER, - + CALL_ELEMENT_WITH_DOT, ; val expressionToCreate get() = "SourceElementPositioningStrategies.${strategy ?: name}" diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 149ab8076ee..ac798bd1375 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -592,7 +592,7 @@ object FirErrors { val UNSAFE_OPERATOR_CALL by error3(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED) val ITERATOR_ON_NULLABLE by error0() val UNNECESSARY_SAFE_CALL by warning1(SourceElementPositioningStrategies.SAFE_ACCESS) - val SAFE_CALL_WILL_CHANGE_NULLABILITY by warning0() + val SAFE_CALL_WILL_CHANGE_NULLABILITY by warning0(SourceElementPositioningStrategies.CALL_ELEMENT_WITH_DOT) val UNEXPECTED_SAFE_CALL by error0(SourceElementPositioningStrategies.SAFE_ACCESS) val UNNECESSARY_NOT_NULL_ASSERTION by warning1(SourceElementPositioningStrategies.OPERATOR) val NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION by warning0(SourceElementPositioningStrategies.OPERATOR) diff --git a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt index 4e0cd135636..747f02c4381 100644 --- a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt +++ b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt @@ -1120,6 +1120,30 @@ object LightTreePositioningStrategies { return markElement(byKeyword, startOffset, endOffset, tree, node) } } + + val CALL_ELEMENT_WITH_DOT: LightTreePositioningStrategy = object : LightTreePositioningStrategy() { + override fun mark( + node: LighterASTNode, + startOffset: Int, + endOffset: Int, + tree: FlyweightCapableTreeStructure + ): List { + val callElementRanges = SELECTOR_BY_QUALIFIED.mark(node, startOffset, endOffset, tree) + val callElementRange = when (callElementRanges.size) { + 1 -> callElementRanges.first() + else -> return callElementRanges + } + + val dotRanges = SAFE_ACCESS.mark(node, startOffset, endOffset, tree) + val dotRange = when (dotRanges.size) { + 1 -> dotRanges.first() + else -> return dotRanges + } + + return listOf(TextRange(dotRange.startOffset, callElementRange.endOffset)) + } + } + } fun KtSourceElement.hasValOrVar(): Boolean = diff --git a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt index 8fce2595260..c66bd4202f4 100644 --- a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt +++ b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt @@ -499,6 +499,25 @@ object PositioningStrategies { } } + @JvmField + val CALL_ELEMENT_WITH_DOT: PositioningStrategy = object : PositioningStrategy() { + override fun mark(element: KtQualifiedExpression): List { + val callElementRanges = SELECTOR_BY_QUALIFIED.mark(element) + val callElementRange = when (callElementRanges.size) { + 1 -> callElementRanges.first() + else -> return callElementRanges + } + + val dotRanges = SAFE_ACCESS.mark(element) + val dotRange = when (dotRanges.size) { + 1 -> dotRanges.first() + else -> return dotRanges + } + + return listOf(TextRange(dotRange.startOffset, callElementRange.endOffset)) + } + } + @JvmField val DECLARATION_WITH_BODY: PositioningStrategy = object : PositioningStrategy() { override fun mark(element: KtDeclarationWithBody): List { diff --git a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/SourceElementPositioningStrategies.kt b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/SourceElementPositioningStrategies.kt index 5a53c8633c8..27edf1346ac 100644 --- a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/SourceElementPositioningStrategies.kt +++ b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/SourceElementPositioningStrategies.kt @@ -364,4 +364,9 @@ object SourceElementPositioningStrategies { LightTreePositioningStrategies.DELEGATED_SUPERTYPE_BY_KEYWORD, PositioningStrategies.DELEGATED_SUPERTYPE_BY_KEYWORD ) + + val CALL_ELEMENT_WITH_DOT = SourceElementPositioningStrategy( + LightTreePositioningStrategies.CALL_ELEMENT_WITH_DOT, + PositioningStrategies.CALL_ELEMENT_WITH_DOT + ) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index b6b52436d2f..8d1a392261c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -1097,7 +1097,7 @@ public interface Errors { DiagnosticFactory3 UNSAFE_INFIX_CALL = DiagnosticFactory3.create(ERROR); DiagnosticFactory3 UNSAFE_OPERATOR_CALL = DiagnosticFactory3.create(ERROR); DiagnosticFactory1 UNNECESSARY_SAFE_CALL = DiagnosticFactory1.create(WARNING); - DiagnosticFactory0 SAFE_CALL_WILL_CHANGE_NULLABILITY = DiagnosticFactory0.create(WARNING); + DiagnosticFactory0 SAFE_CALL_WILL_CHANGE_NULLABILITY = DiagnosticFactory0.create(WARNING, PositioningStrategies.CALL_ELEMENT_WITH_DOT); DiagnosticFactory0 UNEXPECTED_SAFE_CALL = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 UNNECESSARY_NOT_NULL_ASSERTION = DiagnosticFactory1.create(WARNING); DiagnosticFactory0 NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION = DiagnosticFactory0.create(WARNING); diff --git a/compiler/testData/diagnostics/tests/SafeCallNonNullReceiver.kt b/compiler/testData/diagnostics/tests/SafeCallNonNullReceiver.kt index fa7ef57037a..6d1d4b10516 100644 --- a/compiler/testData/diagnostics/tests/SafeCallNonNullReceiver.kt +++ b/compiler/testData/diagnostics/tests/SafeCallNonNullReceiver.kt @@ -4,5 +4,5 @@ fun ff() { val i: Int = 1 - val a: Int = i?.plus(2) + val a: Int = i?.plus(2) } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.fir.kt index 3407594a327..9ac2a512356 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.fir.kt @@ -8,6 +8,6 @@ fun foo(): String { } fun bar(): String { val x = fn() ?: return "" - val y = x?.let { throw Exception() } ?: "unreachable" + val y = x?.let { throw Exception() } ?: "unreachable" return y } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.kt index c3c30cd26f8..718301a6cdf 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.kt @@ -8,6 +8,6 @@ fun foo(): String { } fun bar(): String { val x = fn() ?: return "" - val y = x?.let { throw Exception() } ?: "unreachable" + val y = x?.let { throw Exception() } ?: "unreachable" return y } diff --git a/compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.kt b/compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.kt new file mode 100644 index 00000000000..03e40352f7b --- /dev/null +++ b/compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.kt @@ -0,0 +1,35 @@ +// FIR_IDENTICAL +// LANGUAGE: -SafeCallsAreAlwaysNullable +// DIAGNOSTICS: -UNNECESSARY_SAFE_CALL +// ISSUE: KT-46860 + +interface A { + fun id(): A + + fun foo(): String +} + +fun test_1(a: A) { + val s = a.id().id().id().id().id().id().id().id()?.foo().length +} + +fun test_2(a: A) { + val s = a.id() + .id() + .id() + .id() + ?.id() + .id() + .id() + .id() + ?.foo() + ?.length +} + +fun test_3(a: A) { + val s = a.id()?. + id(). + id()?. + foo(). + length +} diff --git a/compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.txt b/compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.txt new file mode 100644 index 00000000000..53995a2cb73 --- /dev/null +++ b/compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.txt @@ -0,0 +1,13 @@ +package + +public fun test_1(/*0*/ a: A): kotlin.Unit +public fun test_2(/*0*/ a: A): kotlin.Unit +public fun test_3(/*0*/ a: A): kotlin.Unit + +public interface A { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public abstract fun foo(): kotlin.String + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public abstract fun id(): A + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 1a247e0aa1c..4f5ac2b9403 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -21182,6 +21182,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/nullableTypes/safeAccessOnUnit.kt"); } + @Test + @TestMetadata("safeCallOnNotNullableType.kt") + public void testSafeCallOnNotNullableType() throws Exception { + runTest("compiler/testData/diagnostics/tests/nullableTypes/safeCallOnNotNullableType.kt"); + } + @Test @TestMetadata("safeCallOnTypeWithNullableUpperBound.kt") public void testSafeCallOnTypeWithNullableUpperBound() throws Exception {