diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt index 14af3f26369..1ad4058a3d8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/EffectSystem.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall @@ -127,6 +128,10 @@ class EffectSystem( bindingTrace: BindingTrace, moduleDescriptor: ModuleDescriptor ): MutableContextInfo { + val isInContractBlock = expression.parentsWithSelf.filterIsInstance().any { + bindingTrace.bindingContext[BindingContext.IS_CONTRACT_DECLARATION_BLOCK, it] == true + } + if (isInContractBlock) return MutableContextInfo.EMPTY val computation = getNonTrivialComputation(expression, bindingTrace, moduleDescriptor) ?: return MutableContextInfo.EMPTY return InfoCollector(observedEffect, constants).collectFromSchema(computation.effects) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java index a2f0c0b7a0d..430fe25fda9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java @@ -99,6 +99,7 @@ public interface BindingContext { WritableSlice EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice<>(DO_NOTHING); WritableSlice EXPRESSION_EFFECTS = Slices.createSimpleSlice(); WritableSlice CONTRACT_NOT_ALLOWED = Slices.createSimpleSlice(); + WritableSlice IS_CONTRACT_DECLARATION_BLOCK = Slices.createSimpleSlice(); WritableSlice EXPECTED_RETURN_TYPE = new BasicWritableSlice<>(DO_NOTHING); WritableSlice DATAFLOW_INFO_AFTER_CONDITION = Slices.createSimpleSlice(); WritableSlice BOUND_INITIALIZER_VALUE = Slices.createSimpleSlice(); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt index 2c18fb999a8..800136fa4f8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallCompleter.kt @@ -9,12 +9,18 @@ import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType import org.jetbrains.kotlin.builtins.isFunctionOrSuspendFunctionType import org.jetbrains.kotlin.contracts.EffectSystem +import org.jetbrains.kotlin.contracts.description.ContractProviderKey +import org.jetbrains.kotlin.contracts.description.LazyContractProvider +import org.jetbrains.kotlin.contracts.parsing.isContractCallDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.isContractDescriptionCallPsiCheck +import org.jetbrains.kotlin.psi.psiUtil.isFirstStatement import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.BindingContext.CONSTRAINT_SYSTEM_COMPLETER import org.jetbrains.kotlin.resolve.calls.callResolverUtil.ResolveArgumentsMode.RESOLVE_FUNCTION_ARGUMENTS @@ -129,6 +135,20 @@ class CallCompleter( context: BasicCallResolutionContext, tracing: TracingStrategy ) { + context.call.callElement.safeAs()?.let { callExpression -> + if (callExpression.isFirstStatement() && callExpression.isContractDescriptionCallPsiCheck()) { + if (resolvedCall?.resultingDescriptor?.isContractCallDescriptor() != true) { + context.scope.ownerDescriptor + .safeAs() + ?.getUserData(ContractProviderKey) + ?.safeAs() + ?.setContractDescription(null) + } else { + context.trace.record(BindingContext.IS_CONTRACT_DECLARATION_BLOCK, callExpression, true) + } + } + } + if (resolvedCall == null || resolvedCall.isCompleted || resolvedCall.constraintSystem == null) { completeArguments(context, results) resolvedCall?.updateResultDataFlowInfoUsingEffects(context.trace) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/ContractNotAllowedCallChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/ContractNotAllowedCallChecker.kt index 8d259f722da..fdbd1dee728 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/ContractNotAllowedCallChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/ContractNotAllowedCallChecker.kt @@ -12,9 +12,8 @@ import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor import org.jetbrains.kotlin.descriptors.isOverridable import org.jetbrains.kotlin.diagnostics.Errors -import org.jetbrains.kotlin.psi.KtBlockExpression -import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.psiUtil.isFirstStatement import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.scopes.LexicalScope @@ -57,13 +56,7 @@ object ContractNotAllowedCallChecker : CallChecker { if (functionDescriptor?.isOverridable == true) contractNotAllowed("Contracts are not allowed for open functions") - var parent = callElement.parent - var element = callElement - if (parent is KtDotQualifiedExpression) { - element = parent - parent = parent.parent - } - if ((parent !is KtBlockExpression || parent.children.first { it is KtElement } != element) && inFunctionBodyBlock) { + if (!callElement.isFirstStatement() && inFunctionBodyBlock) { contractNotAllowed("Contract should be the first statement") } diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index 2800de007fc..e82c9d1bcbd 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -315,6 +315,16 @@ fun KtQualifiedExpression.isContractDescriptionCallPsiCheck(): Boolean { return receiverExpression.text == "kotlin.contracts" && expression.isContractDescriptionCallPsiCheck() } +fun KtElement.isFirstStatement(): Boolean { + var parent = parent + var element = this + if (parent is KtDotQualifiedExpression) { + element = parent + parent = parent.parent + } + return parent is KtBlockExpression && parent.children.first { it is KtElement } == element +} + // ----------- Other ----------------------------------------------------------------------------------------------------------------------- diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.kt new file mode 100644 index 00000000000..e6e69415072 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.kt @@ -0,0 +1,25 @@ +// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts +// !LANGUAGE: +UseReturnsEffect +// Issue: KT-26386 + +import kotlin.contracts.* + +fun case_1(): Boolean { + contract { returns(null) implies case_1() } + return true +} + +fun case_2(): Boolean { + contract { returns(null) implies case_3() } + return true +} + +fun case_3(): Boolean { + contract { returns(null) implies case_2() } + return true +} + +fun case_4(): Boolean { + kotlin.contracts.contract { returns(null) implies case_1() } + return true +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.txt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.txt new file mode 100644 index 00000000000..2190c587b85 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.txt @@ -0,0 +1,6 @@ +package + +public fun case_1(): kotlin.Boolean +public fun case_2(): kotlin.Boolean +public fun case_3(): kotlin.Boolean +public fun case_4(): kotlin.Boolean diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.kt new file mode 100644 index 00000000000..1b57f6a6d0f --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.kt @@ -0,0 +1,39 @@ +// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts +// !LANGUAGE: +UseReturnsEffect +// Issue: KT-26386 + +fun myRun(block: () -> Unit) { + block() +} + +fun contract(block: () -> Unit) { + block() +} + +fun case_1(): Boolean? { + contract { case_1() } + return null +} + +fun case_2(): Boolean? { + contract { case_3() } + return null +} + +fun case_3(): Boolean? { + contract { case_2() } + return null +} + +fun case4() { + contract { + myRun { + val s: String + run { + s = "hello" + } + s.length + } + } +} + diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.txt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.txt new file mode 100644 index 00000000000..1809737fabd --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.txt @@ -0,0 +1,8 @@ +package + +public fun case4(): kotlin.Unit +public fun case_1(): kotlin.Boolean? +public fun case_2(): kotlin.Boolean? +public fun case_3(): kotlin.Boolean? +public fun contract(/*0*/ block: () -> kotlin.Unit): kotlin.Unit +public fun myRun(/*0*/ block: () -> kotlin.Unit): kotlin.Unit diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/5.exceptions.compiler.txt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/5.exceptions.compiler.txt deleted file mode 100644 index cf7b7d4f2c8..00000000000 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/5.exceptions.compiler.txt +++ /dev/null @@ -1,2 +0,0 @@ -CASES: 4 -java.lang.StackOverflowError \ No newline at end of file diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/5.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/5.kt index 7717898c5e5..82ec0537c4a 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/5.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/common/neg/5.kt @@ -31,12 +31,10 @@ fun case_3(): Boolean { /* * TESTCASE NUMBER: 4 - * UNEXPECTED BEHAVIOUR * ISSUES: KT-26386 - * EXCEPTION: compiler */ fun case_4(): Boolean? { - contract { returns(null) implies case_4() } + contract { returns(null) implies case_4() } return null } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java index f6d679f18bd..ad861babfbc 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java @@ -1209,6 +1209,16 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/notFirstStatement.kt"); } + @TestMetadata("recursiveContract.kt") + public void testRecursiveContract() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.kt"); + } + + @TestMetadata("recursiveContractCustomContractFunction.kt") + public void testRecursiveContractCustomContractFunction() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.kt"); + } + @TestMetadata("referenceToProperty.kt") public void testReferenceToProperty() throws Exception { runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/referenceToProperty.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java index 3fac669c5f3..a37da7316b1 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java @@ -1209,6 +1209,16 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/notFirstStatement.kt"); } + @TestMetadata("recursiveContract.kt") + public void testRecursiveContract() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.kt"); + } + + @TestMetadata("recursiveContractCustomContractFunction.kt") + public void testRecursiveContractCustomContractFunction() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.kt"); + } + @TestMetadata("referenceToProperty.kt") public void testReferenceToProperty() throws Exception { runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/referenceToProperty.kt");