Skip to content

Commit 302e67c

Browse files
dimonchik0036Space Team
authored andcommitted
[LL] FirElementsRecorder: extract anchorPsi as public API
This API should be used in places where we care about PSI <-> FIR mapping, so the result would be in sync ^KT-79653
1 parent c0e38dc commit 302e67c

File tree

1 file changed

+114
-92
lines changed
  • analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure

1 file changed

+114
-92
lines changed

analysis/low-level-api-fir/src/org/jetbrains/kotlin/analysis/low/level/api/fir/file/structure/FirElementsRecorder.kt

Lines changed: 114 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
/*
2-
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
2+
* Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
33
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
44
*/
55

66
package org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure
77

8+
import com.intellij.psi.PsiElement
89
import org.jetbrains.kotlin.*
910
import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.DuplicatedFirSourceElementsException
1011
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.isErrorElement
1112
import org.jetbrains.kotlin.fir.FirElement
1213
import org.jetbrains.kotlin.fir.builder.toFirOperationOrNull
1314
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
1415
import org.jetbrains.kotlin.fir.expressions.*
15-
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
1616
import org.jetbrains.kotlin.fir.expressions.builder.buildLiteralExpression
1717
import org.jetbrains.kotlin.fir.psi
1818
import org.jetbrains.kotlin.fir.references.*
@@ -122,99 +122,10 @@ internal open class FirElementsRecorder : FirVisitor<Unit, MutableMap<KtElement,
122122
}
123123

124124
protected fun cacheElement(element: FirElement, cache: MutableMap<KtElement, FirElement>) {
125-
val psi = element.source
126-
?.takeIf {
127-
it is KtRealPsiSourceElement ||
128-
it.kind == KtFakeSourceElementKind.ReferenceInAtomicQualifiedAccess ||
129-
it.kind == KtFakeSourceElementKind.FromUseSiteTarget ||
130-
// To allow type retrieval from erroneous typealias even though it is erroneous
131-
it.kind == KtFakeSourceElementKind.ErroneousTypealiasExpansion ||
132-
// For secondary constructors without explicit delegated constructor call, the PSI tree always create an empty
133-
// KtConstructorDelegationCall. In this case, the source in FIR has this fake source kind.
134-
it.kind == KtFakeSourceElementKind.ImplicitConstructor ||
135-
it.isSourceForSmartCasts(element) ||
136-
it.kind == KtFakeSourceElementKind.DanglingModifierList ||
137-
it.isSourceForArrayAugmentedAssign(element) ||
138-
it.isSourceForCompoundAccess(element) ||
139-
it.isSourceForInvertedInOperator(element)
140-
}.psi as? KtElement
141-
?: return
125+
val psi = element.anchorPsi as? KtElement ?: return
142126
cache(psi, element, cache)
143127
}
144128

