[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
|
import kotlin.contracts.InvocationKind
|
||||||
|
|
||||||
inline fun foo(block: () -> Unit) {
|
inline fun foo(block: () -> Unit) {
|
||||||
|
|||||||
-2
@@ -1,5 +1,3 @@
|
|||||||
// IGNORE_FIR
|
|
||||||
|
|
||||||
fun foo(string: String) {
|
fun foo(string: String) {
|
||||||
<expr>string.length</expr>
|
<expr>string.length</expr>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,15 +34,15 @@ class KtDotQualifiedExpression : KtExpressionImplStub<KotlinPlaceHolderStub<KtDo
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val receiverExpression: KtExpression
|
override val receiverExpression: KtExpression
|
||||||
get() {
|
get() = stubReceiverExpression ?: super.receiverExpression
|
||||||
val stub = stub
|
|
||||||
if (stub != null) {
|
@KtPsiInconsistencyHandling
|
||||||
val childExpressionsByStub = getChildExpressionsByStub(stub)
|
override val receiverExpressionOrNull: KtExpression?
|
||||||
if (childExpressionsByStub != null) {
|
get() = stubReceiverExpression ?: super.receiverExpressionOrNull
|
||||||
return childExpressionsByStub[0]
|
|
||||||
}
|
private val stubReceiverExpression: KtExpression?
|
||||||
}
|
get() = stub?.let { stub ->
|
||||||
return super.receiverExpression
|
getChildExpressionsByStub(stub)?.getOrNull(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val selectorExpression: KtExpression?
|
override val selectorExpression: KtExpression?
|
||||||
|
|||||||
@@ -26,20 +26,33 @@ import java.util.*
|
|||||||
|
|
||||||
interface KtQualifiedExpression : KtExpression {
|
interface KtQualifiedExpression : KtExpression {
|
||||||
val receiverExpression: 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?
|
val selectorExpression: KtExpression?
|
||||||
get() = getExpression(true)
|
get() = getExpression(true)
|
||||||
|
|
||||||
val operationTokenNode: ASTNode
|
val operationTokenNode: ASTNode
|
||||||
get() = node.findChildByType(KtTokens.OPERATIONS) ?: error(
|
get() = operationTokenNodeOrNull ?: error(
|
||||||
"No operation node for ${node.elementType}. Children: ${Arrays.toString(children)}"
|
"No operation node for ${node.elementType}. Children: ${Arrays.toString(children)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val operationTokenNodeOrNull: ASTNode?
|
||||||
|
get() = node.findChildByType(KtTokens.OPERATIONS)
|
||||||
|
|
||||||
val operationSign: KtSingleValueToken
|
val operationSign: KtSingleValueToken
|
||||||
get() = operationTokenNode.elementType as KtSingleValueToken
|
get() = operationTokenNode.elementType as KtSingleValueToken
|
||||||
|
|
||||||
private fun getExpression(afterOperation: Boolean): KtExpression? {
|
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 =
|
fun KtExpression.isContractDescriptionCallPsiCheck(): Boolean =
|
||||||
(this is KtCallExpression && calleeExpression?.text == "contract") || (this is KtQualifiedExpression && isContractDescriptionCallPsiCheck())
|
(this is KtCallExpression && calleeExpression?.text == "contract") || (this is KtQualifiedExpression && isContractDescriptionCallPsiCheck())
|
||||||
|
|
||||||
|
@OptIn(KtPsiInconsistencyHandling::class)
|
||||||
fun KtQualifiedExpression.isContractDescriptionCallPsiCheck(): Boolean {
|
fun KtQualifiedExpression.isContractDescriptionCallPsiCheck(): Boolean {
|
||||||
val expression = selectorExpression ?: return false
|
val expression = selectorExpression ?: return false
|
||||||
return receiverExpression.text == "kotlin.contracts" && expression.isContractDescriptionCallPsiCheck()
|
return receiverExpressionOrNull?.text == "kotlin.contracts" && expression.isContractDescriptionCallPsiCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun KtElement.isFirstStatement(): Boolean {
|
fun KtElement.isFirstStatement(): Boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user