From ae6eeb2501b574225f530fe241acb4a7c964efa2 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Tue, 16 Mar 2021 11:42:30 +0100 Subject: [PATCH] FIR IDE: introduce KtBackingFieldSymbol --- .../VariableReferenceHighlightingVisitor.kt | 38 +++++++++-------- .../idea/frontend/api/symbols/KtSymbol.kt | 8 ++++ .../api/symbols/KtVariableLikeSymbol.kt | 32 ++++++++++++++ .../frontend/api/fir/KtSymbolByFirBuilder.kt | 17 +++++++- ...tFirSymbolContainingDeclarationProvider.kt | 6 +++ .../fir/symbols/KtFirBackingFieldSymbol.kt | 42 +++++++++++++++++++ .../KtFirBackingFieldSymbolPointer.kt | 27 ++++++++++++ .../references/FirReferenceResolveHelper.kt | 3 ++ .../symbolByReference/accessorField.kt | 5 +++ .../SymbolByReferenceTestGenerated.java | 5 +++ 10 files changed, 164 insertions(+), 19 deletions(-) create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/KtFirBackingFieldSymbol.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/pointers/KtFirBackingFieldSymbolPointer.kt create mode 100644 idea/idea-frontend-fir/testData/symbolByReference/accessorField.kt diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/VariableReferenceHighlightingVisitor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/VariableReferenceHighlightingVisitor.kt index b22dad28b3b..f13da630ba1 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/VariableReferenceHighlightingVisitor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/VariableReferenceHighlightingVisitor.kt @@ -15,6 +15,8 @@ import com.intellij.psi.util.parentOfType import org.jetbrains.kotlin.idea.KotlinIdeaAnalysisBundle import org.jetbrains.kotlin.idea.fir.highlighter.textAttributesKeyForPropertyDeclaration import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtBackingFieldSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol import org.jetbrains.kotlin.idea.highlighter.NameHighlighter import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.lexer.KtTokens @@ -40,21 +42,29 @@ internal class VariableReferenceHighlightingVisitor( return } - val target = expression.mainReference.resolve() - - when { - target != null && expression.isBackingField(target) -> Colors.BACKING_FIELD_VARIABLE - target is PsiMethod -> Colors.SYNTHETIC_EXTENSION_PROPERTY - target != null -> textAttributesKeyForPropertyDeclaration(target) - else -> null - }?.let { attribute -> - highlightName(expression, attribute) - if (target?.isMutableVariable() == true) { - highlightName(expression, Colors.MUTABLE_VARIABLE) + with(analysisSession) { + val targetSymbol = expression.mainReference.resolveToSymbol() + val target = expression.mainReference.resolve() + when { + targetSymbol is KtBackingFieldSymbol -> Colors.BACKING_FIELD_VARIABLE + target is PsiMethod -> Colors.SYNTHETIC_EXTENSION_PROPERTY + target != null -> textAttributesKeyForPropertyDeclaration(target) + else -> null + }?.let { attribute -> + highlightName(expression, attribute) + if (target?.isMutableVariable() == true || targetSymbol != null && isBackingFieldReferencingMutableVariable(targetSymbol)) { + highlightName(expression, Colors.MUTABLE_VARIABLE) + } } } } + @Suppress("unused") + private fun KtAnalysisSession.isBackingFieldReferencingMutableVariable(symbol: KtSymbol): Boolean { + if (symbol !is KtBackingFieldSymbol) return false + return !symbol.owningProperty.isVal + } + private fun KtSimpleNameExpression.isByNameArgumentReference() = parent is KtValueArgumentName @@ -75,9 +85,3 @@ private fun KtSimpleNameExpression.isAssignmentReference(): Boolean { return operationSignTokenType == KtTokens.EQ } -private fun KtSimpleNameExpression.isBackingField(target: PsiElement): Boolean { - if (getReferencedName() != "field") return false - if (target !is KtProperty) return false - val accessor = parentOfType() ?: return false - return accessor.parent == target -} diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtSymbol.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtSymbol.kt index 17470b68f33..3a4775ad71c 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtSymbol.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtSymbol.kt @@ -93,4 +93,12 @@ enum class KtSymbolOrigin { JAVA_SYNTHETIC_PROPERTY, + + /** + * Declaration is backing field of some member property + * A symbol kind of [KtBackingFieldSymbol] + * + * @see KtBackingFieldSymbol + */ + PROPERTY_BACKING_FIELD, } \ No newline at end of file diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtVariableLikeSymbol.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtVariableLikeSymbol.kt index ec5da4d74e2..107ce15d922 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtVariableLikeSymbol.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtVariableLikeSymbol.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.idea.frontend.api.symbols +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.* import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtSymbolPointer import org.jetbrains.kotlin.name.ClassId @@ -15,6 +16,33 @@ sealed class KtVariableLikeSymbol : KtCallableSymbol(), KtNamedSymbol, KtSymbolW abstract override fun createPointer(): KtSymbolPointer } +/** + * Backing field of some member property + * + * E.g, + * ``` + * val x: Int = 10 + * get() = field + * ``` + * + * Symbol at caret will be resolved to a [KtBackingFieldSymbol] + */ +abstract class KtBackingFieldSymbol : KtVariableLikeSymbol() { + abstract val owningProperty: KtKotlinPropertySymbol + + final override val name: Name get() = fieldName + final override val psi: PsiElement? get() = null + final override val symbolKind: KtSymbolKind get() = KtSymbolKind.LOCAL + final override val origin: KtSymbolOrigin get() = KtSymbolOrigin.PROPERTY_BACKING_FIELD + + abstract override fun createPointer(): KtSymbolPointer + + companion object { + private val fieldName = Name.identifier("field") + } +} + + abstract class KtEnumEntrySymbol : KtVariableLikeSymbol(), KtSymbolWithMembers, KtSymbolWithKind { final override val symbolKind: KtSymbolKind get() = KtSymbolKind.MEMBER abstract val containingEnumClassIdIfNonLocal: ClassId? @@ -69,6 +97,8 @@ abstract class KtKotlinPropertySymbol : KtPropertySymbol() { abstract val isLateInit: Boolean abstract val isConst: Boolean + + abstract override fun createPointer(): KtSymbolPointer } abstract class KtSyntheticJavaPropertySymbol : KtPropertySymbol() { @@ -80,6 +110,8 @@ abstract class KtSyntheticJavaPropertySymbol : KtPropertySymbol() { abstract val javaGetterName: Name abstract val javaSetterName: Name? + + abstract override fun createPointer(): KtSymbolPointer } abstract class KtLocalVariableSymbol : KtVariableSymbol(), KtSymbolWithKind { diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtSymbolByFirBuilder.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtSymbolByFirBuilder.kt index 26a2625f0c8..29a7fac5c1d 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtSymbolByFirBuilder.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtSymbolByFirBuilder.kt @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.fir.resolve.getSymbolByLookupTag import org.jetbrains.kotlin.fir.resolve.inference.isFunctionalType import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag +import org.jetbrains.kotlin.fir.symbols.impl.FirBackingFieldSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassifierSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.fir.types.* @@ -46,7 +47,8 @@ internal class KtSymbolByFirBuilder private constructor( val withReadOnlyCaching: Boolean, private val symbolsCache: BuilderCache, private val filesCache: BuilderCache, - private val typesCache: BuilderCache + private val backingFieldCache: BuilderCache, + private val typesCache: BuilderCache, ) : ValidityTokenOwner { private val resolveState by weakRef(resolveState) @@ -70,6 +72,7 @@ internal class KtSymbolByFirBuilder private constructor( withReadOnlyCaching = false, symbolsCache = BuilderCache(), typesCache = BuilderCache(), + backingFieldCache = BuilderCache(), filesCache = BuilderCache(), ) @@ -84,6 +87,7 @@ internal class KtSymbolByFirBuilder private constructor( symbolsCache = symbolsCache.createReadOnlyCopy(), typesCache = typesCache.createReadOnlyCopy(), filesCache = filesCache.createReadOnlyCopy(), + backingFieldCache = backingFieldCache.createReadOnlyCopy(), ) } @@ -215,7 +219,7 @@ internal class KtSymbolByFirBuilder private constructor( } } - fun buildPropertySymbol(fir: FirProperty): KtPropertySymbol { + fun buildPropertySymbol(fir: FirProperty): KtKotlinPropertySymbol { require(!fir.isLocal) require(fir !is FirSyntheticProperty) return symbolsCache.cache(fir) { @@ -262,6 +266,15 @@ internal class KtSymbolByFirBuilder private constructor( return symbolsCache.cache(fir) { KtFirJavaFieldSymbol(fir, resolveState, token, this@KtSymbolByFirBuilder) } } + fun buildBackingFieldSymbol(fir: FirBackingFieldSymbol): KtFirBackingFieldSymbol { + return backingFieldCache.cache(fir) { KtFirBackingFieldSymbol(fir.fir, resolveState, token, this@KtSymbolByFirBuilder) } + } + + fun buildBackingFieldSymbolByProperty(fir: FirProperty): KtFirBackingFieldSymbol { + val backingFieldSymbol = fir.backingFieldSymbol + return buildBackingFieldSymbol(backingFieldSymbol) + } + private fun FirField.isJavaFieldOrSubstitutionOverrideOfJavaField(): Boolean = when (this) { is FirJavaField -> true is FirFieldImpl -> (this as FirField).originalForSubstitutionOverride?.isJavaFieldOrSubstitutionOverrideOfJavaField() == true diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolContainingDeclarationProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolContainingDeclarationProvider.kt index 8cccac83ca4..a29bf1b5ac7 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolContainingDeclarationProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolContainingDeclarationProvider.kt @@ -32,12 +32,18 @@ internal class KtFirSymbolContainingDeclarationProvider( getContainingDeclarationForKotlinInSourceSymbol(symbol) KtSymbolOrigin.LIBRARY, KtSymbolOrigin.JAVA, KtSymbolOrigin.JAVA_SYNTHETIC_PROPERTY -> getContainingDeclarationForLibrarySymbol(symbol) + KtSymbolOrigin.PROPERTY_BACKING_FIELD -> getContainingDeclarationForBackingFieldSymbol(symbol) KtSymbolOrigin.INTERSECTION_OVERRIDE -> TODO() KtSymbolOrigin.SAM_CONSTRUCTOR -> TODO() KtSymbolOrigin.DELEGATED -> TODO() } } + private fun getContainingDeclarationForBackingFieldSymbol(symbol: KtSymbolWithKind): KtSymbolWithKind { + require(symbol is KtBackingFieldSymbol) + return symbol.owningProperty + } + private fun getContainingDeclarationForKotlinInSourceSymbol(symbol: KtSymbolWithKind): KtSymbolWithKind = with(analysisSession) { require(symbol.origin == KtSymbolOrigin.SOURCE || symbol.origin == KtSymbolOrigin.SOURCE_MEMBER_GENERATED) val psi = symbol.getPsi() diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/KtFirBackingFieldSymbol.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/KtFirBackingFieldSymbol.kt new file mode 100644 index 00000000000..a6b0b516507 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/KtFirBackingFieldSymbol.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2020 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.idea.frontend.api.fir.symbols + +import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.declarations.FirResolvePhase +import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState +import org.jetbrains.kotlin.idea.frontend.api.ValidityToken +import org.jetbrains.kotlin.idea.frontend.api.fir.KtSymbolByFirBuilder +import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.pointers.KtFirBackingFieldSymbolPointer +import org.jetbrains.kotlin.idea.frontend.api.fir.utils.cached +import org.jetbrains.kotlin.idea.frontend.api.fir.utils.firRef +import org.jetbrains.kotlin.idea.frontend.api.fir.utils.weakRef +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtBackingFieldSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtKotlinPropertySymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtTypeAndAnnotations +import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtSymbolPointer + +internal class KtFirBackingFieldSymbol( + propertyFir: FirProperty, + resolveState: FirModuleResolveState, + override val token: ValidityToken, + _builder: KtSymbolByFirBuilder +) : KtBackingFieldSymbol(){ + private val builder by weakRef(_builder) + private val propertyFirRef = firRef(propertyFir, resolveState) + + override val annotatedType: KtTypeAndAnnotations by cached { + propertyFirRef.returnTypeAndAnnotations(FirResolvePhase.TYPES, builder) + } + + override val owningProperty: KtKotlinPropertySymbol by propertyFirRef.withFirAndCache { fir -> + builder.variableLikeBuilder.buildPropertySymbol(fir) + } + + override fun createPointer(): KtSymbolPointer { + return KtFirBackingFieldSymbolPointer(owningProperty.createPointer()) + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/pointers/KtFirBackingFieldSymbolPointer.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/pointers/KtFirBackingFieldSymbolPointer.kt new file mode 100644 index 00000000000..90969115978 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/pointers/KtFirBackingFieldSymbolPointer.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2020 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.idea.frontend.api.fir.symbols.pointers + +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirKotlinPropertySymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtBackingFieldSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtKotlinPropertySymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtSymbolPointer + +internal class KtFirBackingFieldSymbolPointer( + private val propertySymbolPointer: KtSymbolPointer, +) : KtSymbolPointer() { + override fun restoreSymbol(analysisSession: KtAnalysisSession): KtBackingFieldSymbol? { + require(analysisSession is KtFirAnalysisSession) + val propertySymbol = propertySymbolPointer.restoreSymbol(analysisSession) ?: return null + check(propertySymbol is KtFirKotlinPropertySymbol) + return propertySymbol.firRef.withFir { firProperty -> + analysisSession.firSymbolBuilder.variableLikeBuilder.buildBackingFieldSymbolByProperty(firProperty) + } + } +} + diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt index 850a7104819..37824acdd3a 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt @@ -81,6 +81,9 @@ internal object FirReferenceResolveHelper { fun FirReference.toTargetSymbol(session: FirSession, symbolBuilder: KtSymbolByFirBuilder): Collection { return when (this) { + is FirBackingFieldReference -> { + listOfNotNull(symbolBuilder.variableLikeBuilder.buildBackingFieldSymbol(resolvedSymbol)) + } is FirResolvedNamedReference -> { val fir = when (val symbol = resolvedSymbol) { is SyntheticPropertySymbol -> { diff --git a/idea/idea-frontend-fir/testData/symbolByReference/accessorField.kt b/idea/idea-frontend-fir/testData/symbolByReference/accessorField.kt new file mode 100644 index 00000000000..fd5ca20e60c --- /dev/null +++ b/idea/idea-frontend-fir/testData/symbolByReference/accessorField.kt @@ -0,0 +1,5 @@ +var x: Int = 0 + get() = field + set(value) { + field = value + } \ No newline at end of file diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/SymbolByReferenceTestGenerated.java b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/SymbolByReferenceTestGenerated.java index 6d30bf539fa..cca6feb9e38 100644 --- a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/SymbolByReferenceTestGenerated.java +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/symbols/SymbolByReferenceTestGenerated.java @@ -25,6 +25,11 @@ public class SymbolByReferenceTestGenerated extends AbstractSymbolByReferenceTes KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); } + @TestMetadata("accessorField.kt") + public void testAccessorField() throws Exception { + runTest("idea/idea-frontend-fir/testData/symbolByReference/accessorField.kt"); + } + public void testAllFilesPresentInSymbolByReference() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/symbolByReference"), Pattern.compile("^(.+)\\.kt$"), null, true); }