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:
Mikhail Zarechenskiy
2018-10-23 11:23:06 +03:00
committed by Dmitry Savvinov
parent d82ca9ccd0
commit bae3ff5211
5 changed files with 95 additions and 8 deletions
@@ -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)
@@ -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 { }
}
@@ -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
}
}
@@ -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");
@@ -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");