[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")
@@ -1,7 +1,7 @@
fun test() {
while (true) {
fun local1() {
break
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break<!>
}
}
}
@@ -9,7 +9,7 @@ fun test() {
fun test2() {
while (true) {
{
continue
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>continue<!>
}
}
}
@@ -18,11 +18,11 @@ fun test3() {
while (true) {
class LocalClass {
init {
continue
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>continue<!>
}
fun foo() {
break
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break<!>
}
}
}
@@ -32,7 +32,7 @@ fun test4() {
while (true) {
object: Any() {
init {
break
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break<!>
}
}
}
@@ -42,10 +42,10 @@ fun test5() {
while (true) {
class LocalClass(val x: Int) {
constructor() : this(42) {
break
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break<!>
}
constructor(y: Double) : this(y.toInt()) {
continue
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>continue<!>
}
}
}
@@ -55,10 +55,10 @@ fun test6() {
while (true) {
class LocalClass(val x: Int) {
init {
break
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break<!>
}
init {
continue
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>continue<!>
}
}
}
@@ -68,10 +68,10 @@ fun test7() {
while (true) {
class LocalClass {
val x: Int = if (true) {
break
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break<!>
}
else {
continue
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>continue<!>
}
}
}
@@ -80,7 +80,7 @@ fun test7() {
fun test8() {
while (true) {
class LocalClass(val x: Int) {
constructor() : this(if (true) { 42 } else { break })
constructor() : this(if (true) { 42 } else { <!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break<!> })
}
}
}
}
@@ -1,17 +0,0 @@
fun call(f: () -> Unit) = f()
fun f1() {
outer@ while (true) {
call {
break@outer
}
}
}
fun f2() {
do {
fun inner() {
continue
}
} while (true)
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
fun call(f: () -> Unit) = f()
fun f1() {
@@ -3,6 +3,6 @@ package b
fun foo() {
for (i in <!ITERATOR_MISSING, UNRESOLVED_REFERENCE!>collection<!>) {
{
break
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break<!>
}
}<!SYNTAX!><!>
@@ -5,14 +5,14 @@
fun case_1(value_1: Boolean) {
while (value_1) {
funWithExactlyOnceCallsInPlace {
break
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break<!>
}
println("1")
}
loop@ for (i in 0..10) {
funWithExactlyOnceCallsInPlace {
break@loop
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>break@loop<!>
}
println("1")
}
@@ -22,14 +22,14 @@ fun case_1(value_1: Boolean) {
fun case_2(value_1: Boolean) {
for (i in 0..10) {
funWithExactlyOnceCallsInPlace {
continue
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>continue<!>
}
println("1")
}
loop@ while (value_1) {
funWithExactlyOnceCallsInPlace {
continue@loop
<!BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY!>continue@loop<!>
}
println("1")
}
@@ -36,6 +36,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
@@ -132,6 +133,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY) { firDiagnostic ->
BreakOrContinueJumpsAcrossFunctionBoundaryImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.VARIABLE_EXPECTED) { firDiagnostic ->
VariableExpectedImpl(
firDiagnostic as FirPsiDiagnostic,
@@ -45,6 +45,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
@@ -122,6 +123,10 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = NotALoopLabel::class
}
abstract class BreakOrContinueJumpsAcrossFunctionBoundary : KtFirDiagnostic<KtExpressionWithLabel>() {
override val diagnosticClass get() = BreakOrContinueJumpsAcrossFunctionBoundary::class
}
abstract class VariableExpected : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = VariableExpected::class
}
@@ -47,6 +47,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
@@ -156,6 +157,13 @@ internal class NotALoopLabelImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class BreakOrContinueJumpsAcrossFunctionBoundaryImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.BreakOrContinueJumpsAcrossFunctionBoundary(), KtAbstractFirDiagnostic<KtExpressionWithLabel> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class VariableExpectedImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,