Fix recursion in contract declaration analysis. KT-26386 Fixed
Issue fixed only in old inference
This commit is contained in:
@@ -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<KtExpression>().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)
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ public interface BindingContext {
|
||||
WritableSlice<KtExpression, KotlinType> EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice<>(DO_NOTHING);
|
||||
WritableSlice<KtElement, Computation> EXPRESSION_EFFECTS = Slices.createSimpleSlice();
|
||||
WritableSlice<KtElement, Boolean> CONTRACT_NOT_ALLOWED = Slices.createSimpleSlice();
|
||||
WritableSlice<KtElement, Boolean> IS_CONTRACT_DECLARATION_BLOCK = Slices.createSimpleSlice();
|
||||
WritableSlice<KtFunction, KotlinType> EXPECTED_RETURN_TYPE = new BasicWritableSlice<>(DO_NOTHING);
|
||||
WritableSlice<KtExpression, DataFlowInfo> DATAFLOW_INFO_AFTER_CONDITION = Slices.createSimpleSlice();
|
||||
WritableSlice<VariableDescriptor, DataFlowValue> BOUND_INITIALIZER_VALUE = Slices.createSimpleSlice();
|
||||
|
||||
@@ -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<KtExpression>()?.let { callExpression ->
|
||||
if (callExpression.isFirstStatement() && callExpression.isContractDescriptionCallPsiCheck()) {
|
||||
if (resolvedCall?.resultingDescriptor?.isContractCallDescriptor() != true) {
|
||||
context.scope.ownerDescriptor
|
||||
.safeAs<FunctionDescriptor>()
|
||||
?.getUserData(ContractProviderKey)
|
||||
?.safeAs<LazyContractProvider>()
|
||||
?.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)
|
||||
|
||||
+2
-9
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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 -----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
+25
@@ -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 <!ERROR_IN_CONTRACT_DESCRIPTION!>case_1()<!> }
|
||||
return true
|
||||
}
|
||||
|
||||
fun case_2(): Boolean {
|
||||
contract { returns(null) implies <!ERROR_IN_CONTRACT_DESCRIPTION!>case_3()<!> }
|
||||
return true
|
||||
}
|
||||
|
||||
fun case_3(): Boolean {
|
||||
contract { returns(null) implies <!ERROR_IN_CONTRACT_DESCRIPTION!>case_2()<!> }
|
||||
return true
|
||||
}
|
||||
|
||||
fun case_4(): Boolean {
|
||||
kotlin.contracts.contract { returns(null) implies <!ERROR_IN_CONTRACT_DESCRIPTION!>case_1()<!> }
|
||||
return true
|
||||
}
|
||||
+6
@@ -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
|
||||
+39
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -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
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
CASES: 4
|
||||
java.lang.StackOverflowError
|
||||
+1
-3
@@ -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 <!ERROR_IN_CONTRACT_DESCRIPTION, TYPE_MISMATCH!>case_4()<!> }
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -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");
|
||||
|
||||
compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
Generated
+10
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user