|
1 | 1 | /*
|
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. |
3 | 3 | * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
4 | 4 | */
|
5 | 5 |
|
6 | 6 | package org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure
|
7 | 7 |
|
| 8 | +import com.intellij.psi.PsiElement |
8 | 9 | import org.jetbrains.kotlin.*
|
9 | 10 | import org.jetbrains.kotlin.analysis.low.level.api.fir.element.builder.DuplicatedFirSourceElementsException
|
10 | 11 | import org.jetbrains.kotlin.analysis.low.level.api.fir.util.isErrorElement
|
11 | 12 | import org.jetbrains.kotlin.fir.FirElement
|
12 | 13 | import org.jetbrains.kotlin.fir.builder.toFirOperationOrNull
|
13 | 14 | import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
|
14 | 15 | import org.jetbrains.kotlin.fir.expressions.*
|
15 |
| -import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression |
16 | 16 | import org.jetbrains.kotlin.fir.expressions.builder.buildLiteralExpression
|
17 | 17 | import org.jetbrains.kotlin.fir.psi
|
18 | 18 | import org.jetbrains.kotlin.fir.references.*
|
@@ -122,99 +122,10 @@ internal open class FirElementsRecorder : FirVisitor<Unit, MutableMap<KtElement,
|
122 | 122 | }
|
123 | 123 |
|
124 | 124 | 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 |
142 | 126 | cache(psi, element, cache)
|
143 | 127 | }
|
144 | 128 |
|
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 |
| - |
218 | 129 | private val FirLiteralExpression.isConverted: Boolean
|
219 | 130 | get() {
|
220 | 131 | val firSourcePsi = this.source?.psi ?: return false
|
@@ -262,5 +173,116 @@ internal open class FirElementsRecorder : FirVisitor<Unit, MutableMap<KtElement,
|
262 | 173 | companion object {
|
263 | 174 | fun recordElementsFrom(firElement: FirElement, recorder: FirElementsRecorder): Map<KtElement, FirElement> =
|
264 | 175 | 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 | + } |
265 | 287 | }
|
266 | 288 | }
|
0 commit comments