Refactor diagnostic reporting of effects parsing
- Introduce ContractParsingDiagnosticsCollector It allows us to make sure that we return 'null'-contract iff there's some error. Note that it differs from previous behavior in two major points: a) if contract was unparsed, but no error were reported for some reason, then generic error will be reported (previously we were swallowing incorrect contract silently) b) if there were some errors during parsing, return only 'null' (previously, we could return some contract. It was unusable due to errors in code, but we want to be extra-safe here) - Introduce ContractCallContext to not pass a bunch of random arguments around - Make message of Error.CONTRACT_NOT_ALLOWED more flexible
This commit is contained in:
committed by
Ilya Gorbunov
parent
a6783176ea
commit
749fa049ca
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.contracts.parsing
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.config.LanguageFeature
|
||||||
|
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||||
|
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
|
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||||
|
|
||||||
|
interface ContractParsingDiagnosticsCollector {
|
||||||
|
fun unsupportedFeature(languageVersionSettings: LanguageVersionSettings)
|
||||||
|
fun contractNotAllowed(message: String)
|
||||||
|
fun badDescription(message: String, reportOn: KtElement)
|
||||||
|
|
||||||
|
fun flushDiagnostics(parsingFailed: Boolean)
|
||||||
|
fun hasErrors(): Boolean
|
||||||
|
|
||||||
|
|
||||||
|
object EMPTY : ContractParsingDiagnosticsCollector {
|
||||||
|
override fun contractNotAllowed(message: String) {}
|
||||||
|
override fun badDescription(message: String, reportOn: KtElement) {}
|
||||||
|
override fun unsupportedFeature(languageVersionSettings: LanguageVersionSettings) { }
|
||||||
|
|
||||||
|
override fun flushDiagnostics(parsingFailed: Boolean) {}
|
||||||
|
|
||||||
|
override fun hasErrors(): Boolean = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TraceBasedCollector(private val bindingTrace: BindingTrace, private val mainCall: KtExpression) : ContractParsingDiagnosticsCollector {
|
||||||
|
private val diagnostics: MutableList<Diagnostic> = mutableListOf()
|
||||||
|
|
||||||
|
override fun contractNotAllowed(message: String) {
|
||||||
|
diagnostics += Errors.CONTRACT_NOT_ALLOWED.on((mainCall as? KtCallExpression)?.calleeExpression ?: mainCall, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun badDescription(message: String, reportOn: KtElement) {
|
||||||
|
diagnostics += Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(reportOn, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unsupportedFeature(languageVersionSettings: LanguageVersionSettings) {
|
||||||
|
diagnostics += Errors.UNSUPPORTED_FEATURE.on(mainCall, LanguageFeature.AllowContractsForCustomFunctions to languageVersionSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flushDiagnostics(parsingFailed: Boolean) {
|
||||||
|
if (parsingFailed && diagnostics.isEmpty()) {
|
||||||
|
diagnostics += Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(mainCall, "Error in contract description")
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics.forEach { bindingTrace.report(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun hasErrors(): Boolean = diagnostics.isNotEmpty()
|
||||||
|
}
|
||||||
+59
-29
@@ -19,10 +19,11 @@ package org.jetbrains.kotlin.contracts.parsing
|
|||||||
import org.jetbrains.kotlin.config.AnalysisFlag
|
import org.jetbrains.kotlin.config.AnalysisFlag
|
||||||
import org.jetbrains.kotlin.config.LanguageFeature
|
import org.jetbrains.kotlin.config.LanguageFeature
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||||
|
import org.jetbrains.kotlin.contracts.description.ContractDescription
|
||||||
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
|
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isContractDescriptionCallPsiCheck
|
import org.jetbrains.kotlin.psi.psiUtil.isContractDescriptionCallPsiCheck
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||||
@@ -32,41 +33,70 @@ import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
|
|||||||
|
|
||||||
class ContractParsingServices(val languageVersionSettings: LanguageVersionSettings) {
|
class ContractParsingServices(val languageVersionSettings: LanguageVersionSettings) {
|
||||||
fun checkContractAndRecordIfPresent(expression: KtExpression, trace: BindingTrace, scope: LexicalScope, isFirstStatement: Boolean) {
|
fun checkContractAndRecordIfPresent(expression: KtExpression, trace: BindingTrace, scope: LexicalScope, isFirstStatement: Boolean) {
|
||||||
val ownerDescriptor = scope.ownerDescriptor
|
if (!expression.isContractDescriptionCallPsiCheck()) return // fastpath
|
||||||
if (!expression.isContractDescriptionCallPsiCheck() || ownerDescriptor !is FunctionDescriptor) return
|
|
||||||
val contractProvider = ownerDescriptor.getUserData(ContractProviderKey)
|
|
||||||
|
|
||||||
|
val collector = TraceBasedCollector(trace, expression)
|
||||||
|
val callContext = ContractCallContext(expression, isFirstStatement, scope, trace.bindingContext)
|
||||||
|
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 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)
|
||||||
|
|
||||||
|
return if (!collector.hasErrors())
|
||||||
|
PsiContractParserDispatcher(collector, callContext).parseContract()
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkFeatureEnabled(collector: ContractParsingDiagnosticsCollector) {
|
||||||
val isFeatureTurnedOn = languageVersionSettings.supportsFeature(LanguageFeature.AllowContractsForCustomFunctions) ||
|
val isFeatureTurnedOn = languageVersionSettings.supportsFeature(LanguageFeature.AllowContractsForCustomFunctions) ||
|
||||||
// This condition is here for technical purposes of compiling 1.2-runtime with contracts
|
// This condition is here for technical purposes of compiling 1.2-runtime with contracts
|
||||||
languageVersionSettings.getFlag(AnalysisFlag.Flags.allowKotlinPackage)
|
languageVersionSettings.getFlag(AnalysisFlag.Flags.allowKotlinPackage)
|
||||||
|
if (!isFeatureTurnedOn) {
|
||||||
val contractDescriptor = when {
|
collector.unsupportedFeature(languageVersionSettings)
|
||||||
!expression.isContractDescriptionCallPreciseCheck(trace.bindingContext) -> null
|
|
||||||
|
|
||||||
!isFeatureTurnedOn -> {
|
|
||||||
trace.report(
|
|
||||||
Errors.UNSUPPORTED_FEATURE.on(
|
|
||||||
expression,
|
|
||||||
LanguageFeature.AllowContractsForCustomFunctions to languageVersionSettings
|
|
||||||
)
|
|
||||||
)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
!isContractAllowedHere(scope) || !isFirstStatement -> {
|
|
||||||
trace.report(Errors.CONTRACT_NOT_ALLOWED.on(expression))
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> PsiContractParserDispatcher(trace).parseContract(expression, ownerDescriptor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contractProvider?.setContractDescription(contractDescriptor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isContractAllowedHere(scope: LexicalScope): Boolean =
|
private fun checkContractAllowedHere(collector: ContractParsingDiagnosticsCollector, callContext: ContractCallContext) {
|
||||||
scope.kind == LexicalScopeKind.CODE_BLOCK && (scope.parent as? LexicalScope)?.kind == LexicalScopeKind.FUNCTION_INNER_SCOPE
|
val functionDescriptor = callContext.ownerDescriptor as? FunctionDescriptor
|
||||||
|
val scope = callContext.scope
|
||||||
|
|
||||||
|
if (!callContext.isFirstStatement)
|
||||||
|
collector.contractNotAllowed("Contract should be the first statement")
|
||||||
|
|
||||||
|
if (functionDescriptor == null)
|
||||||
|
collector.contractNotAllowed("Contracts are allowed only for functions")
|
||||||
|
|
||||||
|
if (scope.kind != LexicalScopeKind.CODE_BLOCK || (scope.parent as? LexicalScope)?.kind != LexicalScopeKind.FUNCTION_INNER_SCOPE)
|
||||||
|
collector.contractNotAllowed("Contracts are allowed only for top-level functions")
|
||||||
|
}
|
||||||
|
|
||||||
private fun KtExpression.isContractDescriptionCallPreciseCheck(context: BindingContext): Boolean =
|
private fun KtExpression.isContractDescriptionCallPreciseCheck(context: BindingContext): Boolean =
|
||||||
getResolvedCall(context)?.resultingDescriptor?.isContractCallDescriptor() ?: false
|
getResolvedCall(context)?.resultingDescriptor?.isContractCallDescriptor() ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ContractCallContext(
|
||||||
|
val contractCallExpression: KtExpression,
|
||||||
|
val isFirstStatement: Boolean,
|
||||||
|
val scope: LexicalScope,
|
||||||
|
val bindingContext: BindingContext
|
||||||
|
) {
|
||||||
|
val ownerDescriptor: DeclarationDescriptor = scope.ownerDescriptor
|
||||||
|
val functionDescriptor: FunctionDescriptor = ownerDescriptor as FunctionDescriptor
|
||||||
|
}
|
||||||
+14
-12
@@ -27,18 +27,22 @@ import org.jetbrains.kotlin.resolve.BindingTrace
|
|||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||||
|
|
||||||
internal class PsiConditionParser(val trace: BindingTrace, val dispatcher: PsiContractParserDispatcher) :
|
internal class PsiConditionParser(
|
||||||
KtVisitor<BooleanExpression?, Unit>() {
|
private val collector: ContractParsingDiagnosticsCollector,
|
||||||
|
private val callContext: ContractCallContext,
|
||||||
|
private val dispatcher: PsiContractParserDispatcher
|
||||||
|
) : KtVisitor<BooleanExpression?, Unit>() {
|
||||||
|
|
||||||
override fun visitIsExpression(expression: KtIsExpression, data: Unit): BooleanExpression? {
|
override fun visitIsExpression(expression: KtIsExpression, data: Unit): BooleanExpression? {
|
||||||
val variable = dispatcher.parseVariable(expression.leftHandSide) ?: return null
|
val variable = dispatcher.parseVariable(expression.leftHandSide) ?: return null
|
||||||
val typeReference = expression.typeReference ?: return null
|
val typeReference = expression.typeReference ?: return null
|
||||||
val type = trace[BindingContext.TYPE, typeReference] ?: return null
|
val type = callContext.bindingContext[BindingContext.TYPE, typeReference] ?: return null
|
||||||
|
|
||||||
return IsInstancePredicate(variable, type, expression.isNegated)
|
return IsInstancePredicate(variable, type, expression.isNegated)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitKtElement(element: KtElement, data: Unit): BooleanExpression? {
|
override fun visitKtElement(element: KtElement, data: Unit): BooleanExpression? {
|
||||||
val resolvedCall = element.getResolvedCall(trace.bindingContext)
|
val resolvedCall = element.getResolvedCall(callContext.bindingContext)
|
||||||
val descriptor = resolvedCall?.resultingDescriptor ?: return null
|
val descriptor = resolvedCall?.resultingDescriptor ?: return null
|
||||||
|
|
||||||
// boolean variable
|
// boolean variable
|
||||||
@@ -63,12 +67,12 @@ internal class PsiConditionParser(val trace: BindingTrace, val dispatcher: PsiCo
|
|||||||
return IsNullPredicate(left, isNegated)
|
return IsNullPredicate(left, isNegated)
|
||||||
}
|
}
|
||||||
|
|
||||||
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(element, "only equality comparisons with 'null' allowed"))
|
collector.badDescription("only equality comparisons with 'null' allowed", element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(element, "unsupported construction"))
|
collector.badDescription("unsupported construction", element)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +84,7 @@ internal class PsiConditionParser(val trace: BindingTrace, val dispatcher: PsiCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitCallExpression(expression: KtCallExpression, data: Unit?): BooleanExpression? {
|
override fun visitCallExpression(expression: KtCallExpression, data: Unit?): BooleanExpression? {
|
||||||
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(expression, "call-expressions are not supported yet"))
|
collector.badDescription("call-expressions are not supported yet", expression)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,11 +106,9 @@ internal class PsiConditionParser(val trace: BindingTrace, val dispatcher: PsiCo
|
|||||||
if (expression.operationToken != KtTokens.EXCL) return super.visitUnaryExpression(expression, data)
|
if (expression.operationToken != KtTokens.EXCL) return super.visitUnaryExpression(expression, data)
|
||||||
val arg = expression.baseExpression?.accept(this, data) ?: return null
|
val arg = expression.baseExpression?.accept(this, data) ?: return null
|
||||||
if (arg !is ContractDescriptionValue) {
|
if (arg !is ContractDescriptionValue) {
|
||||||
trace.report(
|
collector.badDescription(
|
||||||
Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(
|
"negations in contract description can be applied only to variables/values",
|
||||||
expression.baseExpression!!,
|
expression.baseExpression!!
|
||||||
"negations in contract description can be applied only to variables/values"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return LogicalNot(arg)
|
return LogicalNot(arg)
|
||||||
|
|||||||
@@ -22,17 +22,18 @@ import org.jetbrains.kotlin.psi.KtConstantExpression
|
|||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
import org.jetbrains.kotlin.psi.KtVisitor
|
import org.jetbrains.kotlin.psi.KtVisitor
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
|
||||||
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
|
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
internal class PsiConstantParser(val trace: BindingTrace) : KtVisitor<ConstantReference?, Unit>() {
|
internal class PsiConstantParser(private val callContext: ContractCallContext) : KtVisitor<ConstantReference?, Unit>() {
|
||||||
override fun visitKtElement(element: KtElement, data: Unit?): ConstantReference? = null
|
override fun visitKtElement(element: KtElement, data: Unit?): ConstantReference? = null
|
||||||
|
|
||||||
override fun visitConstantExpression(expression: KtConstantExpression, data: Unit?): ConstantReference? {
|
override fun visitConstantExpression(expression: KtConstantExpression, data: Unit?): ConstantReference? {
|
||||||
val type: KotlinType = trace.getType(expression) ?: return null
|
val type: KotlinType = callContext.bindingContext.getType(expression) ?: return null
|
||||||
|
|
||||||
|
val compileTimeConstant: CompileTimeConstant<*> = callContext.bindingContext.get(BindingContext.COMPILE_TIME_VALUE, expression)
|
||||||
|
?: return null
|
||||||
|
|
||||||
val compileTimeConstant: CompileTimeConstant<*> = trace.get(BindingContext.COMPILE_TIME_VALUE, expression) ?: return null
|
|
||||||
val value: Any? = compileTimeConstant.getValue(type)
|
val value: Any? = compileTimeConstant.getValue(type)
|
||||||
|
|
||||||
return when (value) {
|
return when (value) {
|
||||||
|
|||||||
+19
-30
@@ -31,32 +31,30 @@ import org.jetbrains.kotlin.contracts.parsing.ContractsDslNames.RETURNS_NOT_NULL
|
|||||||
import org.jetbrains.kotlin.contracts.parsing.effects.PsiCallsEffectParser
|
import org.jetbrains.kotlin.contracts.parsing.effects.PsiCallsEffectParser
|
||||||
import org.jetbrains.kotlin.contracts.parsing.effects.PsiConditionalEffectParser
|
import org.jetbrains.kotlin.contracts.parsing.effects.PsiConditionalEffectParser
|
||||||
import org.jetbrains.kotlin.contracts.parsing.effects.PsiReturnsEffectParser
|
import org.jetbrains.kotlin.contracts.parsing.effects.PsiReturnsEffectParser
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
|
||||||
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
|
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
|
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.psi.KtLambdaExpression
|
import org.jetbrains.kotlin.psi.KtLambdaExpression
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
|
||||||
|
|
||||||
internal class PsiContractParserDispatcher(val trace: BindingTrace) {
|
internal class PsiContractParserDispatcher(
|
||||||
private val conditionParser = PsiConditionParser(trace, this)
|
private val collector: ContractParsingDiagnosticsCollector,
|
||||||
private val constantParser = PsiConstantParser(trace)
|
private val callContext: ContractCallContext
|
||||||
|
) {
|
||||||
|
private val conditionParser = PsiConditionParser(collector, callContext, this)
|
||||||
|
private val constantParser = PsiConstantParser(callContext)
|
||||||
private val effectsParsers: Map<Name, PsiEffectParser> = mapOf(
|
private val effectsParsers: Map<Name, PsiEffectParser> = mapOf(
|
||||||
RETURNS_EFFECT to PsiReturnsEffectParser(trace, this),
|
RETURNS_EFFECT to PsiReturnsEffectParser(collector, callContext, this),
|
||||||
RETURNS_NOT_NULL_EFFECT to PsiReturnsEffectParser(trace, this),
|
RETURNS_NOT_NULL_EFFECT to PsiReturnsEffectParser(collector, callContext, this),
|
||||||
CALLS_IN_PLACE_EFFECT to PsiCallsEffectParser(trace, this),
|
CALLS_IN_PLACE_EFFECT to PsiCallsEffectParser(collector, callContext, this),
|
||||||
CONDITIONAL_EFFECT to PsiConditionalEffectParser(trace, this)
|
CONDITIONAL_EFFECT to PsiConditionalEffectParser(collector, callContext, this)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun parseContract(expression: KtExpression?, ownerDescriptor: FunctionDescriptor): ContractDescription? {
|
fun parseContract(): ContractDescription? {
|
||||||
if (expression == null) return null
|
|
||||||
|
|
||||||
// Must be non-null because of checks in 'checkContractAndRecordIfPresent', but actually is not, see EA-124365
|
// Must be non-null because of checks in 'checkContractAndRecordIfPresent', but actually is not, see EA-124365
|
||||||
val resolvedCall = expression.getResolvedCall(trace.bindingContext) ?: return null
|
val resolvedCall = callContext.contractCallExpression.getResolvedCall(callContext.bindingContext) ?: return null
|
||||||
|
|
||||||
val lambda = resolvedCall.firstArgumentAsExpressionOrNull() as? KtLambdaExpression ?: return null
|
val lambda = resolvedCall.firstArgumentAsExpressionOrNull() as? KtLambdaExpression ?: return null
|
||||||
|
|
||||||
@@ -64,17 +62,17 @@ internal class PsiContractParserDispatcher(val trace: BindingTrace) {
|
|||||||
|
|
||||||
if (effects.isEmpty()) return null
|
if (effects.isEmpty()) return null
|
||||||
|
|
||||||
return ContractDescription(effects, ownerDescriptor)
|
return ContractDescription(effects, callContext.functionDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseCondition(expression: KtExpression?): BooleanExpression? = expression?.accept(conditionParser, Unit)
|
fun parseCondition(expression: KtExpression?): BooleanExpression? = expression?.accept(conditionParser, Unit)
|
||||||
|
|
||||||
fun parseEffect(expression: KtExpression?): EffectDeclaration? {
|
fun parseEffect(expression: KtExpression?): EffectDeclaration? {
|
||||||
if (expression == null) return null
|
if (expression == null) return null
|
||||||
val returnType = expression.getType(trace.bindingContext) ?: return null
|
val returnType = expression.getType(callContext.bindingContext) ?: return null
|
||||||
val parser = effectsParsers[returnType.constructor.declarationDescriptor?.name]
|
val parser = effectsParsers[returnType.constructor.declarationDescriptor?.name]
|
||||||
if (parser == null) {
|
if (parser == null) {
|
||||||
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(expression, "Unrecognized effect"))
|
collector.badDescription("unrecognized effect", expression)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return parser.tryParseEffect(expression)
|
return parser.tryParseEffect(expression)
|
||||||
@@ -87,24 +85,15 @@ internal class PsiContractParserDispatcher(val trace: BindingTrace) {
|
|||||||
|
|
||||||
fun parseVariable(expression: KtExpression?): VariableReference? {
|
fun parseVariable(expression: KtExpression?): VariableReference? {
|
||||||
if (expression == null) return null
|
if (expression == null) return null
|
||||||
val descriptor = expression.getResolvedCall(trace.bindingContext)?.resultingDescriptor ?: return null
|
val descriptor = expression.getResolvedCall(callContext.bindingContext)?.resultingDescriptor ?: return null
|
||||||
if (descriptor !is ParameterDescriptor) {
|
if (descriptor !is ParameterDescriptor) {
|
||||||
trace.report(
|
collector.badDescription("only references to parameters are allowed in contract description", expression)
|
||||||
Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(
|
|
||||||
expression,
|
|
||||||
"only references to parameters are allowed in contract description"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descriptor is ReceiverParameterDescriptor && descriptor.type.constructor.declarationDescriptor?.isFromContractDsl() == true) {
|
if (descriptor is ReceiverParameterDescriptor && descriptor.type.constructor.declarationDescriptor?.isFromContractDsl() == true) {
|
||||||
trace.report(
|
collector.badDescription("only references to parameters are allowed. Did you miss label on <this>?", expression)
|
||||||
Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(
|
return null
|
||||||
expression,
|
|
||||||
"only references to parameters are allowed. Did you miss label on <this>?"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (KotlinBuiltIns.isBoolean(descriptor.type))
|
return if (KotlinBuiltIns.isBoolean(descriptor.type))
|
||||||
|
|||||||
@@ -24,5 +24,8 @@ internal interface PsiEffectParser {
|
|||||||
fun tryParseEffect(expression: KtExpression): EffectDeclaration?
|
fun tryParseEffect(expression: KtExpression): EffectDeclaration?
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class AbstractPsiEffectParser(val trace: BindingTrace, val contractParserDispatcher: PsiContractParserDispatcher) :
|
internal abstract class AbstractPsiEffectParser(
|
||||||
PsiEffectParser
|
val collector: ContractParsingDiagnosticsCollector,
|
||||||
|
val callContext: ContractCallContext,
|
||||||
|
val contractParserDispatcher: PsiContractParserDispatcher
|
||||||
|
) : PsiEffectParser
|
||||||
|
|||||||
+8
-7
@@ -21,7 +21,7 @@ import org.jetbrains.kotlin.contracts.description.EffectDeclaration
|
|||||||
import org.jetbrains.kotlin.contracts.description.InvocationKind
|
import org.jetbrains.kotlin.contracts.description.InvocationKind
|
||||||
import org.jetbrains.kotlin.contracts.parsing.*
|
import org.jetbrains.kotlin.contracts.parsing.*
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
||||||
@@ -29,12 +29,13 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
|||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
|
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
|
||||||
|
|
||||||
internal class PsiCallsEffectParser(
|
internal class PsiCallsEffectParser(
|
||||||
trace: BindingTrace,
|
collector: ContractParsingDiagnosticsCollector,
|
||||||
|
callContext: ContractCallContext,
|
||||||
contractParserDispatcher: PsiContractParserDispatcher
|
contractParserDispatcher: PsiContractParserDispatcher
|
||||||
) : AbstractPsiEffectParser(trace, contractParserDispatcher) {
|
) : AbstractPsiEffectParser(collector, callContext, contractParserDispatcher) {
|
||||||
|
|
||||||
override fun tryParseEffect(expression: KtExpression): EffectDeclaration? {
|
override fun tryParseEffect(expression: KtExpression): EffectDeclaration? {
|
||||||
val resolvedCall = expression.getResolvedCall(trace.bindingContext) ?: return null
|
val resolvedCall = expression.getResolvedCall(callContext.bindingContext) ?: return null
|
||||||
val descriptor = resolvedCall.resultingDescriptor
|
val descriptor = resolvedCall.resultingDescriptor
|
||||||
|
|
||||||
if (!descriptor.isCallsInPlaceEffectDescriptor()) return null
|
if (!descriptor.isCallsInPlaceEffectDescriptor()) return null
|
||||||
@@ -45,15 +46,15 @@ internal class PsiCallsEffectParser(
|
|||||||
|
|
||||||
val kind = when (kindArgument) {
|
val kind = when (kindArgument) {
|
||||||
is DefaultValueArgument -> InvocationKind.UNKNOWN
|
is DefaultValueArgument -> InvocationKind.UNKNOWN
|
||||||
is ExpressionValueArgument -> kindArgument.valueArgument?.getArgumentExpression()?.toInvocationKind(trace) ?: return null
|
is ExpressionValueArgument -> kindArgument.valueArgument?.getArgumentExpression()?.toInvocationKind(callContext.bindingContext) ?: return null
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return CallsEffectDeclaration(lambda, kind)
|
return CallsEffectDeclaration(lambda, kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtExpression.toInvocationKind(trace: BindingTrace): InvocationKind? {
|
private fun KtExpression.toInvocationKind(bindingContext: BindingContext): InvocationKind? {
|
||||||
val descriptor = this.getResolvedCall(trace.bindingContext)?.resultingDescriptor ?: return null
|
val descriptor = this.getResolvedCall(bindingContext)?.resultingDescriptor ?: return null
|
||||||
if (!descriptor.parents.first().isInvocationKindEnum()) return null
|
if (!descriptor.parents.first().isInvocationKindEnum()) return null
|
||||||
|
|
||||||
return when (descriptor.fqNameSafe.shortName()) {
|
return when (descriptor.fqNameSafe.shortName()) {
|
||||||
|
|||||||
+5
-9
@@ -18,23 +18,19 @@ package org.jetbrains.kotlin.contracts.parsing.effects
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.contracts.description.ConditionalEffectDeclaration
|
import org.jetbrains.kotlin.contracts.description.ConditionalEffectDeclaration
|
||||||
import org.jetbrains.kotlin.contracts.description.EffectDeclaration
|
import org.jetbrains.kotlin.contracts.description.EffectDeclaration
|
||||||
import org.jetbrains.kotlin.contracts.parsing.AbstractPsiEffectParser
|
import org.jetbrains.kotlin.contracts.parsing.*
|
||||||
import org.jetbrains.kotlin.contracts.parsing.PsiContractParserDispatcher
|
|
||||||
import org.jetbrains.kotlin.contracts.parsing.firstArgumentAsExpressionOrNull
|
|
||||||
import org.jetbrains.kotlin.contracts.parsing.isImpliesCallDescriptor
|
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||||
|
|
||||||
internal class PsiConditionalEffectParser(
|
internal class PsiConditionalEffectParser(
|
||||||
trace: BindingTrace,
|
collector: ContractParsingDiagnosticsCollector,
|
||||||
|
callContext: ContractCallContext,
|
||||||
dispatcher: PsiContractParserDispatcher
|
dispatcher: PsiContractParserDispatcher
|
||||||
) : AbstractPsiEffectParser(trace, dispatcher) {
|
) : AbstractPsiEffectParser(collector, callContext, dispatcher) {
|
||||||
override fun tryParseEffect(expression: KtExpression): EffectDeclaration? {
|
override fun tryParseEffect(expression: KtExpression): EffectDeclaration? {
|
||||||
val resolvedCall = expression.getResolvedCall(trace.bindingContext) ?: return null
|
val resolvedCall = expression.getResolvedCall(callContext.bindingContext) ?: return null
|
||||||
|
|
||||||
if (!resolvedCall.resultingDescriptor.isImpliesCallDescriptor()) return null
|
if (!resolvedCall.resultingDescriptor.isImpliesCallDescriptor()) return null
|
||||||
|
|
||||||
val effect = contractParserDispatcher.parseEffect(resolvedCall.dispatchReceiver.safeAs<ExpressionReceiver>()?.expression)
|
val effect = contractParserDispatcher.parseEffect(resolvedCall.dispatchReceiver.safeAs<ExpressionReceiver>()?.expression)
|
||||||
|
|||||||
+7
-8
@@ -26,11 +26,12 @@ import org.jetbrains.kotlin.resolve.BindingTrace
|
|||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
|
||||||
internal class PsiReturnsEffectParser(
|
internal class PsiReturnsEffectParser(
|
||||||
trace: BindingTrace,
|
collector: ContractParsingDiagnosticsCollector,
|
||||||
|
callContext: ContractCallContext,
|
||||||
contractParserDispatcher: PsiContractParserDispatcher
|
contractParserDispatcher: PsiContractParserDispatcher
|
||||||
) : AbstractPsiEffectParser(trace, contractParserDispatcher) {
|
) : AbstractPsiEffectParser(collector, callContext, contractParserDispatcher) {
|
||||||
override fun tryParseEffect(expression: KtExpression): EffectDeclaration? {
|
override fun tryParseEffect(expression: KtExpression): EffectDeclaration? {
|
||||||
val resolvedCall = expression.getResolvedCall(trace.bindingContext) ?: return null
|
val resolvedCall = expression.getResolvedCall(callContext.bindingContext) ?: return null
|
||||||
val descriptor = resolvedCall.resultingDescriptor
|
val descriptor = resolvedCall.resultingDescriptor
|
||||||
|
|
||||||
if (descriptor.isReturnsNotNullDescriptor())
|
if (descriptor.isReturnsNotNullDescriptor())
|
||||||
@@ -45,11 +46,9 @@ internal class PsiReturnsEffectParser(
|
|||||||
// Note that we distinguish absence of an argument and unparsed argument
|
// Note that we distinguish absence of an argument and unparsed argument
|
||||||
val constant = contractParserDispatcher.parseConstant(argumentExpression)
|
val constant = contractParserDispatcher.parseConstant(argumentExpression)
|
||||||
if (constant == null) {
|
if (constant == null) {
|
||||||
trace.report(
|
collector.badDescription(
|
||||||
Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(
|
"only true/false/null constants in Returns-effect are currently supported",
|
||||||
argumentExpression,
|
argumentExpression
|
||||||
"only true/false/null constants in Returns-effect are currently supported"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1049,7 +1049,7 @@ public interface Errors {
|
|||||||
|
|
||||||
// Function contracts
|
// Function contracts
|
||||||
DiagnosticFactory1<KtElement, String> ERROR_IN_CONTRACT_DESCRIPTION = DiagnosticFactory1.create(ERROR);
|
DiagnosticFactory1<KtElement, String> ERROR_IN_CONTRACT_DESCRIPTION = DiagnosticFactory1.create(ERROR);
|
||||||
DiagnosticFactory0<KtElement> CONTRACT_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
|
DiagnosticFactory1<KtElement, String> CONTRACT_NOT_ALLOWED = DiagnosticFactory1.create(ERROR);
|
||||||
|
|
||||||
// Error sets
|
// Error sets
|
||||||
ImmutableSet<? extends DiagnosticFactory<?>> UNRESOLVED_REFERENCE_DIAGNOSTICS = ImmutableSet.of(
|
ImmutableSet<? extends DiagnosticFactory<?>> UNRESOLVED_REFERENCE_DIAGNOSTICS = ImmutableSet.of(
|
||||||
|
|||||||
+1
-1
@@ -941,7 +941,7 @@ public class DefaultErrorMessages {
|
|||||||
MAP.put(PLUGIN_INFO, "{0}", (d, c) -> d.getText());
|
MAP.put(PLUGIN_INFO, "{0}", (d, c) -> d.getText());
|
||||||
|
|
||||||
MAP.put(ERROR_IN_CONTRACT_DESCRIPTION, "Error in contract description: {0}", TO_STRING);
|
MAP.put(ERROR_IN_CONTRACT_DESCRIPTION, "Error in contract description: {0}", TO_STRING);
|
||||||
MAP.put(CONTRACT_NOT_ALLOWED, "Contract is not allowed here");
|
MAP.put(CONTRACT_NOT_ALLOWED, "{0}", TO_STRING);
|
||||||
|
|
||||||
MAP.setImmutable();
|
MAP.setImmutable();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user