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
|
||||
|
||||
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) {
|
||||
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 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)
|
||||
|
||||
collector.flushDiagnostics(parsingFailed = parsedContract == null)
|
||||
|
||||
val contractProviderIfAny = (scope.ownerDescriptor as? FunctionDescriptor)?.getUserData(ContractProviderKey)
|
||||
|
||||
if (collector.hasErrors())
|
||||
contractProviderIfAny?.setContractDescription(null)
|
||||
else
|
||||
contractProviderIfAny?.setContractDescription(parsedContract)
|
||||
}
|
||||
|
||||
private fun ContractCallContext.isContractDescriptionCallPreciseCheck(): Boolean =
|
||||
contractCallExpression.isContractDescriptionCallPreciseCheck(bindingContext)
|
||||
|
||||
private fun doCheckContract(collector: ContractParsingDiagnosticsCollector, callContext: ContractCallContext): ContractDescription? {
|
||||
val expression = callContext.contractCallExpression
|
||||
val bindingContext = callContext.bindingContext
|
||||
|
||||
if (!expression.isContractDescriptionCallPreciseCheck(bindingContext)) return null
|
||||
|
||||
checkFeatureEnabled(collector)
|
||||
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);
|
||||
}
|
||||
|
||||
@TestMetadata("callUsualContractFunction.kt")
|
||||
public void testCallUsualContractFunction() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("useBeforeDeclaration.kt")
|
||||
public void testUseBeforeDeclaration() throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
@TestMetadata("callUsualContractFunction.kt")
|
||||
public void testCallUsualContractFunction() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("useBeforeDeclaration.kt")
|
||||
public void testUseBeforeDeclaration() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/useBeforeDeclaration.kt");
|
||||
|
||||
Reference in New Issue
Block a user