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.LanguageFeature
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.contracts.description.ContractDescription
|
||||
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isContractDescriptionCallPsiCheck
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
@@ -32,41 +33,70 @@ import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
|
||||
|
||||
class ContractParsingServices(val languageVersionSettings: LanguageVersionSettings) {
|
||||
fun checkContractAndRecordIfPresent(expression: KtExpression, trace: BindingTrace, scope: LexicalScope, isFirstStatement: Boolean) {
|
||||
val ownerDescriptor = scope.ownerDescriptor
|
||||
if (!expression.isContractDescriptionCallPsiCheck() || ownerDescriptor !is FunctionDescriptor) return
|
||||
val contractProvider = ownerDescriptor.getUserData(ContractProviderKey)
|
||||
if (!expression.isContractDescriptionCallPsiCheck()) return // fastpath
|
||||
|
||||
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) ||
|
||||
// This condition is here for technical purposes of compiling 1.2-runtime with contracts
|
||||
languageVersionSettings.getFlag(AnalysisFlag.Flags.allowKotlinPackage)
|
||||
|
||||
val contractDescriptor = when {
|
||||
!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)
|
||||
if (!isFeatureTurnedOn) {
|
||||
collector.unsupportedFeature(languageVersionSettings)
|
||||
}
|
||||
|
||||
contractProvider?.setContractDescription(contractDescriptor)
|
||||
}
|
||||
|
||||
private fun isContractAllowedHere(scope: LexicalScope): Boolean =
|
||||
scope.kind == LexicalScopeKind.CODE_BLOCK && (scope.parent as? LexicalScope)?.kind == LexicalScopeKind.FUNCTION_INNER_SCOPE
|
||||
private fun checkContractAllowedHere(collector: ContractParsingDiagnosticsCollector, callContext: ContractCallContext) {
|
||||
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 =
|
||||
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.scopes.receivers.ExpressionReceiver
|
||||
|
||||
internal class PsiConditionParser(val trace: BindingTrace, val dispatcher: PsiContractParserDispatcher) :
|
||||
KtVisitor<BooleanExpression?, Unit>() {
|
||||
internal class PsiConditionParser(
|
||||
private val collector: ContractParsingDiagnosticsCollector,
|
||||
private val callContext: ContractCallContext,
|
||||
private val dispatcher: PsiContractParserDispatcher
|
||||
) : KtVisitor<BooleanExpression?, Unit>() {
|
||||
|
||||
override fun visitIsExpression(expression: KtIsExpression, data: Unit): BooleanExpression? {
|
||||
val variable = dispatcher.parseVariable(expression.leftHandSide) ?: 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)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// boolean variable
|
||||
@@ -63,12 +67,12 @@ internal class PsiConditionParser(val trace: BindingTrace, val dispatcher: PsiCo
|
||||
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
|
||||
}
|
||||
|
||||
else -> {
|
||||
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(element, "unsupported construction"))
|
||||
collector.badDescription("unsupported construction", element)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -80,7 +84,7 @@ internal class PsiConditionParser(val trace: BindingTrace, val dispatcher: PsiCo
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -102,11 +106,9 @@ internal class PsiConditionParser(val trace: BindingTrace, val dispatcher: PsiCo
|
||||
if (expression.operationToken != KtTokens.EXCL) return super.visitUnaryExpression(expression, data)
|
||||
val arg = expression.baseExpression?.accept(this, data) ?: return null
|
||||
if (arg !is ContractDescriptionValue) {
|
||||
trace.report(
|
||||
Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(
|
||||
expression.baseExpression!!,
|
||||
"negations in contract description can be applied only to variables/values"
|
||||
)
|
||||
collector.badDescription(
|
||||
"negations in contract description can be applied only to variables/values",
|
||||
expression.baseExpression!!
|
||||
)
|
||||
}
|
||||
return LogicalNot(arg)
|
||||
|
||||
@@ -22,17 +22,18 @@ import org.jetbrains.kotlin.psi.KtConstantExpression
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtVisitor
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
|
||||
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 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)
|
||||
|
||||
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.PsiConditionalEffectParser
|
||||
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.ReceiverParameterDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
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.getType
|
||||
|
||||
internal class PsiContractParserDispatcher(val trace: BindingTrace) {
|
||||
private val conditionParser = PsiConditionParser(trace, this)
|
||||
private val constantParser = PsiConstantParser(trace)
|
||||
internal class PsiContractParserDispatcher(
|
||||
private val collector: ContractParsingDiagnosticsCollector,
|
||||
private val callContext: ContractCallContext
|
||||
) {
|
||||
private val conditionParser = PsiConditionParser(collector, callContext, this)
|
||||
private val constantParser = PsiConstantParser(callContext)
|
||||
private val effectsParsers: Map<Name, PsiEffectParser> = mapOf(
|
||||
RETURNS_EFFECT to PsiReturnsEffectParser(trace, this),
|
||||
RETURNS_NOT_NULL_EFFECT to PsiReturnsEffectParser(trace, this),
|
||||
CALLS_IN_PLACE_EFFECT to PsiCallsEffectParser(trace, this),
|
||||
CONDITIONAL_EFFECT to PsiConditionalEffectParser(trace, this)
|
||||
RETURNS_EFFECT to PsiReturnsEffectParser(collector, callContext, this),
|
||||
RETURNS_NOT_NULL_EFFECT to PsiReturnsEffectParser(collector, callContext, this),
|
||||
CALLS_IN_PLACE_EFFECT to PsiCallsEffectParser(collector, callContext, this),
|
||||
CONDITIONAL_EFFECT to PsiConditionalEffectParser(collector, callContext, this)
|
||||
)
|
||||
|
||||
fun parseContract(expression: KtExpression?, ownerDescriptor: FunctionDescriptor): ContractDescription? {
|
||||
if (expression == null) return null
|
||||
|
||||
fun parseContract(): ContractDescription? {
|
||||
// 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
|
||||
|
||||
@@ -64,17 +62,17 @@ internal class PsiContractParserDispatcher(val trace: BindingTrace) {
|
||||
|
||||
if (effects.isEmpty()) return null
|
||||
|
||||
return ContractDescription(effects, ownerDescriptor)
|
||||
return ContractDescription(effects, callContext.functionDescriptor)
|
||||
}
|
||||
|
||||
fun parseCondition(expression: KtExpression?): BooleanExpression? = expression?.accept(conditionParser, Unit)
|
||||
|
||||
fun parseEffect(expression: KtExpression?): EffectDeclaration? {
|
||||
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]
|
||||
if (parser == null) {
|
||||
trace.report(Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(expression, "Unrecognized effect"))
|
||||
collector.badDescription("unrecognized effect", expression)
|
||||
return null
|
||||
}
|
||||
return parser.tryParseEffect(expression)
|
||||
@@ -87,24 +85,15 @@ internal class PsiContractParserDispatcher(val trace: BindingTrace) {
|
||||
|
||||
fun parseVariable(expression: KtExpression?): VariableReference? {
|
||||
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) {
|
||||
trace.report(
|
||||
Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(
|
||||
expression,
|
||||
"only references to parameters are allowed in contract description"
|
||||
)
|
||||
)
|
||||
collector.badDescription("only references to parameters are allowed in contract description", expression)
|
||||
return null
|
||||
}
|
||||
|
||||
if (descriptor is ReceiverParameterDescriptor && descriptor.type.constructor.declarationDescriptor?.isFromContractDsl() == true) {
|
||||
trace.report(
|
||||
Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(
|
||||
expression,
|
||||
"only references to parameters are allowed. Did you miss label on <this>?"
|
||||
)
|
||||
)
|
||||
collector.badDescription("only references to parameters are allowed. Did you miss label on <this>?", expression)
|
||||
return null
|
||||
}
|
||||
|
||||
return if (KotlinBuiltIns.isBoolean(descriptor.type))
|
||||
|
||||
@@ -24,5 +24,8 @@ internal interface PsiEffectParser {
|
||||
fun tryParseEffect(expression: KtExpression): EffectDeclaration?
|
||||
}
|
||||
|
||||
internal abstract class AbstractPsiEffectParser(val trace: BindingTrace, val contractParserDispatcher: PsiContractParserDispatcher) :
|
||||
PsiEffectParser
|
||||
internal abstract class AbstractPsiEffectParser(
|
||||
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.parsing.*
|
||||
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.model.DefaultValueArgument
|
||||
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
|
||||
|
||||
internal class PsiCallsEffectParser(
|
||||
trace: BindingTrace,
|
||||
collector: ContractParsingDiagnosticsCollector,
|
||||
callContext: ContractCallContext,
|
||||
contractParserDispatcher: PsiContractParserDispatcher
|
||||
) : AbstractPsiEffectParser(trace, contractParserDispatcher) {
|
||||
) : AbstractPsiEffectParser(collector, callContext, contractParserDispatcher) {
|
||||
|
||||
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
|
||||
|
||||
if (!descriptor.isCallsInPlaceEffectDescriptor()) return null
|
||||
@@ -45,15 +46,15 @@ internal class PsiCallsEffectParser(
|
||||
|
||||
val kind = when (kindArgument) {
|
||||
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
|
||||
}
|
||||
|
||||
return CallsEffectDeclaration(lambda, kind)
|
||||
}
|
||||
|
||||
private fun KtExpression.toInvocationKind(trace: BindingTrace): InvocationKind? {
|
||||
val descriptor = this.getResolvedCall(trace.bindingContext)?.resultingDescriptor ?: return null
|
||||
private fun KtExpression.toInvocationKind(bindingContext: BindingContext): InvocationKind? {
|
||||
val descriptor = this.getResolvedCall(bindingContext)?.resultingDescriptor ?: return null
|
||||
if (!descriptor.parents.first().isInvocationKindEnum()) return null
|
||||
|
||||
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.EffectDeclaration
|
||||
import org.jetbrains.kotlin.contracts.parsing.AbstractPsiEffectParser
|
||||
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.contracts.parsing.*
|
||||
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.scopes.receivers.ExpressionReceiver
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
internal class PsiConditionalEffectParser(
|
||||
trace: BindingTrace,
|
||||
collector: ContractParsingDiagnosticsCollector,
|
||||
callContext: ContractCallContext,
|
||||
dispatcher: PsiContractParserDispatcher
|
||||
) : AbstractPsiEffectParser(trace, dispatcher) {
|
||||
) : AbstractPsiEffectParser(collector, callContext, dispatcher) {
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
internal class PsiReturnsEffectParser(
|
||||
trace: BindingTrace,
|
||||
collector: ContractParsingDiagnosticsCollector,
|
||||
callContext: ContractCallContext,
|
||||
contractParserDispatcher: PsiContractParserDispatcher
|
||||
) : AbstractPsiEffectParser(trace, contractParserDispatcher) {
|
||||
) : AbstractPsiEffectParser(collector, callContext, contractParserDispatcher) {
|
||||
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
|
||||
|
||||
if (descriptor.isReturnsNotNullDescriptor())
|
||||
@@ -45,11 +46,9 @@ internal class PsiReturnsEffectParser(
|
||||
// Note that we distinguish absence of an argument and unparsed argument
|
||||
val constant = contractParserDispatcher.parseConstant(argumentExpression)
|
||||
if (constant == null) {
|
||||
trace.report(
|
||||
Errors.ERROR_IN_CONTRACT_DESCRIPTION.on(
|
||||
argumentExpression,
|
||||
"only true/false/null constants in Returns-effect are currently supported"
|
||||
)
|
||||
collector.badDescription(
|
||||
"only true/false/null constants in Returns-effect are currently supported",
|
||||
argumentExpression
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1049,7 +1049,7 @@ public interface Errors {
|
||||
|
||||
// Function contracts
|
||||
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
|
||||
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(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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user