Don't try parsing contract-like function not from kotlin package
Returning `null` from `doCheckContract` functions means that we have failed to parse contract function and should report an error, but if the called function isn't true contract, we shouldn't evaluate that code at all. #KT-27758 Fixed
This commit is contained in:
committed by
Dmitry Savvinov
parent
d82ca9ccd0
commit
bae3ff5211
+22
-8
@@ -34,29 +34,43 @@ import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
|||||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
|
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
|
||||||
|
|
||||||
class ContractParsingServices(val languageVersionSettings: LanguageVersionSettings) {
|
class ContractParsingServices(val languageVersionSettings: LanguageVersionSettings) {
|
||||||
|
/**
|
||||||
|
* ! IMPORTANT NOTICE !
|
||||||
|
*
|
||||||
|
* This function has very important non-obvious implicit contract:
|
||||||
|
* it *must* call [org.jetbrains.kotlin.contracts.description.LazyContractProvider.setContractDescription]
|
||||||
|
* if FunctionDescriptor had [LazyContractProvider] in the user data.
|
||||||
|
*
|
||||||
|
* Otherwise, it may lead to inconsistent resolve state and failed assertions
|
||||||
|
*/
|
||||||
fun checkContractAndRecordIfPresent(expression: KtExpression, trace: BindingTrace, scope: LexicalScope, isFirstStatement: Boolean) {
|
fun checkContractAndRecordIfPresent(expression: KtExpression, trace: BindingTrace, scope: LexicalScope, isFirstStatement: Boolean) {
|
||||||
if (!expression.isContractDescriptionCallPsiCheck()) return // fastpath
|
// Fastpath. Note that it doesn't violates invariant described in KDoc, because 'isContractDescriptionCallPsiCheck'
|
||||||
|
// is a *necessary* (but not sufficient, actually) condition for presence of 'LazyContractProvider'
|
||||||
|
if (!expression.isContractDescriptionCallPsiCheck()) return
|
||||||
|
|
||||||
val collector = TraceBasedCollector(trace, expression)
|
val collector = TraceBasedCollector(trace, expression)
|
||||||
val callContext = ContractCallContext(expression, isFirstStatement, scope, trace.bindingContext)
|
val callContext = ContractCallContext(expression, isFirstStatement, scope, trace.bindingContext)
|
||||||
|
val contractProviderIfAny = (scope.ownerDescriptor as? FunctionDescriptor)?.getUserData(ContractProviderKey)
|
||||||
|
|
||||||
|
if (!callContext.isContractDescriptionCallPreciseCheck()) {
|
||||||
|
contractProviderIfAny?.setContractDescription(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val parsedContract = doCheckContract(collector, callContext)
|
val parsedContract = doCheckContract(collector, callContext)
|
||||||
|
|
||||||
collector.flushDiagnostics(parsingFailed = parsedContract == null)
|
collector.flushDiagnostics(parsingFailed = parsedContract == null)
|
||||||
|
|
||||||
val contractProviderIfAny = (scope.ownerDescriptor as? FunctionDescriptor)?.getUserData(ContractProviderKey)
|
|
||||||
|
|
||||||
if (collector.hasErrors())
|
if (collector.hasErrors())
|
||||||
contractProviderIfAny?.setContractDescription(null)
|
contractProviderIfAny?.setContractDescription(null)
|
||||||
else
|
else
|
||||||
contractProviderIfAny?.setContractDescription(parsedContract)
|
contractProviderIfAny?.setContractDescription(parsedContract)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ContractCallContext.isContractDescriptionCallPreciseCheck(): Boolean =
|
||||||
|
contractCallExpression.isContractDescriptionCallPreciseCheck(bindingContext)
|
||||||
|
|
||||||
private fun doCheckContract(collector: ContractParsingDiagnosticsCollector, callContext: ContractCallContext): ContractDescription? {
|
private fun doCheckContract(collector: ContractParsingDiagnosticsCollector, callContext: ContractCallContext): ContractDescription? {
|
||||||
val expression = callContext.contractCallExpression
|
|
||||||
val bindingContext = callContext.bindingContext
|
|
||||||
|
|
||||||
if (!expression.isContractDescriptionCallPreciseCheck(bindingContext)) return null
|
|
||||||
|
|
||||||
checkFeatureEnabled(collector)
|
checkFeatureEnabled(collector)
|
||||||
checkContractAllowedHere(collector, callContext)
|
checkContractAllowedHere(collector, callContext)
|
||||||
|
|
||||||
|
|||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||||
|
|
||||||
|
package my
|
||||||
|
|
||||||
|
// Accepts block, can't be distinguished from kotlin.contract without resolve
|
||||||
|
fun contract(block: () -> Unit) {}
|
||||||
|
|
||||||
|
// Accepts int, potentially can be distinguished from kotlin.contract by PSI,
|
||||||
|
// but as of Kotlin 1.3.0, we don't do that
|
||||||
|
fun contract(i: Int) {}
|
||||||
|
|
||||||
|
fun doStuff() {}
|
||||||
|
|
||||||
|
class SomeClass {
|
||||||
|
|
||||||
|
fun contract() {}
|
||||||
|
|
||||||
|
fun callMemberContractWithThis() {
|
||||||
|
this.contract()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callMemberContractWithoutThis() {
|
||||||
|
contract()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callTopLevelSamePsiInMember() {
|
||||||
|
contract { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callTopLevelSamePsi() {
|
||||||
|
contract { }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callTopLevelDifferentPsi() {
|
||||||
|
contract(42)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callTopLevelSamePsiNotFirstStatement() {
|
||||||
|
doStuff()
|
||||||
|
contract { }
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
package my {
|
||||||
|
public fun callTopLevelDifferentPsi(): kotlin.Unit
|
||||||
|
public fun callTopLevelSamePsi(): kotlin.Unit
|
||||||
|
public fun callTopLevelSamePsiNotFirstStatement(): kotlin.Unit
|
||||||
|
public fun contract(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
|
||||||
|
public fun contract(/*0*/ i: kotlin.Int): kotlin.Unit
|
||||||
|
public fun doStuff(): kotlin.Unit
|
||||||
|
|
||||||
|
public final class SomeClass {
|
||||||
|
public constructor SomeClass()
|
||||||
|
public final fun callMemberContractWithThis(): kotlin.Unit
|
||||||
|
public final fun callMemberContractWithoutThis(): kotlin.Unit
|
||||||
|
public final fun callTopLevelSamePsiInMember(): kotlin.Unit
|
||||||
|
public final fun contract(): kotlin.Unit
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -1112,6 +1112,11 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("callUsualContractFunction.kt")
|
||||||
|
public void testCallUsualContractFunction() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("useBeforeDeclaration.kt")
|
@TestMetadata("useBeforeDeclaration.kt")
|
||||||
public void testUseBeforeDeclaration() throws Exception {
|
public void testUseBeforeDeclaration() throws Exception {
|
||||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/useBeforeDeclaration.kt");
|
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/useBeforeDeclaration.kt");
|
||||||
|
|||||||
compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
Generated
+5
@@ -1112,6 +1112,11 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("callUsualContractFunction.kt")
|
||||||
|
public void testCallUsualContractFunction() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("useBeforeDeclaration.kt")
|
@TestMetadata("useBeforeDeclaration.kt")
|
||||||
public void testUseBeforeDeclaration() throws Exception {
|
public void testUseBeforeDeclaration() throws Exception {
|
||||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/useBeforeDeclaration.kt");
|
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/useBeforeDeclaration.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user