From 772579143bc6485a082194e566be7ccc6c68aa26 Mon Sep 17 00:00:00 2001 From: "Denis.Zharkov" Date: Wed, 26 Jan 2022 18:19:17 +0300 Subject: [PATCH] FIR: Change semantics for combination of safe calls and operators ^KT-41034 Fixed --- .../FirLazyBodiesCalculatorTestGenerated.java | 18 +++ .../kotlin/fir/backend/Fir2IrVisitor.kt | 4 +- .../FirBlackBoxCodegenTestGenerated.java | 30 ++++ .../converter/ExpressionsConverter.kt | 8 +- ...ghtTree2FirConverterTestCaseGenerated.java | 15 ++ .../kotlin/fir/builder/RawFirBuilder.kt | 9 +- .../expressions/safeCallsWithAssignment.kt | 15 ++ .../safeCallsWithAssignment.lazyBodies.txt | 2 + .../expressions/safeCallsWithAssignment.txt | 13 ++ .../safeCallsWithAugmentedAssignment.kt | 15 ++ ...allsWithAugmentedAssignment.lazyBodies.txt | 2 + .../safeCallsWithAugmentedAssignment.txt | 13 ++ .../safeCallsWithUnaryOperators.kt | 31 ++++ ...safeCallsWithUnaryOperators.lazyBodies.txt | 3 + .../safeCallsWithUnaryOperators.txt | 133 +++++++++++++++++ ...FirBuilderLazyBodiesTestCaseGenerated.java | 15 ++ .../RawFirBuilderTestCaseGenerated.java | 15 ++ .../kotlin/fir/builder/BaseFirBuilder.kt | 138 +++++++++++++----- .../kotlin/fir/builder/ConversionUtils.kt | 20 ++- .../kotlin/fir/resolve/ResolveUtils.kt | 4 +- .../codegen/box/coroutines/kt21080.kt | 2 - .../box/safeCall/augmentedAssigmentPlus.kt | 56 +++++++ .../safeCall/augmentedAssigmentPlusAssign.kt | 53 +++++++ .../codegen/box/safeCall/incrementPostfix.kt | 58 ++++++++ .../codegen/box/safeCall/incrementPrefix.kt | 57 ++++++++ .../codegen/box/safeCall/withAssignment.kt | 54 +++++++ .../IrBlackBoxCodegenTestGenerated.java | 30 ++++ .../p-4/neg/1.1.fir.kt | 2 +- .../p-4/neg/1.1.fir.kt | 2 +- .../operator-call/p-1/pos/2.2.fir.kt | 12 +- .../operator-call/p-1/pos/2.3.fir.kt | 12 +- .../diagnostics/notLinked/dfa/neg/13.fir.kt | 2 +- .../FirVisualizerForRawFirDataGenerated.java | 18 +++ .../PsiVisualizerForRawFirDataGenerated.java | 18 +++ 34 files changed, 811 insertions(+), 68 deletions(-) create mode 100644 compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt create mode 100644 compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.lazyBodies.txt create mode 100644 compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.txt create mode 100644 compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt create mode 100644 compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.lazyBodies.txt create mode 100644 compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.txt create mode 100644 compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt create mode 100644 compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.lazyBodies.txt create mode 100644 compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.txt create mode 100644 compiler/testData/codegen/box/safeCall/augmentedAssigmentPlus.kt create mode 100644 compiler/testData/codegen/box/safeCall/augmentedAssigmentPlusAssign.kt create mode 100644 compiler/testData/codegen/box/safeCall/incrementPostfix.kt create mode 100644 compiler/testData/codegen/box/safeCall/incrementPrefix.kt create mode 100644 compiler/testData/codegen/box/safeCall/withAssignment.kt diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/FirLazyBodiesCalculatorTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/FirLazyBodiesCalculatorTestGenerated.java index c338ba171d1..7d4c9282c9c 100644 --- a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/FirLazyBodiesCalculatorTestGenerated.java +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/FirLazyBodiesCalculatorTestGenerated.java @@ -433,6 +433,24 @@ public class FirLazyBodiesCalculatorTestGenerated extends AbstractFirLazyBodiesC runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/qualifierWithTypeArguments.kt"); } + @Test + @TestMetadata("safeCallsWithAssignment.kt") + public void testSafeCallsWithAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt"); + } + + @Test + @TestMetadata("safeCallsWithAugmentedAssignment.kt") + public void testSafeCallsWithAugmentedAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt"); + } + + @Test + @TestMetadata("safeCallsWithUnaryOperators.kt") + public void testSafeCallsWithUnaryOperators() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt"); + } + @Test @TestMetadata("simpleReturns.kt") public void testSimpleReturns() throws Exception { diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrVisitor.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrVisitor.kt index ee7844a84cb..1b4d6916ef7 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrVisitor.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrVisitor.kt @@ -391,7 +391,9 @@ class Fir2IrVisitor( ) return conversionScope.withSafeCallSubject(receiverVariable) { - val afterNotNullCheck = safeCallExpression.selector.accept(this, data) as IrExpression + val afterNotNullCheck = + (safeCallExpression.selector as? FirExpression)?.let(::convertToIrExpression) + ?: safeCallExpression.selector.accept(this, data) as IrExpression components.createSafeCallConstruction(receiverVariable, variableSymbol, afterNotNullCheck) } } diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index df672f17c9f..e61a80bbc56 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -41267,12 +41267,36 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/safeCall"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); } + @Test + @TestMetadata("augmentedAssigmentPlus.kt") + public void testAugmentedAssigmentPlus() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/augmentedAssigmentPlus.kt"); + } + + @Test + @TestMetadata("augmentedAssigmentPlusAssign.kt") + public void testAugmentedAssigmentPlusAssign() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/augmentedAssigmentPlusAssign.kt"); + } + @Test @TestMetadata("genericNull.kt") public void testGenericNull() throws Exception { runTest("compiler/testData/codegen/box/safeCall/genericNull.kt"); } + @Test + @TestMetadata("incrementPostfix.kt") + public void testIncrementPostfix() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/incrementPostfix.kt"); + } + + @Test + @TestMetadata("incrementPrefix.kt") + public void testIncrementPrefix() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/incrementPrefix.kt"); + } + @Test @TestMetadata("kt1572.kt") public void testKt1572() throws Exception { @@ -41368,6 +41392,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT public void testSafeCallWithElvisFolding() throws Exception { runTest("compiler/testData/codegen/box/safeCall/safeCallWithElvisFolding.kt"); } + + @Test + @TestMetadata("withAssignment.kt") + public void testWithAssignment() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/withAssignment.kt"); + } } @Nested diff --git a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt index e8505e46e1d..2e3ca6240ea 100644 --- a/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt +++ b/compiler/fir/raw-fir/light-tree2fir/src/org/jetbrains/kotlin/fir/lightTree/converter/ExpressionsConverter.kt @@ -12,8 +12,8 @@ import org.jetbrains.kotlin.ElementTypeUtils.getOperationSymbol import org.jetbrains.kotlin.ElementTypeUtils.isExpression import org.jetbrains.kotlin.KtFakeSourceElementKind import org.jetbrains.kotlin.KtLightSourceElement -import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.KtNodeTypes.* +import org.jetbrains.kotlin.KtSourceElement import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.fakeElement @@ -556,7 +556,7 @@ class ExpressionsConverter( if (isSafe) { @OptIn(FirImplementationDetail::class) it.replaceSource(dotQualifiedExpression.toFirSourceElement(KtFakeSourceElementKind.DesugaredSafeCallExpression)) - return it.wrapWithSafeCall( + return it.createSafeCall( firReceiver!!, dotQualifiedExpression.toFirSourceElement() ) @@ -918,7 +918,7 @@ class ExpressionsConverter( * @see org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseArrayAccess * @see org.jetbrains.kotlin.fir.builder.RawFirBuilder.Visitor.visitArrayAccessExpression */ - private fun convertArrayAccessExpression(arrayAccess: LighterASTNode): FirFunctionCall { + private fun convertArrayAccessExpression(arrayAccess: LighterASTNode): FirExpression { var firExpression: FirExpression? = null val indices: MutableList = mutableListOf() arrayAccess.forEachChildren { @@ -942,7 +942,7 @@ class ExpressionsConverter( getArgument?.let { arguments += it } } origin = FirFunctionCallOrigin.Operator - } + }.pullUpSafeCallIfNecessary() } /** diff --git a/compiler/fir/raw-fir/light-tree2fir/tests-gen/org/jetbrains/kotlin/fir/lightTree/LightTree2FirConverterTestCaseGenerated.java b/compiler/fir/raw-fir/light-tree2fir/tests-gen/org/jetbrains/kotlin/fir/lightTree/LightTree2FirConverterTestCaseGenerated.java index 3f62ab6781c..3d3cdfe531c 100644 --- a/compiler/fir/raw-fir/light-tree2fir/tests-gen/org/jetbrains/kotlin/fir/lightTree/LightTree2FirConverterTestCaseGenerated.java +++ b/compiler/fir/raw-fir/light-tree2fir/tests-gen/org/jetbrains/kotlin/fir/lightTree/LightTree2FirConverterTestCaseGenerated.java @@ -393,6 +393,21 @@ public class LightTree2FirConverterTestCaseGenerated extends AbstractLightTree2F runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/qualifierWithTypeArguments.kt"); } + @TestMetadata("safeCallsWithAssignment.kt") + public void testSafeCallsWithAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt"); + } + + @TestMetadata("safeCallsWithAugmentedAssignment.kt") + public void testSafeCallsWithAugmentedAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt"); + } + + @TestMetadata("safeCallsWithUnaryOperators.kt") + public void testSafeCallsWithUnaryOperators() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt"); + } + @TestMetadata("simpleReturns.kt") public void testSimpleReturns() throws Exception { runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/simpleReturns.kt"); diff --git a/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt b/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt index 84e64d046bf..8353b63060a 100644 --- a/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt +++ b/compiler/fir/raw-fir/psi2fir/src/org/jetbrains/kotlin/fir/builder/RawFirBuilder.kt @@ -10,7 +10,10 @@ import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.* import org.jetbrains.kotlin.builtins.StandardNames.BACKING_FIELD import org.jetbrains.kotlin.builtins.StandardNames.DEFAULT_VALUE_PARAMETER -import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget.* import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.contracts.FirContractDescription @@ -2354,7 +2357,7 @@ open class RawFirBuilder( } } origin = FirFunctionCallOrigin.Operator - } + }.pullUpSafeCallIfNecessary() } override fun visitQualifiedExpression(expression: KtQualifiedExpression, data: Unit): FirElement { @@ -2369,7 +2372,7 @@ open class RawFirBuilder( if (expression is KtSafeQualifiedExpression) { @OptIn(FirImplementationDetail::class) firSelector.replaceSource(expression.toFirSourceElement(KtFakeSourceElementKind.DesugaredSafeCallExpression)) - return firSelector.wrapWithSafeCall( + return firSelector.createSafeCall( receiver, expression.toFirSourceElement() ) diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt new file mode 100644 index 00000000000..35590d24c85 --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt @@ -0,0 +1,15 @@ +fun foo() { + a?.b = 1 + a?.b?.c = 1 + a?.b.c = 1 + + a?.b[0] = 1 + a?.b?.c[0] = 1 + a?.b.c[0] = 1 + + a?.b[0][0] = 1 + a?.b?.c[0][0] = 1 + a?.b.c[0][0] = 1 + + a?.b.d() = 1 +} diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.lazyBodies.txt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.lazyBodies.txt new file mode 100644 index 00000000000..b82a45d6493 --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.lazyBodies.txt @@ -0,0 +1,2 @@ +FILE: safeCallsWithAssignment.kt + public? final? fun foo(): R|kotlin/Unit| { LAZY_BLOCK } diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.txt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.txt new file mode 100644 index 00000000000..38bfc3b1881 --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.txt @@ -0,0 +1,13 @@ +FILE: safeCallsWithAssignment.kt + public? final? fun foo(): R|kotlin/Unit| { + a#?.{ $subj$.b# = IntegerLiteral(1) } + a#?.{ $subj$.b# }?.{ $subj$.c# = IntegerLiteral(1) } + a#?.{ $subj$.b# }.c# = IntegerLiteral(1) + a#?.{ $subj$.b#.set#(IntegerLiteral(0), IntegerLiteral(1)) } + a#?.{ $subj$.b# }?.{ $subj$.c#.set#(IntegerLiteral(0), IntegerLiteral(1)) } + a#?.{ $subj$.b# }.c#.set#(IntegerLiteral(0), IntegerLiteral(1)) + a#?.{ $subj$.b#.get#(IntegerLiteral(0)).set#(IntegerLiteral(0), IntegerLiteral(1)) } + a#?.{ $subj$.b# }?.{ $subj$.c#.get#(IntegerLiteral(0)).set#(IntegerLiteral(0), IntegerLiteral(1)) } + a#?.{ $subj$.b# }.c#.get#(IntegerLiteral(0)).set#(IntegerLiteral(0), IntegerLiteral(1)) + a#?.{ $subj$.b# }.d# = IntegerLiteral(1) + } diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt new file mode 100644 index 00000000000..909e6a8d892 --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt @@ -0,0 +1,15 @@ +fun foo() { + a?.b += 1 + a?.b?.c += 1 + a?.b.c += 1 + + a?.b[0] += 1 + a?.b?.c[0] += 1 + a?.b.c[0] += 1 + + a?.b[0][0] += 1 + a?.b?.c[0][0] += 1 + a?.b.c[0][0] += 1 + + a?.b.d() += 1 +} diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.lazyBodies.txt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.lazyBodies.txt new file mode 100644 index 00000000000..912b632dcf6 --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.lazyBodies.txt @@ -0,0 +1,2 @@ +FILE: safeCallsWithAugmentedAssignment.kt + public? final? fun foo(): R|kotlin/Unit| { LAZY_BLOCK } diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.txt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.txt new file mode 100644 index 00000000000..6c83c5484e9 --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.txt @@ -0,0 +1,13 @@ +FILE: safeCallsWithAugmentedAssignment.kt + public? final? fun foo(): R|kotlin/Unit| { + a#?.{ +=($subj$.b#, IntegerLiteral(1)) } + a#?.{ $subj$.b# }?.{ +=($subj$.c#, IntegerLiteral(1)) } + +=(a#?.{ $subj$.b# }.c#, IntegerLiteral(1)) + a#?.{ ArraySet:[$subj$.b#.get#(IntegerLiteral(0)).plusAssign#(IntegerLiteral(1))] } + a#?.{ $subj$.b# }?.{ ArraySet:[$subj$.c#.get#(IntegerLiteral(0)).plusAssign#(IntegerLiteral(1))] } + ArraySet:[a#?.{ $subj$.b# }.c#.get#(IntegerLiteral(0)).plusAssign#(IntegerLiteral(1))] + a#?.{ ArraySet:[$subj$.b#.get#(IntegerLiteral(0)).get#(IntegerLiteral(0)).plusAssign#(IntegerLiteral(1))] } + a#?.{ $subj$.b# }?.{ ArraySet:[$subj$.c#.get#(IntegerLiteral(0)).get#(IntegerLiteral(0)).plusAssign#(IntegerLiteral(1))] } + ArraySet:[a#?.{ $subj$.b# }.c#.get#(IntegerLiteral(0)).get#(IntegerLiteral(0)).plusAssign#(IntegerLiteral(1))] + +=(a#?.{ $subj$.b# }.d#(), IntegerLiteral(1)) + } diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt new file mode 100644 index 00000000000..75cfc1f8547 --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt @@ -0,0 +1,31 @@ +fun foo() { + a?.b++ + a?.b?.c++ + a?.b.c++ + + a?.b[0]++ + a?.b?.c[0]++ + a?.b.c[0]++ + + a?.b[0][0]++ + a?.b?.c[0][0]++ + a?.b.c[0][0]++ + + a?.b.d()++ +} + +fun foo2() { + ++a?.b + ++a?.b?.c + ++a?.b.c + + ++a?.b[0] + ++a?.b?.c[0] + ++a?.b.c[0] + + ++a?.b[0][0] + ++a?.b?.c[0][0] + ++a?.b.c[0][0] + + ++a?.b.d() +} diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.lazyBodies.txt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.lazyBodies.txt new file mode 100644 index 00000000000..69c0fe33379 --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.lazyBodies.txt @@ -0,0 +1,3 @@ +FILE: safeCallsWithUnaryOperators.kt + public? final? fun foo(): R|kotlin/Unit| { LAZY_BLOCK } + public? final? fun foo2(): R|kotlin/Unit| { LAZY_BLOCK } diff --git a/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.txt b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.txt new file mode 100644 index 00000000000..da890c8b285 --- /dev/null +++ b/compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.txt @@ -0,0 +1,133 @@ +FILE: safeCallsWithUnaryOperators.kt + public? final? fun foo(): R|kotlin/Unit| { + a#?.{ { + lval : = $subj$ + lval : = R|/|.b# + R|/|.b# = R|/|.inc#() + R|/| + } + } + a#?.{ $subj$.b# }?.{ { + lval : = $subj$ + lval : = R|/|.c# + R|/|.c# = R|/|.inc#() + R|/| + } + } + lval : = a#?.{ $subj$.b# } + lval : = R|/|.c# + R|/|.c# = R|/|.inc#() + R|/| + a#?.{ { + lval : = $subj$.b# + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|) + R|/|.set#(R|/|, R|/|.inc#()) + R|/| + } + } + a#?.{ $subj$.b# }?.{ { + lval : = $subj$.c# + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|) + R|/|.set#(R|/|, R|/|.inc#()) + R|/| + } + } + lval : = a#?.{ $subj$.b# }.c# + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|) + R|/|.set#(R|/|, R|/|.inc#()) + R|/| + a#?.{ { + lval : = $subj$.b#.get#(IntegerLiteral(0)) + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|) + R|/|.set#(R|/|, R|/|.inc#()) + R|/| + } + } + a#?.{ $subj$.b# }?.{ { + lval : = $subj$.c#.get#(IntegerLiteral(0)) + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|) + R|/|.set#(R|/|, R|/|.inc#()) + R|/| + } + } + lval : = a#?.{ $subj$.b# }.c#.get#(IntegerLiteral(0)) + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|) + R|/|.set#(R|/|, R|/|.inc#()) + R|/| + lval : = a#?.{ $subj$.b# } + lval : = R|/|.d#() + R|/|.d# = R|/|.inc#() + R|/| + } + public? final? fun foo2(): R|kotlin/Unit| { + a#?.{ { + lval : = $subj$ + lval : = R|/|.b#.inc#() + R|/|.b# = R|/| + R|/| + } + } + a#?.{ $subj$.b# }?.{ { + lval : = $subj$ + lval : = R|/|.c#.inc#() + R|/|.c# = R|/| + R|/| + } + } + lval : = a#?.{ $subj$.b# } + lval : = R|/|.c#.inc#() + R|/|.c# = R|/| + R|/| + a#?.{ { + lval : = $subj$.b# + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|).inc#() + R|/|.set#(R|/|, R|/|) + R|/| + } + } + a#?.{ $subj$.b# }?.{ { + lval : = $subj$.c# + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|).inc#() + R|/|.set#(R|/|, R|/|) + R|/| + } + } + lval : = a#?.{ $subj$.b# }.c# + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|).inc#() + R|/|.set#(R|/|, R|/|) + R|/| + a#?.{ { + lval : = $subj$.b#.get#(IntegerLiteral(0)) + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|).inc#() + R|/|.set#(R|/|, R|/|) + R|/| + } + } + a#?.{ $subj$.b# }?.{ { + lval : = $subj$.c#.get#(IntegerLiteral(0)) + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|).inc#() + R|/|.set#(R|/|, R|/|) + R|/| + } + } + lval : = a#?.{ $subj$.b# }.c#.get#(IntegerLiteral(0)) + lval : = IntegerLiteral(0) + lval : = R|/|.get#(R|/|).inc#() + R|/|.set#(R|/|, R|/|) + R|/| + lval : = a#?.{ $subj$.b# } + lval : = R|/|.d#().inc#() + R|/|.d# = R|/| + R|/| + } diff --git a/compiler/fir/raw-fir/psi2fir/tests-gen/org/jetbrains/kotlin/fir/builder/RawFirBuilderLazyBodiesTestCaseGenerated.java b/compiler/fir/raw-fir/psi2fir/tests-gen/org/jetbrains/kotlin/fir/builder/RawFirBuilderLazyBodiesTestCaseGenerated.java index aff7ecb75b4..2e68a47b3e7 100644 --- a/compiler/fir/raw-fir/psi2fir/tests-gen/org/jetbrains/kotlin/fir/builder/RawFirBuilderLazyBodiesTestCaseGenerated.java +++ b/compiler/fir/raw-fir/psi2fir/tests-gen/org/jetbrains/kotlin/fir/builder/RawFirBuilderLazyBodiesTestCaseGenerated.java @@ -393,6 +393,21 @@ public class RawFirBuilderLazyBodiesTestCaseGenerated extends AbstractRawFirBuil runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/qualifierWithTypeArguments.kt"); } + @TestMetadata("safeCallsWithAssignment.kt") + public void testSafeCallsWithAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt"); + } + + @TestMetadata("safeCallsWithAugmentedAssignment.kt") + public void testSafeCallsWithAugmentedAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt"); + } + + @TestMetadata("safeCallsWithUnaryOperators.kt") + public void testSafeCallsWithUnaryOperators() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt"); + } + @TestMetadata("simpleReturns.kt") public void testSimpleReturns() throws Exception { runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/simpleReturns.kt"); diff --git a/compiler/fir/raw-fir/psi2fir/tests-gen/org/jetbrains/kotlin/fir/builder/RawFirBuilderTestCaseGenerated.java b/compiler/fir/raw-fir/psi2fir/tests-gen/org/jetbrains/kotlin/fir/builder/RawFirBuilderTestCaseGenerated.java index 971d3e88e16..018521f0cc6 100644 --- a/compiler/fir/raw-fir/psi2fir/tests-gen/org/jetbrains/kotlin/fir/builder/RawFirBuilderTestCaseGenerated.java +++ b/compiler/fir/raw-fir/psi2fir/tests-gen/org/jetbrains/kotlin/fir/builder/RawFirBuilderTestCaseGenerated.java @@ -393,6 +393,21 @@ public class RawFirBuilderTestCaseGenerated extends AbstractRawFirBuilderTestCas runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/qualifierWithTypeArguments.kt"); } + @TestMetadata("safeCallsWithAssignment.kt") + public void testSafeCallsWithAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt"); + } + + @TestMetadata("safeCallsWithAugmentedAssignment.kt") + public void testSafeCallsWithAugmentedAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt"); + } + + @TestMetadata("safeCallsWithUnaryOperators.kt") + public void testSafeCallsWithUnaryOperators() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt"); + } + @TestMetadata("simpleReturns.kt") public void testSimpleReturns() throws Exception { runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/simpleReturns.kt"); diff --git a/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/BaseFirBuilder.kt b/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/BaseFirBuilder.kt index eca28ff6e4f..55aee6d472d 100644 --- a/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/BaseFirBuilder.kt +++ b/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/BaseFirBuilder.kt @@ -489,7 +489,7 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte diagnostic = ConeSimpleDiagnostic("Inc/dec without operand", DiagnosticKind.Syntax) } - if (unwrappedReceiver.elementType == DOT_QUALIFIED_EXPRESSION) { + if (unwrappedReceiver.elementType == DOT_QUALIFIED_EXPRESSION || unwrappedReceiver.elementType == SAFE_ACCESS_EXPRESSION) { return generateIncrementOrDecrementBlockForQualifiedAccess( wholeExpression, operationReference, @@ -670,35 +670,35 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte private fun generateIncrementOrDecrementBlockForQualifiedAccess( wholeExpression: T, operationReference: T?, - receiverForOperation: T, + receiverForOperation: T, // a.b callName: Name, prefix: Boolean, convert: T.() -> FirExpression ): FirExpression { - return buildBlock { + val argumentReceiver = receiverForOperation.receiverExpression // a + return buildBlockProbablyUnderSafeCall( + receiverForOperation, + convert, + receiverForOperation.toFirSourceElement(), + ) { qualifiedFir -> + val receiverFir = (qualifiedFir as? FirQualifiedAccess)?.explicitReceiver ?: buildErrorExpression { + source = receiverForOperation.toFirSourceElement() + diagnostic = ConeSimpleDiagnostic("Qualified expression without selector", DiagnosticKind.Syntax) + } + val baseSource = wholeExpression?.toFirSourceElement() val desugaredSource = baseSource?.fakeElement(KtFakeSourceElementKind.DesugaredIncrementOrDecrement) source = desugaredSource - val argumentReceiver = receiverForOperation.receiverExpression - val argumentSelector = receiverForOperation.selectorExpression - val argumentReceiverVariable = generateTemporaryVariable( baseModuleData, argumentReceiver?.toFirSourceElement(), Name.special(""), - argumentReceiver?.convert() ?: buildErrorExpression { - source = receiverForOperation.toFirSourceElement() - diagnostic = ConeSimpleDiagnostic("Qualified expression without receiver", DiagnosticKind.Syntax) - } + initializer = receiverFir, ).also { statements += it } val firArgument = generateResolvedAccessExpression(argumentReceiverVariable.source, argumentReceiverVariable).let { receiver -> - val firArgumentSelector = argumentSelector?.convert() ?: buildErrorExpression { - source = receiverForOperation.toFirSourceElement() - diagnostic = ConeSimpleDiagnostic("Qualified expression without selector", DiagnosticKind.Syntax) - } - firArgumentSelector.also { if (it is FirQualifiedAccessExpression) it.replaceExplicitReceiver(receiver) } + qualifiedFir.also { if (it is FirQualifiedAccessExpression) it.replaceExplicitReceiver(receiver) } } putIncrementOrDecrementStatements( @@ -759,12 +759,14 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte prefix: Boolean, convert: T.() -> FirExpression ): FirExpression { - return buildBlock { + val array = receiver.arrayExpression + return buildBlockProbablyUnderSafeCall( + array, convert, receiver.toFirSourceElement(), + ) { arrayReceiver -> val baseSource = wholeExpression?.toFirSourceElement() val desugaredSource = baseSource?.fakeElement(KtFakeSourceElementKind.DesugaredIncrementOrDecrement) source = desugaredSource - val array = receiver.arrayExpression val indices = receiver.indexExpressions requireNotNull(indices) { "No indices in ${wholeExpression.asText}" } @@ -772,10 +774,7 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte baseModuleData, array?.toFirSourceElement(), Name.special(""), - array?.convert() ?: buildErrorExpression { - source = receiver.toFirSourceElement() - diagnostic = ConeSimpleDiagnostic("No array expression", DiagnosticKind.Syntax) - } + initializer = arrayReceiver, ).also { statements += it } val indexVariables = indices.mapIndexed { i, index -> @@ -829,6 +828,42 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte } } + + // if `receiver` is a safe call a?.f(...), insert a block under safe call + // a?.{ val receiver = $subj$.f() ... } where `...` is generated by `init(FIR<$subj$.f()>)` + // + // Otherwise just returns buildBlock { init(FIR)) } + private fun buildBlockProbablyUnderSafeCall( + receiver: T?, + convert: T.() -> FirExpression, + sourceElementForError: KtSourceElement?, + init: FirBlockBuilder.(receiver: FirExpression) -> Unit = {} + ): FirExpression { + val receiverFir = receiver?.convert() ?: buildErrorExpression { + source = sourceElementForError + diagnostic = ConeSimpleDiagnostic("No receiver expression", DiagnosticKind.Syntax) + } + + if (receiverFir is FirSafeCallExpression) { + receiverFir.replaceSelector( + buildBlock { + init( + receiverFir.selector as? FirExpression ?: buildErrorExpression { + source = sourceElementForError + diagnostic = ConeSimpleDiagnostic("Safe call selector expected to be an expression here", DiagnosticKind.Syntax) + } + ) + } + ) + + return receiverFir + } + + return buildBlock { + init(receiverFir) + } + } + private fun FirQualifiedAccessBuilder.initializeLValue( left: T?, convertQualified: T.() -> FirQualifiedAccess? @@ -893,26 +928,51 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte return if (operation == FirOperation.ASSIGN) { val result = unwrappedLhs.convert() (result.annotations as MutableList) += annotations - result + result.pullUpSafeCallIfNecessary() } else { - generateAugmentedArraySetCall(unwrappedLhs, baseSource, arrayAccessSource, operation, annotations, rhsAST, convert) + val receiver = unwrappedLhs.convert() + + if (receiver is FirSafeCallExpression) { + receiver.replaceSelector( + generateAugmentedArraySetCall( + receiver.selector as FirExpression, baseSource, arrayAccessSource, operation, annotations, rhsAST, convert + ) + ) + receiver + } else { + generateAugmentedArraySetCall(receiver, baseSource, arrayAccessSource, operation, annotations, rhsAST, convert) + } } } if (operation in FirOperation.ASSIGNMENTS && operation != FirOperation.ASSIGN) { - return buildAssignmentOperatorStatement { + val lhsReceiver = this@generateAssignment?.convert() + + val receiverToUse = + if (lhsReceiver is FirSafeCallExpression) + lhsReceiver.selector as? FirExpression + else + lhsReceiver + + val result = buildAssignmentOperatorStatement { source = baseSource this.operation = operation - leftArgument = this@generateAssignment?.convert() - ?: buildErrorExpression { - source = null - diagnostic = ConeSimpleDiagnostic( - "Unsupported left value of assignment: ${baseSource?.psi?.text}", DiagnosticKind.ExpressionExpected - ) - } + leftArgument = receiverToUse ?: buildErrorExpression { + source = null + diagnostic = ConeSimpleDiagnostic( + "Unsupported left value of assignment: ${baseSource?.psi?.text}", DiagnosticKind.ExpressionExpected + ) + } rightArgument = rhsExpression this.annotations += annotations } + + return if (lhsReceiver is FirSafeCallExpression) { + lhsReceiver.replaceSelector(result) + lhsReceiver + } else { + result + } } require(operation == FirOperation.ASSIGN) @@ -956,7 +1016,7 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte } private fun generateAugmentedArraySetCall( - unwrappedReceiver: T, + receiver: FirExpression, // a.get(x,y) baseSource: KtSourceElement?, arrayAccessSource: KtSourceElement?, operation: FirOperation, @@ -967,15 +1027,15 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte return buildAugmentedArraySetCall { source = baseSource this.operation = operation - assignCall = generateAugmentedCallForAugmentedArraySetCall(unwrappedReceiver, baseSource, operation, rhs, convert) + assignCall = generateAugmentedCallForAugmentedArraySetCall(receiver, baseSource, operation, rhs, convert) setGetBlock = - generateSetGetBlockForAugmentedArraySetCall(unwrappedReceiver, baseSource, arrayAccessSource, operation, rhs, convert) + generateSetGetBlockForAugmentedArraySetCall(receiver, baseSource, arrayAccessSource, operation, rhs, convert) this.annotations += annotations } } private fun generateAugmentedCallForAugmentedArraySetCall( - unwrappedReceiver: T, + receiver: FirExpression, // a.get(x,y) baseSource: KtSourceElement?, operation: FirOperation, rhs: T?, @@ -990,7 +1050,7 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte calleeReference = buildSimpleNamedReference { name = FirOperationNameConventions.ASSIGNMENTS.getValue(operation) } - explicitReceiver = unwrappedReceiver.convert() + explicitReceiver = receiver argumentList = buildArgumentList { arguments += rhs?.convert() ?: buildErrorExpression( null, @@ -1003,7 +1063,7 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte private fun generateSetGetBlockForAugmentedArraySetCall( - unwrappedReceiver: T, + receiver: FirExpression, baseSource: KtSourceElement?, arrayAccessSource: KtSourceElement?, operation: FirOperation, @@ -1020,7 +1080,7 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte * } */ return buildBlock { - val baseCall = unwrappedReceiver.convert() as FirFunctionCall + val baseCall = receiver as FirFunctionCall val arrayVariable = generateTemporaryVariable( baseModuleData, @@ -1105,7 +1165,7 @@ abstract class BaseFirBuilder(val baseSession: FirSession, val context: Conte classTypeRefWithCorrectSourceKind: FirTypeRef, firPropertyReturnTypeRefWithCorrectSourceKind: FirTypeRef ) = - buildPropertyAccessExpression { + buildPropertyAccessExpression { source = parameterSource typeRef = firPropertyReturnTypeRefWithCorrectSourceKind dispatchReceiver = buildThisReceiverExpression { diff --git a/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt b/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt index 52eb30cbd82..10cf87bd701 100644 --- a/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt +++ b/compiler/fir/raw-fir/raw-fir.common/src/org/jetbrains/kotlin/fir/builder/ConversionUtils.kt @@ -566,7 +566,10 @@ private fun FirExpression.checkReceiver(name: String?): Boolean { return receiverName == name } -fun FirQualifiedAccess.wrapWithSafeCall(receiver: FirExpression, source: KtSourceElement): FirSafeCallExpression { +// this = .f(...) +// receiver = +// Returns safe call ?.{ f(...) } +fun FirQualifiedAccess.createSafeCall(receiver: FirExpression, source: KtSourceElement): FirSafeCallExpression { val checkedSafeCallSubject = buildCheckedSafeCallSubject { @OptIn(FirContractViolation::class) this.originalReceiverRef = FirExpressionRef().apply { @@ -582,11 +585,24 @@ fun FirQualifiedAccess.wrapWithSafeCall(receiver: FirExpression, source: KtSourc this.checkedSubjectRef = FirExpressionRef().apply { bind(checkedSafeCallSubject) } - this.selector = this@wrapWithSafeCall + this.selector = this@createSafeCall this.source = source } } +// Turns (a?.b).f(...) to a?.{ b.f(...) ) -- for any qualified access `.f(...)` +// Other patterns remain unchanged +fun FirExpression.pullUpSafeCallIfNecessary(): FirExpression { + if (this !is FirQualifiedAccess) return this + val safeCall = explicitReceiver as? FirSafeCallExpression ?: return this + val safeCallSelector = safeCall.selector as? FirExpression ?: return this + + replaceExplicitReceiver(safeCallSelector) + safeCall.replaceSelector(this) + + return safeCall +} + fun List.filterUseSiteTarget(target: AnnotationUseSiteTarget): List = mapNotNull { if (it.useSiteTarget != target) null diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt index 2aca1236e80..a19c13c03a4 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt @@ -392,7 +392,7 @@ fun FirSafeCallExpression.propagateTypeFromQualifiedAccessAfterNullCheck( session: FirSession, ) { val receiverType = nullableReceiverExpression.typeRef.coneTypeSafe() - val typeAfterNullCheck = (selector as? FirQualifiedAccess)?.expressionTypeOrUnitForAssignment() ?: return + val typeAfterNullCheck = selector.expressionTypeOrUnitForAssignment() ?: return val isReceiverActuallyNullable = if (session.languageVersionSettings.supportsFeature(LanguageFeature.SafeCallsAreAlwaysNullable)) { true } else { @@ -409,7 +409,7 @@ fun FirSafeCallExpression.propagateTypeFromQualifiedAccessAfterNullCheck( session.lookupTracker?.recordTypeResolveAsLookup(resolvedTypeRef, source, null) } -private fun FirQualifiedAccess.expressionTypeOrUnitForAssignment(): ConeKotlinType? { +private fun FirStatement.expressionTypeOrUnitForAssignment(): ConeKotlinType? { if (this is FirExpression) return typeRef.coneTypeSafe() require(this is FirVariableAssignment) { diff --git a/compiler/testData/codegen/box/coroutines/kt21080.kt b/compiler/testData/codegen/box/coroutines/kt21080.kt index c1a2f771c0d..6f1aca92c60 100644 --- a/compiler/testData/codegen/box/coroutines/kt21080.kt +++ b/compiler/testData/codegen/box/coroutines/kt21080.kt @@ -1,8 +1,6 @@ // WITH_STDLIB // WITH_COROUTINES // IGNORE_BACKEND: JVM -// IGNORE_BACKEND_FIR: JVM_IR -// FIR status: VARIABLE_EXPECTED at b?.a += ... Not sure FIR supports this (contact Dmitry Novozhilov) import kotlin.coroutines.* import helpers.* diff --git a/compiler/testData/codegen/box/safeCall/augmentedAssigmentPlus.kt b/compiler/testData/codegen/box/safeCall/augmentedAssigmentPlus.kt new file mode 100644 index 00000000000..66576145de8 --- /dev/null +++ b/compiler/testData/codegen/box/safeCall/augmentedAssigmentPlus.kt @@ -0,0 +1,56 @@ +// TARGET_BACKEND: JVM_IR +// IGNORE_BACKEND: JVM_IR + +var cnt = 0 + +class A + +var A?.b: A? + get() { + return this + } + set(v) { + cnt++ + } + +var A?.c: A? + get() { + return this + } + set(v) { + cnt++ + } + +operator fun A?.get(i: Int): A? = this +operator fun A?.set(i: Int, v: A?): A? { + cnt++ + + return this +} + +operator fun A?.plus(a: A?) = this + +fun test(a: A?) { + a?.b += null + a?.b?.c += null + a?.b.c += null // ".c" will be called anyway + + a?.b[0] += null + a?.b?.c[0] += null + a?.b.c[0] += null // ".c" will be called anyway + + a?.b[0][0] += null + a?.b?.c[0][0] += null + a?.b.c[0][0] += null // ".c" will be called anyway +} + +fun box(): String { + test(null) + if (cnt != 3) return "fail 1: $cnt" + + cnt = 0 + test(A()) + if (cnt != 9) return "fail 2: $cnt" + + return "OK" +} diff --git a/compiler/testData/codegen/box/safeCall/augmentedAssigmentPlusAssign.kt b/compiler/testData/codegen/box/safeCall/augmentedAssigmentPlusAssign.kt new file mode 100644 index 00000000000..724b99957b7 --- /dev/null +++ b/compiler/testData/codegen/box/safeCall/augmentedAssigmentPlusAssign.kt @@ -0,0 +1,53 @@ +// TARGET_BACKEND: JVM_IR +// IGNORE_BACKEND: JVM_IR + +var cnt = 0 + +class A + +var A?.b: A? + get() { + return this + } + set(v) { + cnt++ + } + +var A?.c: A? + get() { + return this + } + set(v) { + cnt++ + } + +operator fun A?.get(i: Int): A? = this + +operator fun A?.plusAssign(a: A?) { + cnt++ +} + +fun test(a: A?) { + a?.b += null + a?.b?.c += null + a?.b.c += null // ".c" will be called anyway + + a?.b[0] += null + a?.b?.c[0] += null + a?.b.c[0] += null // ".c" will be called anyway + + a?.b[0][0] += null + a?.b?.c[0][0] += null + a?.b.c[0][0] += null // ".c" will be called anyway +} + +fun box(): String { + test(null) + if (cnt != 3) return "fail 1: $cnt" + + cnt = 0 + test(A()) + if (cnt != 9) return "fail 2: $cnt" + + return "OK" +} diff --git a/compiler/testData/codegen/box/safeCall/incrementPostfix.kt b/compiler/testData/codegen/box/safeCall/incrementPostfix.kt new file mode 100644 index 00000000000..09bc64655e9 --- /dev/null +++ b/compiler/testData/codegen/box/safeCall/incrementPostfix.kt @@ -0,0 +1,58 @@ +// TARGET_BACKEND: JVM_IR +// IGNORE_BACKEND: JVM_IR + +var cnt = 0 + +class A + +var A?.b: A? + get() { + return this + } + set(v) { + cnt++ + } + +var A?.c: A? + get() { + return this + } + set(v) { + cnt++ + } + +operator fun A?.get(i: Int): A? = this +operator fun A?.set(i: Int, v: A?): A? { + cnt++ + + return this +} + +operator fun A?.inc(): A? { + return this +} + +fun test(a: A?) { + a?.b++ + a?.b?.c++ + a?.b.c++ // ".c" will be called anyway + + a?.b[0]++ + a?.b?.c[0]++ + a?.b.c[0]++ // ".c" will be called anyway + + a?.b[0][0]++ + a?.b?.c[0][0]++ + a?.b.c[0][0]++ // ".c" will be called anyway +} + +fun box(): String { + test(null) + if (cnt != 3) return "fail 1: $cnt" + + cnt = 0 + test(A()) + if (cnt != 9) return "fail 2: $cnt" + + return "OK" +} diff --git a/compiler/testData/codegen/box/safeCall/incrementPrefix.kt b/compiler/testData/codegen/box/safeCall/incrementPrefix.kt new file mode 100644 index 00000000000..9d04653017e --- /dev/null +++ b/compiler/testData/codegen/box/safeCall/incrementPrefix.kt @@ -0,0 +1,57 @@ +// TARGET_BACKEND: JVM_IR +// IGNORE_BACKEND: JVM_IR + +var cnt = 0 + +class A + +var A?.b: A? + get() { + return this + } + set(v) { + cnt++ + } + +var A?.c: A? + get() { + return this + } + set(v) { + cnt++ + } + +operator fun A?.get(i: Int): A? = this +operator fun A?.set(i: Int, v: A?): A? { + cnt++ + return this +} + +operator fun A?.inc(): A? { + return this +} + +fun test(a: A?) { + ++a?.b + ++a?.b?.c + ++a?.b.c // ".c" will be called anyway + + ++a?.b[0] + ++a?.b?.c[0] + ++a?.b.c[0] // ".c" will be called anyway + + ++a?.b[0][0] + ++a?.b?.c[0][0] + ++a?.b.c[0][0] // ".c" will be called anyway +} + +fun box(): String { + test(null) + if (cnt != 3) return "fail 1: $cnt" + + cnt = 0 + test(A()) + if (cnt != 9) return "fail 2: $cnt" + + return "OK" +} diff --git a/compiler/testData/codegen/box/safeCall/withAssignment.kt b/compiler/testData/codegen/box/safeCall/withAssignment.kt new file mode 100644 index 00000000000..fcb18ef7245 --- /dev/null +++ b/compiler/testData/codegen/box/safeCall/withAssignment.kt @@ -0,0 +1,54 @@ +// TARGET_BACKEND: JVM_IR +// IGNORE_BACKEND: JVM_IR + +var cnt = 0 + +class A + +var A?.b: A? + get() { + return this + } + set(v) { + cnt++ + } + +var A?.c: A? + get() { + return this + } + set(v) { + cnt++ + } + +operator fun A?.get(i: Int): A? = this +operator fun A?.set(i: Int, v: A?): A? { + cnt++ + + return this +} + +fun test(a: A?) { + a?.b = null + a?.b?.c = null + a?.b.c = null // ".c" will be called anyway + + a?.b[0] = null + a?.b?.c[0] = null + a?.b.c[0] = null // ".c" will be called anyway + + a?.b[0][0] = null + a?.b?.c[0][0] = null + a?.b.c[0][0] = null // ".c" will be called anyway +} + +fun box(): String { + test(null) + if (cnt != 3) return "fail 1: $cnt" + + cnt = 0 + test(A()) + if (cnt != 9) return "fail 2: $cnt" + + return "OK" +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 7357b3ad63b..9d78d4559fd 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -41267,12 +41267,36 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/safeCall"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); } + @Test + @TestMetadata("augmentedAssigmentPlus.kt") + public void testAugmentedAssigmentPlus() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/augmentedAssigmentPlus.kt"); + } + + @Test + @TestMetadata("augmentedAssigmentPlusAssign.kt") + public void testAugmentedAssigmentPlusAssign() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/augmentedAssigmentPlusAssign.kt"); + } + @Test @TestMetadata("genericNull.kt") public void testGenericNull() throws Exception { runTest("compiler/testData/codegen/box/safeCall/genericNull.kt"); } + @Test + @TestMetadata("incrementPostfix.kt") + public void testIncrementPostfix() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/incrementPostfix.kt"); + } + + @Test + @TestMetadata("incrementPrefix.kt") + public void testIncrementPrefix() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/incrementPrefix.kt"); + } + @Test @TestMetadata("kt1572.kt") public void testKt1572() throws Exception { @@ -41368,6 +41392,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes public void testSafeCallWithElvisFolding() throws Exception { runTest("compiler/testData/codegen/box/safeCall/safeCallWithElvisFolding.kt"); } + + @Test + @TestMetadata("withAssignment.kt") + public void testWithAssignment() throws Exception { + runTest("compiler/testData/codegen/box/safeCall/withAssignment.kt"); + } } @Nested diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/prefix-expressions/prefix-decrement-expression/p-4/neg/1.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/prefix-expressions/prefix-decrement-expression/p-4/neg/1.1.fir.kt index fff37ca63ee..f65eea5b8f7 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/prefix-expressions/prefix-decrement-expression/p-4/neg/1.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/prefix-expressions/prefix-decrement-expression/p-4/neg/1.1.fir.kt @@ -15,7 +15,7 @@ class A() { fun case1() { var b: Case1? = Case1() - --b?.a + --b?.a } diff --git a/compiler/tests-spec/testData/diagnostics/linked/expressions/prefix-expressions/prefix-increment-expression/p-4/neg/1.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/expressions/prefix-expressions/prefix-increment-expression/p-4/neg/1.1.fir.kt index 1b263ccaed0..7ecddd6a5a7 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/expressions/prefix-expressions/prefix-increment-expression/p-4/neg/1.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/expressions/prefix-expressions/prefix-increment-expression/p-4/neg/1.1.fir.kt @@ -15,7 +15,7 @@ class A() { fun case1() { var b: Case1? = Case1() - ++b?.a + ++b?.a } diff --git a/compiler/tests-spec/testData/diagnostics/linked/overload-resolution/building-the-overload-candidate-set-ocs/operator-call/p-1/pos/2.2.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/overload-resolution/building-the-overload-candidate-set-ocs/operator-call/p-1/pos/2.2.fir.kt index 3e8f4d6af9d..581054b825d 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/overload-resolution/building-the-overload-candidate-set-ocs/operator-call/p-1/pos/2.2.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/overload-resolution/building-the-overload-candidate-set-ocs/operator-call/p-1/pos/2.2.fir.kt @@ -21,15 +21,15 @@ package testPackCase2 fun case2(a: A?, c: C) { - a?.b += c + a?.b += c a?.b.plusAssign(c) val x = { - a?.b += c + a?.b += c a?.b.plusAssign(c) }() - a?.b += { c }() + a?.b += { c }() a?.b.plusAssign({ c }()) @@ -60,15 +60,15 @@ package testPackCase3 fun case3(a: A?, c: C) { - a?.b += c + a?.b += c a?.b.plusAssign(c) val x = { - a?.b += c + a?.b += c a?.b.plusAssign(c) }() - a?.b += { c }() + a?.b += { c }() a?.b.plusAssign({ c }()) diff --git a/compiler/tests-spec/testData/diagnostics/linked/overload-resolution/building-the-overload-candidate-set-ocs/operator-call/p-1/pos/2.3.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/overload-resolution/building-the-overload-candidate-set-ocs/operator-call/p-1/pos/2.3.fir.kt index 3eef32d554e..b9fa9549f26 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/overload-resolution/building-the-overload-candidate-set-ocs/operator-call/p-1/pos/2.3.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/overload-resolution/building-the-overload-candidate-set-ocs/operator-call/p-1/pos/2.3.fir.kt @@ -43,15 +43,15 @@ package testPackCase2 fun case2(a: A?, c: C) { - a?.b += c + a?.b += c a?.b.plusAssign(c) val x = { - a?.b += c + a?.b += c a?.b.plusAssign(c) }() - a?.b += { c }() + a?.b += { c }() a?.b.plusAssign({ c }()) @@ -81,15 +81,15 @@ package testPackCase3 fun case3(a: A?, c: C) { - a?.b += c + a?.b += c a?.b.plusAssign(c) val x = { - a?.b += c + a?.b += c a?.b.plusAssign(c) }() - a?.b += { c }() + a?.b += { c }() a?.b.plusAssign({ c }()) diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/13.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/13.fir.kt index 8ac004ec9d6..ce6778e7f9f 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/13.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/13.fir.kt @@ -5,7 +5,7 @@ // TESTCASE NUMBER: 1 fun case_1(x: Class?) { x!! - x[if (true) {x=null;0} else 0] += x[0] + x[if (true) {x=null;0} else 0] += x[0] x x[0].inv() } diff --git a/compiler/visualizer/tests-gen/org/jetbrains/kotlin/visualizer/fir/FirVisualizerForRawFirDataGenerated.java b/compiler/visualizer/tests-gen/org/jetbrains/kotlin/visualizer/fir/FirVisualizerForRawFirDataGenerated.java index ede1c7c3d1d..fa1cc7ec3e1 100644 --- a/compiler/visualizer/tests-gen/org/jetbrains/kotlin/visualizer/fir/FirVisualizerForRawFirDataGenerated.java +++ b/compiler/visualizer/tests-gen/org/jetbrains/kotlin/visualizer/fir/FirVisualizerForRawFirDataGenerated.java @@ -433,6 +433,24 @@ public class FirVisualizerForRawFirDataGenerated extends AbstractFirVisualizerTe runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/qualifierWithTypeArguments.kt"); } + @Test + @TestMetadata("safeCallsWithAssignment.kt") + public void testSafeCallsWithAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt"); + } + + @Test + @TestMetadata("safeCallsWithAugmentedAssignment.kt") + public void testSafeCallsWithAugmentedAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt"); + } + + @Test + @TestMetadata("safeCallsWithUnaryOperators.kt") + public void testSafeCallsWithUnaryOperators() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt"); + } + @Test @TestMetadata("simpleReturns.kt") public void testSimpleReturns() throws Exception { diff --git a/compiler/visualizer/tests-gen/org/jetbrains/kotlin/visualizer/psi/PsiVisualizerForRawFirDataGenerated.java b/compiler/visualizer/tests-gen/org/jetbrains/kotlin/visualizer/psi/PsiVisualizerForRawFirDataGenerated.java index c9a5a066f98..4e8dd56f12c 100644 --- a/compiler/visualizer/tests-gen/org/jetbrains/kotlin/visualizer/psi/PsiVisualizerForRawFirDataGenerated.java +++ b/compiler/visualizer/tests-gen/org/jetbrains/kotlin/visualizer/psi/PsiVisualizerForRawFirDataGenerated.java @@ -433,6 +433,24 @@ public class PsiVisualizerForRawFirDataGenerated extends AbstractPsiVisualizerTe runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/qualifierWithTypeArguments.kt"); } + @Test + @TestMetadata("safeCallsWithAssignment.kt") + public void testSafeCallsWithAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAssignment.kt"); + } + + @Test + @TestMetadata("safeCallsWithAugmentedAssignment.kt") + public void testSafeCallsWithAugmentedAssignment() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithAugmentedAssignment.kt"); + } + + @Test + @TestMetadata("safeCallsWithUnaryOperators.kt") + public void testSafeCallsWithUnaryOperators() throws Exception { + runTest("compiler/fir/raw-fir/psi2fir/testData/rawBuilder/expressions/safeCallsWithUnaryOperators.kt"); + } + @Test @TestMetadata("simpleReturns.kt") public void testSimpleReturns() throws Exception {