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:
Dmitry Savvinov
2018-08-20 13:43:37 +03:00
committed by Ilya Gorbunov
parent a6783176ea
commit 749fa049ca
11 changed files with 187 additions and 103 deletions
@@ -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()
}
@@ -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
}
@@ -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) {
@@ -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
@@ -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()) {
@@ -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)
@@ -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(
@@ -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();