145-
private fun KtSourceElement.isSourceForInvertedInOperator(fir: FirElement) =
146-
kind == KtFakeSourceElementKind.DesugaredInvertedContains
147-
&& fir is FirResolvedNamedReference && fir.name == OperatorNameConventions.CONTAINS
148-
149-
/**
150-
* FIR represents compound assignment and inc/dec operations as multiple smaller instructions. Here we choose the write operation as the
151-
* resolved FirElement for binary and unary expressions. For example, the `FirVariableAssignment` or the call to `set` or `plusAssign`
152-
* function, etc. This is because the write FirElement can be used to retrieve all other information related to this compound operation.
153-
154-
* On the other hand, if the PSI is the left operand of an assignment or the base expression of a unary expression, we take the read FIR
155-
* element so the user of the Analysis API is able to retrieve such read calls reliably.
156-
*/
157-
private fun KtSourceElement.isSourceForCompoundAccess(fir: FirElement): Boolean {
158-
val psi = psi
159-
val parentPsi = psi?.parent
160-
if (kind !is KtFakeSourceElementKind.DesugaredAugmentedAssign && kind !is KtFakeSourceElementKind.DesugaredIncrementOrDecrement) {
161-
return false
162-
}
163-
return when {
164-
psi is KtBinaryExpression || psi is KtUnaryExpression -> fir.isWriteInCompoundCall()
165-
parentPsi is KtBinaryExpression && psi == parentPsi.left -> fir.isReadInCompoundCall()
166-
parentPsi is KtUnaryExpression && psi == parentPsi.baseExpression -> fir.isReadInCompoundCall()
167-
else -> false
168-
}
169-
}
170-
171-
// After desugaring, we also have FirBlock with the same source element.
172-
// We need to filter it out to map this source element to set/plusAssign call, so we check `is FirFunctionCall`
173-
private fun KtSourceElement.isSourceForArrayAugmentedAssign(fir: FirElement): Boolean {
174-
return kind is KtFakeSourceElementKind.DesugaredAugmentedAssign && (fir is FirFunctionCall || fir is FirThisReceiverExpression)
175-
}
176-
177-
// `FirSmartCastExpression` forward the source from the original expression,
178-
// and implicit receivers have fake sources pointing to a wider part of the expression.
179-
// Thus, `FirElementsRecorder` may try assigning an unnecessarily wide source
180-
// to smart cast expressions, which will affect the
181-
// `org.jetbrains.kotlin.idea.highlighting.highlighters.ExpressionsSmartcastHighlighter#highlightExpression`
182-
// function in intellij.git
183-
private fun KtSourceElement.isSourceForSmartCasts(fir: FirElement) =
184-
(kind is KtFakeSourceElementKind.SmartCastExpression) && fir is FirSmartCastExpression && !fir.originalExpression.isImplicitThisReceiver
185-
186-
private val FirExpression.isImplicitThisReceiver get() = this is FirThisReceiverExpression && this.isImplicit
187-
188-
private fun FirElement.isReadInCompoundCall(): Boolean {
189-
if (this is FirPropertyAccessExpression) return true
190-
if (this !is FirFunctionCall) return false
191-
val name = (calleeReference as? FirResolvedNamedReference)?.name ?: getFallbackCompoundCalleeName()
192-
return name == OperatorNameConventions.GET
193-
}
194-
195-
private fun FirElement.isWriteInCompoundCall(): Boolean {
196-
if (this is FirVariableAssignment) return true
197-
if (this !is FirFunctionCall) return false
198-
val name = (calleeReference as? FirResolvedNamedReference)?.name ?: getFallbackCompoundCalleeName()
199-
return name == OperatorNameConventions.SET || name in OperatorNameConventions.ASSIGNMENT_OPERATIONS
200-
}
201-
202-
/**
203-
* If the callee reference is not a [FirResolvedNamedReference], we can get the compound callee name from the source instead. For
204-
* example, if the callee reference is a [FirErrorNamedReference] with an unresolved name `plusAssign`, the operation element type from
205-
* the source will be `KtTokens.PLUSEQ`, which can be transformed to `plusAssign`.
206-
*/
207-
private fun FirElement.getFallbackCompoundCalleeName(): Name? {
208-
val psi = source.psi as? KtOperationExpression ?: return null
209-
val operationReference = psi.operationReference
210-
return operationReference.getAssignmentOperationName() ?: operationReference.getReferencedNameAsName()
211-
}
212-
213-
private fun KtSimpleNameExpression.getAssignmentOperationName(): Name? {
214-
val firOperation = getReferencedNameElementType().toFirOperationOrNull() ?: return null
215-
return FirOperationNameConventions.ASSIGNMENTS[firOperation]
216-
}
217-
218129
private val FirLiteralExpression.isConverted: Boolean
219130
get() {
220131
val firSourcePsi = this.source?.psi ?: return false
@@ -262,5 +173,116 @@ internal open class FirElementsRecorder : FirVisitor<Unit, MutableMap<KtElement,
262173
companion object {
263174
fun recordElementsFrom(firElement: FirElement, recorder: FirElementsRecorder): Map<KtElement, FirElement> =
264175
buildMap { firElement.accept(recorder, this) }
176+
177+
/**
178+
* The PSI element which can be used as an anchor point for FIR <–> PSI mapping.
179+
*
180+
* Not all fake FIR elements might have an anhor PSI element to avoid conflict with the original source element.
181+
* For instance, the synthetic enum supertype would have the same psi as the class itself, so it shouldn't be used
182+
* as an anchor to avoid ambiguity. Clients won't expect to see the supertype type reference value instead of the [FirRegularClass][org.jetbrains.kotlin.fir.declarations.FirRegularClass]
183+
* by [KtClass] key.
184+
*/
185+
val FirElement.anchorPsi: PsiElement?
186+
get() {
187+
val source = source as? KtPsiSourceElement? ?: return null
188+
when (source.kind) {
189+
KtRealSourceElementKind,
190+
KtFakeSourceElementKind.ReferenceInAtomicQualifiedAccess,
191+
KtFakeSourceElementKind.FromUseSiteTarget,
192+
// To allow type retrieval from erroneous typealias even though it is erroneous
193+
KtFakeSourceElementKind.ErroneousTypealiasExpansion,
194+
// For secondary constructors without explicit delegated constructor call, the PSI tree always create an empty
195+
// KtConstructorDelegationCall. In this case, the source in FIR has this fake source kind.
196+
KtFakeSourceElementKind.ImplicitConstructor,
197+
KtFakeSourceElementKind.DanglingModifierList,
198+
-> Unit
199+
200+
else if (
201+
source.isSourceForSmartCasts(this) ||
202+
source.isSourceForArrayAugmentedAssign(this) ||
203+
source.isSourceForCompoundAccess(this) ||
204+
source.isSourceForInvertedInOperator(this)
205+
)
206+
-> Unit
207+
208+
else -> return null
209+
}
210+
211+
return source.psi
212+
}
213+
214+
215+
private fun KtSourceElement.isSourceForInvertedInOperator(fir: FirElement) =
216+
kind == KtFakeSourceElementKind.DesugaredInvertedContains
217+
&& fir is FirResolvedNamedReference && fir.name == OperatorNameConventions.CONTAINS
218+
219+
/**
220+
* FIR represents compound assignment and inc/dec operations as multiple smaller instructions. Here we choose the write operation as the
221+
* resolved FirElement for binary and unary expressions. For example, the `FirVariableAssignment` or the call to `set` or `plusAssign`
222+
* function, etc. This is because the write FirElement can be used to retrieve all other information related to this compound operation.
223+
224+
* On the other hand, if the PSI is the left operand of an assignment or the base expression of a unary expression, we take the read FIR
225+
* element so the user of the Analysis API is able to retrieve such read calls reliably.
226+
*/
227+
private fun KtSourceElement.isSourceForCompoundAccess(fir: FirElement): Boolean {
228+
val psi = psi
229+
val parentPsi = psi?.parent
230+
if (kind !is KtFakeSourceElementKind.DesugaredAugmentedAssign && kind !is KtFakeSourceElementKind.DesugaredIncrementOrDecrement) {
231+
return false
232+
}
233+
return when {
234+
psi is KtBinaryExpression || psi is KtUnaryExpression -> fir.isWriteInCompoundCall()
235+
parentPsi is KtBinaryExpression && psi == parentPsi.left -> fir.isReadInCompoundCall()
236+
parentPsi is KtUnaryExpression && psi == parentPsi.baseExpression -> fir.isReadInCompoundCall()
237+
else -> false
238+
}
239+
}
240+
241+
// After desugaring, we also have FirBlock with the same source element.
242+
// We need to filter it out to map this source element to set/plusAssign call, so we check `is FirFunctionCall`
243+
private fun KtSourceElement.isSourceForArrayAugmentedAssign(fir: FirElement): Boolean {
244+
return kind is KtFakeSourceElementKind.DesugaredAugmentedAssign && (fir is FirFunctionCall || fir is FirThisReceiverExpression)
245+
}
246+
247+
// `FirSmartCastExpression` forward the source from the original expression,
248+
// and implicit receivers have fake sources pointing to a wider part of the expression.
249+
// Thus, `FirElementsRecorder` may try assigning an unnecessarily wide source
250+
// to smart cast expressions, which will affect the
251+
// `org.jetbrains.kotlin.idea.highlighting.highlighters.ExpressionsSmartcastHighlighter#highlightExpression`
252+
// function in intellij.git
253+
private fun KtSourceElement.isSourceForSmartCasts(fir: FirElement) =
254+
(kind is KtFakeSourceElementKind.SmartCastExpression) && fir is FirSmartCastExpression && !fir.originalExpression.isImplicitThisReceiver
255+
256+
private val FirExpression.isImplicitThisReceiver get() = this is FirThisReceiverExpression && this.isImplicit
257+
258+
private fun FirElement.isReadInCompoundCall(): Boolean {
259+
if (this is FirPropertyAccessExpression) return true
260+
if (this !is FirFunctionCall) return false
261+
val name = (calleeReference as? FirResolvedNamedReference)?.name ?: getFallbackCompoundCalleeName()
262+
return name == OperatorNameConventions.GET
263+
}
264+
265+
private fun FirElement.isWriteInCompoundCall(): Boolean {
266+
if (this is FirVariableAssignment) return true
267+
if (this !is FirFunctionCall) return false
268+
val name = (calleeReference as? FirResolvedNamedReference)?.name ?: getFallbackCompoundCalleeName()
269+
return name == OperatorNameConventions.SET || name in OperatorNameConventions.ASSIGNMENT_OPERATIONS
270+
}
271+
272+
/**
273+
* If the callee reference is not a [FirResolvedNamedReference], we can get the compound callee name from the source instead. For
274+
* example, if the callee reference is a [FirErrorNamedReference] with an unresolved name `plusAssign`, the operation element type from
275+
* the source will be `KtTokens.PLUSEQ`, which can be transformed to `plusAssign`.
276+
*/
277+
private fun FirElement.getFallbackCompoundCalleeName(): Name? {
278+
val psi = source.psi as? KtOperationExpression ?: return null
279+
val operationReference = psi.operationReference
280+
return operationReference.getAssignmentOperationName() ?: operationReference.getReferencedNameAsName()
281+
}
282+
283+
private fun KtSimpleNameExpression.getAssignmentOperationName(): Name? {
284+
val firOperation = getReferencedNameElementType().toFirOperationOrNull() ?: return null
285+
return FirOperationNameConventions.ASSIGNMENTS[firOperation]
286+
}
265287
}
266288
}

0 commit comments

Comments
 (0)