[FIR] Implement BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY

This commit is contained in:
Ivan Kochurkin
2021-07-13 15:26:18 +03:00
committed by teamcityserver
parent cbdd0f579a
commit 2333b1bcf6
19 changed files with 169 additions and 37 deletions
@@ -34,6 +34,7 @@ fun main(args: Array<String>) {
alias<FirTryExpression>("TryExpressionChecker")
alias<FirWhenExpression>("WhenExpressionChecker")
alias<FirLoop>("LoopExpressionChecker")
alias<FirLoopJump>("LoopJumpChecker")
alias<FirBinaryLogicExpression>("LogicExpressionChecker")
alias<FirReturnExpression>("ReturnExpressionChecker")
alias<FirBlock>("BlockChecker")
@@ -59,6 +59,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val ASSIGNMENT_IN_EXPRESSION_CONTEXT by error<KtBinaryExpression>()
val BREAK_OR_CONTINUE_OUTSIDE_A_LOOP by error<PsiElement>()
val NOT_A_LOOP_LABEL by error<PsiElement>()
val BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY by error<KtExpressionWithLabel>()
val VARIABLE_EXPECTED by error<PsiElement>(PositioningStrategy.ASSIGNMENT_LHS)
val DELEGATION_IN_INTERFACE by error<PsiElement>()
val DELEGATION_NOT_TO_INTERFACE by error<PsiElement>()
@@ -29,6 +29,8 @@ class ComposedExpressionCheckers : ExpressionCheckers() {
get() = _whenExpressionCheckers
override val loopExpressionCheckers: Set<FirLoopExpressionChecker>
get() = _loopExpressionCheckers
override val loopJumpCheckers: Set<FirLoopJumpChecker>
get() = _loopJumpCheckers
override val logicExpressionCheckers: Set<FirLogicExpressionChecker>
get() = _logicExpressionCheckers
override val returnExpressionCheckers: Set<FirReturnExpressionChecker>
@@ -76,6 +78,7 @@ class ComposedExpressionCheckers : ExpressionCheckers() {
private val _tryExpressionCheckers: MutableSet<FirTryExpressionChecker> = mutableSetOf()
private val _whenExpressionCheckers: MutableSet<FirWhenExpressionChecker> = mutableSetOf()
private val _loopExpressionCheckers: MutableSet<FirLoopExpressionChecker> = mutableSetOf()
private val _loopJumpCheckers: MutableSet<FirLoopJumpChecker> = mutableSetOf()
private val _logicExpressionCheckers: MutableSet<FirLogicExpressionChecker> = mutableSetOf()
private val _returnExpressionCheckers: MutableSet<FirReturnExpressionChecker> = mutableSetOf()
private val _blockCheckers: MutableSet<FirBlockChecker> = mutableSetOf()
@@ -106,6 +109,7 @@ class ComposedExpressionCheckers : ExpressionCheckers() {
_tryExpressionCheckers += checkers.tryExpressionCheckers
_whenExpressionCheckers += checkers.whenExpressionCheckers
_loopExpressionCheckers += checkers.loopExpressionCheckers
_loopJumpCheckers += checkers.loopJumpCheckers
_logicExpressionCheckers += checkers.logicExpressionCheckers
_returnExpressionCheckers += checkers.returnExpressionCheckers
_blockCheckers += checkers.blockCheckers
@@ -25,6 +25,7 @@ abstract class ExpressionCheckers {
open val tryExpressionCheckers: Set<FirTryExpressionChecker> = emptySet()
open val whenExpressionCheckers: Set<FirWhenExpressionChecker> = emptySet()
open val loopExpressionCheckers: Set<FirLoopExpressionChecker> = emptySet()
open val loopJumpCheckers: Set<FirLoopJumpChecker> = emptySet()
open val logicExpressionCheckers: Set<FirLogicExpressionChecker> = emptySet()
open val returnExpressionCheckers: Set<FirReturnExpressionChecker> = emptySet()
open val blockCheckers: Set<FirBlockChecker> = emptySet()
@@ -53,6 +54,7 @@ abstract class ExpressionCheckers {
@CheckersComponentInternal internal val allTryExpressionCheckers: Set<FirTryExpressionChecker> by lazy { tryExpressionCheckers + basicExpressionCheckers }
@CheckersComponentInternal internal val allWhenExpressionCheckers: Set<FirWhenExpressionChecker> by lazy { whenExpressionCheckers + basicExpressionCheckers }
@CheckersComponentInternal internal val allLoopExpressionCheckers: Set<FirLoopExpressionChecker> by lazy { loopExpressionCheckers + basicExpressionCheckers }
@CheckersComponentInternal internal val allLoopJumpCheckers: Set<FirLoopJumpChecker> by lazy { loopJumpCheckers + basicExpressionCheckers }
@CheckersComponentInternal internal val allLogicExpressionCheckers: Set<FirLogicExpressionChecker> by lazy { logicExpressionCheckers + basicExpressionCheckers }
@CheckersComponentInternal internal val allReturnExpressionCheckers: Set<FirReturnExpressionChecker> by lazy { returnExpressionCheckers + basicExpressionCheckers }
@CheckersComponentInternal internal val allBlockCheckers: Set<FirBlockChecker> by lazy { blockCheckers + basicExpressionCheckers }
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.fir.expressions.FirEqualityOperatorCall
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirGetClassCall
import org.jetbrains.kotlin.fir.expressions.FirLoop
import org.jetbrains.kotlin.fir.expressions.FirLoopJump
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
@@ -46,6 +47,7 @@ typealias FirVariableAssignmentChecker = FirExpressionChecker<FirVariableAssignm
typealias FirTryExpressionChecker = FirExpressionChecker<FirTryExpression>
typealias FirWhenExpressionChecker = FirExpressionChecker<FirWhenExpression>
typealias FirLoopExpressionChecker = FirExpressionChecker<FirLoop>
typealias FirLoopJumpChecker = FirExpressionChecker<FirLoopJump>
typealias FirLogicExpressionChecker = FirExpressionChecker<FirBinaryLogicExpression>
typealias FirReturnExpressionChecker = FirExpressionChecker<FirReturnExpression>
typealias FirBlockChecker = FirExpressionChecker<FirBlock>
@@ -51,6 +51,7 @@ import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtExpressionWithLabel
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtImportDirective
@@ -97,6 +98,7 @@ object FirErrors {
val ASSIGNMENT_IN_EXPRESSION_CONTEXT by error0<KtBinaryExpression>()
val BREAK_OR_CONTINUE_OUTSIDE_A_LOOP by error0<PsiElement>()
val NOT_A_LOOP_LABEL by error0<PsiElement>()
val BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY by error0<KtExpressionWithLabel>()
val VARIABLE_EXPECTED by error0<PsiElement>(SourceElementPositioningStrategies.ASSIGNMENT_LHS)
val DELEGATION_IN_INTERFACE by error0<PsiElement>()
val DELEGATION_NOT_TO_INTERFACE by error0<PsiElement>()
@@ -218,7 +218,7 @@ object FirReturnsImpliesAnalyzer : FirControlFlowChecker() {
}
private val CheckerContext.containingProperty: FirProperty?
get() = (containingDeclarations.asReversed().firstOrNull { it is FirProperty } as? FirProperty)
get() = (containingDeclarations.lastOrNull { it is FirProperty } as? FirProperty)
private fun FirFunction.getParameterType(symbol: FirBasedSymbol<*>, context: CheckerContext): ConeKotlinType? {
val typeRef = if (this.symbol == symbol) {
@@ -20,7 +20,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
get() = setOf(
FirReservedUnderscoreExpressionChecker,
FirExpressionAnnotationChecker,
FirDeprecationChecker,
FirDeprecationChecker
)
override val qualifiedAccessCheckers: Set<FirQualifiedAccessChecker>
@@ -76,6 +76,11 @@ object CommonExpressionCheckers : ExpressionCheckers() {
FirLoopConditionChecker,
)
override val loopJumpCheckers: Set<FirLoopJumpChecker>
get() = setOf(
FirBreakOrContinueJumpsAcrossFunctionBoundaryChecker
)
override val logicExpressionCheckers: Set<FirLogicExpressionChecker>
get() = setOf(
FirLogicExpressionTypeChecker,
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.fir.analysis.checkers.expression
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
object FirBreakOrContinueJumpsAcrossFunctionBoundaryChecker : FirLoopJumpChecker() {
override fun check(expression: FirLoopJump, context: CheckerContext, reporter: DiagnosticReporter) {
val errorPathElements = ArrayDeque<FirElement>()
fun findPathAndCheck(element: FirElement?): Boolean {
fun findPathAndCheckWithAddingErrorElement(errorElement: FirElement, checkElement: FirElement?): Boolean {
errorPathElements.addLast(errorElement)
val result = findPathAndCheck(checkElement)
errorPathElements.removeLast()
return result
}
if (element == null) {
return false
}
when (element) {
expression -> {
if (errorPathElements.any()) {
reporter.reportOn(expression.source, FirErrors.BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY, context)
}
return true
}
is FirBlock -> {
for (statement in element.statements) {
if (findPathAndCheck(statement)) {
return true
}
}
}
is FirWhenExpression -> {
for (branch in element.branches) {
if (findPathAndCheck(branch.result)) {
return true
}
}
}
is FirVariable -> return findPathAndCheck(element.initializer)
is FirWrappedExpression -> return findPathAndCheck(element.expression)
is FirCall -> {
for (argument in element.arguments) {
if (findPathAndCheck(argument)) {
return true
}
}
}
is FirClass -> {
errorPathElements.addLast(element)
for (declaration in element.declarations) {
if (findPathAndCheck(declaration)) {
errorPathElements.removeLast()
return true
}
}
errorPathElements.removeLast()
}
is FirFunction -> {
if (findPathAndCheckWithAddingErrorElement(element, element.body)) {
return true
}
if (element is FirConstructor) {
val argumentList = element.delegatedConstructor?.argumentList
if (argumentList != null) {
errorPathElements.addLast(element)
for (argument in argumentList.arguments) {
if (findPathAndCheck(argument)) {
errorPathElements.removeLast()
return true
}
}
errorPathElements.removeLast()
}
}
}
is FirAnonymousInitializer -> return findPathAndCheckWithAddingErrorElement(element, element.body)
is FirAnonymousObjectExpression -> return findPathAndCheckWithAddingErrorElement(element, element.anonymousObject)
is FirAnonymousFunctionExpression -> return findPathAndCheckWithAddingErrorElement(element, element.anonymousFunction)
}
return false
}
findPathAndCheck(expression.target.labeledElement.block)
}
}
@@ -117,6 +117,14 @@ class ExpressionCheckersDiagnosticComponent(
checkers.allReturnExpressionCheckers.check(returnExpression, data, reporter)
}
override fun visitBreakExpression(breakExpression: FirBreakExpression, data: CheckerContext) {
checkers.allLoopJumpCheckers.check(breakExpression, data, reporter)
}
override fun visitContinueExpression(continueExpression: FirContinueExpression, data: CheckerContext) {
checkers.allLoopJumpCheckers.check(continueExpression, data, reporter)
}
override fun visitBlock(block: FirBlock, data: CheckerContext) {
checkers.allBlockCheckers.check(block, data, reporter)
}
@@ -67,6 +67,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ASSIGN_OPERATOR_A
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BACKING_FIELD_IN_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BOUND_ON_TYPE_ALIAS_PARAMETER_NOT_ALLOWED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BREAK_OR_CONTINUE_OUTSIDE_A_LOOP
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CALLABLE_REFERENCE_LHS_NOT_A_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR
@@ -456,6 +457,7 @@ class FirDefaultErrorMessages {
// map.put(EXPRESSION_REQUIRED, ...) // &
map.put(BREAK_OR_CONTINUE_OUTSIDE_A_LOOP, "'break' and 'continue' are only allowed inside a loop")
map.put(NOT_A_LOOP_LABEL, "The label does not denote a loop") // *
map.put(BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY, "'break' or 'continue' jumps across a function or a class boundary")
map.put(VARIABLE_EXPECTED, "Variable expected")
map.put(DELEGATION_IN_INTERFACE, "Interfaces cannot use delegation")
map.put(DELEGATION_NOT_TO_INTERFACE, "Only interfaces can be delegated to")