[FIR] Properly track problems in contract description during effect extraction

This commit is contained in:
Dmitriy Novozhilov
2023-04-12 17:31:16 +03:00
committed by Space Team
parent 79d378f2bd
commit 5a92eb2c67
34 changed files with 563 additions and 102 deletions
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.KtRealSourceElementKind
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
@@ -13,15 +12,16 @@ import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.contracts.FirResolvedContractDescription
import org.jetbrains.kotlin.fir.contracts.description.*
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.declarations.FirContractDescriptionOwner
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
object FirContractChecker : FirFunctionChecker() {
// TODO: The message should vary. Migrate this to [ConeEffectExtractor] when creating fine-grained errors.
private const val UNEXPECTED_CONSTRUCTION = "unexpected construction in contract description"
private val EMPTY_CONTRACT_MESSAGE = "Empty contract block is not allowed"
override fun check(declaration: FirFunction, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration !is FirContractDescriptionOwner) return
@@ -29,6 +29,9 @@ object FirContractChecker : FirFunctionChecker() {
checkUnresolvedEffects(contractDescription, context, reporter)
checkContractNotAllowed(declaration, contractDescription, context, reporter)
if (contractDescription.effects.isEmpty() && contractDescription.unresolvedEffects.isEmpty()) {
reporter.reportOn(contractDescription.source, FirErrors.ERROR_IN_CONTRACT_DESCRIPTION, EMPTY_CONTRACT_MESSAGE, context)
}
}
private fun checkUnresolvedEffects(
@@ -38,12 +41,11 @@ object FirContractChecker : FirFunctionChecker() {
) {
// Any statements that [ConeEffectExtractor] cannot extract effects will be in `unresolvedEffects`.
for (unresolvedEffect in contractDescription.unresolvedEffects) {
val statement = unresolvedEffect.statement
if (statement.source == null || statement.source!!.kind is KtFakeSourceElementKind) continue
val diagnostic = unresolvedEffect.effect.accept(DiagnosticExtractor, null) ?: continue
// TODO: report on fine-grained locations, e.g., ... implies unresolved => report on unresolved, not the entire statement.
// but, sometimes, it's just reported on `contract`...
reporter.reportOn(statement.source, FirErrors.ERROR_IN_CONTRACT_DESCRIPTION, UNEXPECTED_CONSTRUCTION, context)
reporter.reportOn(unresolvedEffect.source, FirErrors.ERROR_IN_CONTRACT_DESCRIPTION, diagnostic.reason, context)
}
}
@@ -64,4 +66,78 @@ object FirContractChecker : FirFunctionChecker() {
else if (declaration.symbol.callableId.isLocal || declaration.visibility == Visibilities.Local) contractNotAllowed("Contracts are not allowed for local functions")
}
private object DiagnosticExtractor : ConeContractDescriptionVisitor<ConeDiagnostic?, Nothing?>() {
override fun visitContractDescriptionElement(
contractDescriptionElement: ConeContractDescriptionElement,
data: Nothing?
): ConeDiagnostic? {
return null
}
override fun visitConditionalEffectDeclaration(
conditionalEffect: ConeConditionalEffectDeclaration,
data: Nothing?
): ConeDiagnostic? {
return conditionalEffect.effect.accept(this, null) ?: conditionalEffect.condition.accept(this, null)
}
override fun visitReturnsEffectDeclaration(returnsEffect: ConeReturnsEffectDeclaration, data: Nothing?): ConeDiagnostic? {
return returnsEffect.value.accept(this, null)
}
override fun visitCallsEffectDeclaration(callsEffect: ConeCallsEffectDeclaration, data: Nothing?): ConeDiagnostic? {
return callsEffect.valueParameterReference.accept(this, data)
}
override fun visitErroneousCallsEffectDeclaration(
callsEffect: ConeErroneousCallsEffectDeclaration,
data: Nothing?
): ConeDiagnostic {
return callsEffect.diagnostic
}
override fun visitLogicalBinaryOperationContractExpression(
binaryLogicExpression: ConeBinaryLogicExpression,
data: Nothing?
): ConeDiagnostic? {
return binaryLogicExpression.left.accept(this, null) ?: binaryLogicExpression.right.accept(this, null)
}
override fun visitLogicalNot(logicalNot: ConeLogicalNot, data: Nothing?): ConeDiagnostic? {
return logicalNot.arg.accept(this, null)
}
override fun visitIsInstancePredicate(isInstancePredicate: ConeIsInstancePredicate, data: Nothing?): ConeDiagnostic? {
return isInstancePredicate.arg.accept(this, data)
}
override fun visitErroneousIsInstancePredicate(
isInstancePredicate: ConeErroneousIsInstancePredicate,
data: Nothing?
): ConeDiagnostic {
return isInstancePredicate.diagnostic
}
override fun visitIsNullPredicate(isNullPredicate: ConeIsNullPredicate, data: Nothing?): ConeDiagnostic? {
return isNullPredicate.arg.accept(this, data)
}
override fun visitErroneousConstantReference(
erroneousConstantReference: ConeErroneousConstantReference,
data: Nothing?
): ConeDiagnostic {
return erroneousConstantReference.diagnostic
}
override fun visitErroneousValueParameterReference(
valueParameterReference: ConeErroneousValueParameterReference,
data: Nothing?
): ConeDiagnostic {
return valueParameterReference.diagnostic
}
override fun visitErroneousElement(element: ConeErroneousContractElement, data: Nothing?): ConeDiagnostic {
return element.diagnostic
}
}
}