[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:
committed by
Space Team
parent
6c67835128
commit
6668cc281d
-2
@@ -1,5 +1,3 @@
|
||||
// IGNORE_FIR
|
||||
|
||||
import kotlin.contracts.InvocationKind
|
||||
|
||||
inline fun foo(block: () -> Unit) {
|
||||
|
||||
-2
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user