[LL] Avoid exceptions when analyzing inconsistent PSI in LLFirDeclarationModificationService

- The exceptions in KT-63130 occur because PSI tree change events may be
  published when parts of the PSI under modification are inconsistent.
  Such inconsistent elements are then analyzed by
  `LLFirDeclarationModificationService` to check if the modification
  occurs in a contract statement.
- The solution adds `*OrNull` functions to `KtQualifiedExpression` which
  return `null` instead of failing with an exception when the receiver
  or selector cannot be found.

^KT-63130 fixed
This commit is contained in:
Marco Pennekamp
2023-11-13 19:59:35 +01:00
committed by Space Team
parent 6c67835128
commit 6668cc281d
6 changed files with 43 additions and 17 deletions
@@ -1,5 +1,3 @@
// IGNORE_FIR
import kotlin.contracts.InvocationKind
inline fun foo(block: () -> Unit) {
@@ -1,5 +1,3 @@
// IGNORE_FIR
fun foo(string: String) {
<expr>string.length</expr>
}
@@ -34,15 +34,15 @@ class KtDotQualifiedExpression : KtExpressionImplStub<KotlinPlaceHolderStub<KtDo
}
override val receiverExpression: KtExpression
get() {
val stub = stub
if (stub != null) {
val childExpressionsByStub = getChildExpressionsByStub(stub)
if (childExpressionsByStub != null) {
return childExpressionsByStub[0]
}
}
return super.receiverExpression
get() = stubReceiverExpression ?: super.receiverExpression
@KtPsiInconsistencyHandling
override val receiverExpressionOrNull: KtExpression?
get() = stubReceiverExpression ?: super.receiverExpressionOrNull
private val stubReceiverExpression: KtExpression?
get() = stub?.let { stub ->
getChildExpressionsByStub(stub)?.getOrNull(0)
}
override val selectorExpression: KtExpression?
@@ -26,20 +26,33 @@ import java.util.*
interface KtQualifiedExpression : KtExpression {
val receiverExpression: KtExpression
get() = getExpression(false) ?: throw AssertionError("No receiver found: ${getElementTextWithContext()}")
@OptIn(KtPsiInconsistencyHandling::class)
get() = receiverExpressionOrNull ?: throw AssertionError("No receiver found: ${getElementTextWithContext()}")
/**
* A consistent [KtQualifiedExpression] should always have a receiver, so [receiverExpression] should be preferred if possible. Only use
* [receiverExpressionOrNull] if you suspect that the PSI might be inconsistent (e.g. due to ongoing modification) and need a `null`
* value instead of an error.
*/
@KtPsiInconsistencyHandling
val receiverExpressionOrNull: KtExpression?
get() = getExpression(false)
val selectorExpression: KtExpression?
get() = getExpression(true)
val operationTokenNode: ASTNode
get() = node.findChildByType(KtTokens.OPERATIONS) ?: error(
get() = operationTokenNodeOrNull ?: error(
"No operation node for ${node.elementType}. Children: ${Arrays.toString(children)}"
)
private val operationTokenNodeOrNull: ASTNode?
get() = node.findChildByType(KtTokens.OPERATIONS)
val operationSign: KtSingleValueToken
get() = operationTokenNode.elementType as KtSingleValueToken
private fun getExpression(afterOperation: Boolean): KtExpression? {
return operationTokenNode.psi?.siblings(afterOperation, false)?.firstIsInstanceOrNull<KtExpression>()
return operationTokenNodeOrNull?.psi?.siblings(afterOperation, false)?.firstIsInstanceOrNull<KtExpression>()
}
}
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2023 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.psi
/**
* Functions and classes annotated with [KtPsiInconsistencyHandling] are not intended for general-purpose use, but for working with possibly
* inconsistent PSI. The specific circumstances need to be described in the documentation of the annotated function/class.
*
* Inconsistent PSI cannot be produced by the Kotlin parser. It occurs rarely, for example during modification of the PSI by the IDE. In
* general, it can be assumed that all PSI is consistent. Inconsistent PSI should only be assumed when there is sufficient proof.
*/
@RequiresOptIn
annotation class KtPsiInconsistencyHandling
@@ -297,9 +297,10 @@ fun KtNamedFunction.isContractPresentPsiCheck(isAllowedOnMembers: Boolean): Bool
fun KtExpression.isContractDescriptionCallPsiCheck(): Boolean =
(this is KtCallExpression && calleeExpression?.text == "contract") || (this is KtQualifiedExpression && isContractDescriptionCallPsiCheck())
@OptIn(KtPsiInconsistencyHandling::class)
fun KtQualifiedExpression.isContractDescriptionCallPsiCheck(): Boolean {
val expression = selectorExpression ?: return false
return receiverExpression.text == "kotlin.contracts" && expression.isContractDescriptionCallPsiCheck()
return receiverExpressionOrNull?.text == "kotlin.contracts" && expression.isContractDescriptionCallPsiCheck()
}
fun KtElement.isFirstStatement(): Boolean {