FIR IDE: introduce KtBackingFieldSymbol

This commit is contained in:
Ilya Kirillov
2021-03-16 11:42:30 +01:00
parent 443a14756f
commit ae6eeb2501
10 changed files with 164 additions and 19 deletions
@@ -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<KtPropertyAccessor>() ?: return false
return accessor.parent == target
}
@@ -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,
}
@@ -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<KtVariableLikeSymbol>
}
/**
* Backing field of some member property
*
* E.g,
* ```
* val x: Int = 10
* get() = field<caret>
* ```
*
* 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<KtVariableLikeSymbol>
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<KtKotlinPropertySymbol>
}
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<KtSyntheticJavaPropertySymbol>
}
abstract class KtLocalVariableSymbol : KtVariableSymbol(), KtSymbolWithKind {
@@ -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<FirDeclaration, KtSymbol>,
private val filesCache: BuilderCache<FirFile, KtFileSymbol>,
private val typesCache: BuilderCache<ConeKotlinType, KtType>
private val backingFieldCache: BuilderCache<FirBackingFieldSymbol, KtBackingFieldSymbol>,
private val typesCache: BuilderCache<ConeKotlinType, KtType>,
) : 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
@@ -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()
@@ -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<KtBackingFieldSymbol> {
return KtFirBackingFieldSymbolPointer(owningProperty.createPointer())
}
}
@@ -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<KtKotlinPropertySymbol>,
) : KtSymbolPointer<KtBackingFieldSymbol>() {
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)
}
}
}
@@ -81,6 +81,9 @@ internal object FirReferenceResolveHelper {
fun FirReference.toTargetSymbol(session: FirSession, symbolBuilder: KtSymbolByFirBuilder): Collection<KtSymbol> {
return when (this) {
is FirBackingFieldReference -> {
listOfNotNull(symbolBuilder.variableLikeBuilder.buildBackingFieldSymbol(resolvedSymbol))
}
is FirResolvedNamedReference -> {
val fir = when (val symbol = resolvedSymbol) {
is SyntheticPropertySymbol -> {
@@ -0,0 +1,5 @@
var x: Int = 0
get() = <caret>field
set(value) {
field = value
}
@@ -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);
}