FIR IDE: resolve call to property accessors

This commit is contained in:
Jinseong Jeon
2021-08-25 00:24:06 -07:00
committed by Ilya Kirillov
parent cd4a08cb18
commit 8e242655f9
27 changed files with 278 additions and 13 deletions
@@ -381,6 +381,14 @@ fun KtExpression.getAssignmentByLHS(): KtBinaryExpression? {
return if (KtPsiUtil.isAssignment(parent) && parent.left == this) parent else null
}
tailrec fun findAssignment(element: PsiElement?): KtBinaryExpression? =
when (val parent = element?.parent) {
is KtBinaryExpression -> if (parent.left == element && parent.operationToken == KtTokens.EQ) parent else null
is KtQualifiedExpression -> findAssignment(element.parent)
is KtSimpleNameExpression -> findAssignment(element.parent)
else -> null
}
fun KtStringTemplateExpression.getContentRange(): TextRange {
val start = node.firstChildNode.textLength
val lastChild = node.lastChildNode
@@ -6,12 +6,10 @@
package org.jetbrains.kotlin.idea.frontend.api.components
import org.jetbrains.kotlin.idea.frontend.api.calls.KtCall
import org.jetbrains.kotlin.psi.KtArrayAccessExpression
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtCallElement
import org.jetbrains.kotlin.psi.KtUnaryExpression
import org.jetbrains.kotlin.psi.*
public abstract class KtCallResolver : KtAnalysisSessionComponent() {
public abstract fun resolveAccessorCall(call: KtSimpleNameExpression): KtCall?
public abstract fun resolveCall(call: KtCallElement): KtCall?
public abstract fun resolveCall(call: KtBinaryExpression): KtCall?
public abstract fun resolveCall(call: KtUnaryExpression): KtCall?
@@ -19,6 +17,14 @@ public abstract class KtCallResolver : KtAnalysisSessionComponent() {
}
public interface KtCallResolverMixIn : KtAnalysisSessionMixIn {
/**
* Resolves the given simple name expression to an accessor call if that name refers to a property.
*
* This spans both Kotlin property and synthetic Java property.
*/
public fun KtSimpleNameExpression.resolveAccessorCall(): KtCall? =
analysisSession.callResolver.resolveAccessorCall(this)
public fun KtCallElement.resolveCall(): KtCall? =
analysisSession.callResolver.resolveCall(this)
@@ -7,18 +7,15 @@ package org.jetbrains.kotlin.idea.frontend.api.symbols
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.*
import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.name.CallableId
public sealed class KtPropertyAccessorSymbol : KtCallableSymbol(),
public sealed class KtPropertyAccessorSymbol : KtFunctionLikeSymbol(),
KtPossibleMemberSymbol,
KtSymbolWithModality,
KtSymbolWithVisibility,
KtAnnotatedSymbol,
KtSymbolWithKind {
final override val callableIdIfNonLocal: CallableId? get() = null
final override val isExtension: Boolean get() = false
final override val receiverType: KtTypeAndAnnotations? get() = null
public abstract val isDefault: Boolean
public abstract val isInline: Boolean
@@ -38,4 +35,4 @@ public abstract class KtPropertySetterSymbol : KtPropertyAccessorSymbol() {
public abstract val parameter: KtValueParameterSymbol
abstract override fun createPointer(): KtSymbolPointer<KtPropertySetterSymbol>
}
}
@@ -200,6 +200,7 @@ internal class KtSymbolByFirBuilder private constructor(
}
is FirConstructor -> buildConstructorSymbol(fir)
is FirAnonymousFunction -> buildAnonymousFunctionSymbol(fir)
is FirPropertyAccessor -> buildPropertyAccessorSymbol(fir)
else -> throwUnexpectedElementError(fir)
}
}
@@ -224,6 +225,16 @@ internal class KtSymbolByFirBuilder private constructor(
check(fir.origin == FirDeclarationOrigin.SamConstructor)
return symbolsCache.cache(fir) { KtFirSamConstructorSymbol(fir, resolveState, token, this@KtSymbolByFirBuilder) }
}
fun buildPropertyAccessorSymbol(fir: FirPropertyAccessor): KtFunctionLikeSymbol {
return symbolsCache.cache(fir) {
if (fir.isGetter) {
KtFirPropertyGetterSymbol(fir, resolveState, token, this@KtSymbolByFirBuilder)
} else {
KtFirPropertySetterSymbol(fir, resolveState, token, this@KtSymbolByFirBuilder)
}
}
}
}
inner class VariableLikeSymbolBuilder {
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.references.FirSuperReference
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.resolve.calls.FirErrorReferenceWithCandidate
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType
import org.jetbrains.kotlin.fir.types.classId
@@ -36,8 +37,10 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithMember
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper
import org.jetbrains.kotlin.idea.references.readWriteAccess
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.findAssignment
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -47,6 +50,37 @@ internal class KtFirCallResolver(
) : KtCallResolver(), KtFirAnalysisSessionComponent {
private val diagnosticCache = mutableListOf<FirDiagnostic>()
override fun resolveAccessorCall(call: KtSimpleNameExpression): KtCall? = withValidityAssertion {
when (val fir = call.getOrBuildFir(firResolveState)) {
is FirResolvedNamedReference -> {
val propertySymbol = fir.resolvedSymbol as? FirPropertySymbol ?: return null
val access = call.readWriteAccess(useResolveForReadWrite = false)
val setterValue = findAssignment(call)?.right
val accessor = when {
access.isWrite -> propertySymbol.setterSymbol?.fir
access.isRead -> propertySymbol.getterSymbol?.fir
else -> null
} ?: return null
val accessorSymbol = analysisSession.firSymbolBuilder.functionLikeBuilder.buildFunctionLikeSymbol(accessor)
val target =
if (!access.isWrite || setterValue != null)
KtSuccessCallTarget(accessorSymbol)
else // access.isWrite && setterValue == null
KtErrorCallTarget(
listOf(accessorSymbol),
KtNonBoundToPsiErrorDiagnostic(factoryName = null, "Setter value is missing", token)
)
val ktArgumentMapping = LinkedHashMap<KtExpression, KtValueParameterSymbol>()
if (access.isWrite && setterValue != null) {
val setterParameterSymbol = accessor.valueParameters.single().buildSymbol(firSymbolBuilder) as KtValueParameterSymbol
ktArgumentMapping[setterValue] = setterParameterSymbol
}
return KtFunctionCall(ktArgumentMapping, target)
}
else -> return null
}
}
override fun resolveCall(call: KtBinaryExpression): KtCall? = withValidityAssertion {
when (val fir = call.getOrBuildFir(firResolveState)) {
is FirFunctionCall -> resolveCall(fir)
@@ -11,8 +11,10 @@ import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.synthetic.FirSyntheticPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.declarations.utils.isOverride
import org.jetbrains.kotlin.fir.resolve.getHasStableParameterNames
import org.jetbrains.kotlin.idea.fir.findPsi
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.frontend.api.fir.KtSymbolByFirBuilder
@@ -23,12 +25,14 @@ 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.KtPropertyGetterSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtValueParameterSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtAnnotationCall
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtTypeAndAnnotations
import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtPsiBasedSymbolPointer
import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
internal class KtFirPropertyGetterSymbol(
@@ -59,10 +63,29 @@ internal class KtFirPropertyGetterSymbol(
override fun containsAnnotation(classId: ClassId): Boolean = firRef.containsAnnotation(classId)
override val annotationClassIds: Collection<ClassId> by cached { firRef.getAnnotationClassIds() }
/**
* Returns [CallableId] of the delegated Java method if the corresponding property of this getter is a synthetic Java property.
* Otherwise, returns `null`
*/
override val callableIdIfNonLocal: CallableId? by firRef.withFirAndCache { fir ->
if (fir is FirSyntheticPropertyAccessor) {
fir.delegate.symbol.callableId
} else null
}
override val dispatchType: KtType? by cached {
firRef.dispatchReceiverTypeAndAnnotations(builder)
}
override val receiverType: KtTypeAndAnnotations? by cached {
firRef.receiverTypeAndAnnotations(builder)
}
override val valueParameters: List<KtValueParameterSymbol>
get() = emptyList()
override val hasStableParameterNames: Boolean = firRef.withFir { it.getHasStableParameterNames(it.moduleData.session) }
override fun createPointer(): KtSymbolPointer<KtPropertyGetterSymbol> {
KtPsiBasedSymbolPointer.createForSymbolFromSource(this)?.let { return it }
TODO("Creating pointers for getters from library is not supported yet")
@@ -11,8 +11,10 @@ import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.synthetic.FirSyntheticPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.declarations.utils.isOverride
import org.jetbrains.kotlin.fir.resolve.getHasStableParameterNames
import org.jetbrains.kotlin.idea.fir.findPsi
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.frontend.api.fir.KtSymbolByFirBuilder
@@ -30,6 +32,7 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtPsiBasedSymbolP
import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
internal class KtFirPropertySetterSymbol(
@@ -58,10 +61,22 @@ internal class KtFirPropertySetterSymbol(
override fun containsAnnotation(classId: ClassId): Boolean = firRef.containsAnnotation(classId)
override val annotationClassIds: Collection<ClassId> by cached { firRef.getAnnotationClassIds() }
/**
* Returns [CallableId] of the delegated Java method if the corresponding property of this setter is a synthetic Java property.
* Otherwise, returns `null`
*/
override val callableIdIfNonLocal: CallableId? by firRef.withFirAndCache { fir ->
if (fir is FirSyntheticPropertyAccessor) {
fir.delegate.symbol.callableId
} else null
}
override val parameter: KtValueParameterSymbol by firRef.withFirAndCache { fir ->
builder.variableLikeBuilder.buildValueParameterSymbol(fir.valueParameters.single())
}
override val valueParameters: List<KtValueParameterSymbol> by cached { listOf(parameter) }
override val annotatedType: KtTypeAndAnnotations by cached {
firRef.returnTypeAndAnnotations(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE, builder)
}
@@ -70,6 +85,12 @@ internal class KtFirPropertySetterSymbol(
firRef.dispatchReceiverTypeAndAnnotations(builder)
}
override val receiverType: KtTypeAndAnnotations? by cached {
firRef.receiverTypeAndAnnotations(builder)
}
override val hasStableParameterNames: Boolean = firRef.withFir { it.getHasStableParameterNames(it.moduleData.session) }
override fun createPointer(): KtSymbolPointer<KtPropertySetterSymbol> {
KtPsiBasedSymbolPointer.createForSymbolFromSource(this)?.let { return it }
TODO("Creating pointers for setters from library is not supported yet")
@@ -0,0 +1,10 @@
// FILE: call.kt
fun call() {
val javaClass = JavaClass()
javaClass.<expr>foo</expr>
}
// FILE: JavaClass.java
class JavaClass {
int getFoo() { return 42; }
}
@@ -0,0 +1,3 @@
KtFunctionCall:
argumentMapping = { }
targetFunction = /JavaClass.getFoo(): kotlin.Int
@@ -0,0 +1,18 @@
// FILE: call.kt
fun call() {
val javaClass = JavaClass()
javaClass.<expr>sub</expr>.foo = 42
}
// FILE: JavaClass.java
class JavaClass {
private JavaSubClass instance = new JavaSubClass();
JavaSubClass getSub() { return instance; }
}
// FILE: JavaSubClass.java
class JavaSubClass {
private int foo = -1;
int getFoo() { return foo; }
void setFoo(int v) { foo = v; }
}
@@ -0,0 +1,3 @@
KtFunctionCall:
argumentMapping = { }
targetFunction = /JavaClass.getSub(): ft<JavaSubClass, JavaSubClass?>
@@ -0,0 +1,12 @@
// FILE: call.kt
fun call() {
val javaClass = JavaClass()
javaClass.<expr>foo</expr> = 42
}
// FILE: JavaClass.java
class JavaClass {
private int foo = -1;
int getFoo() { return foo; }
void setFoo(int v) { foo = v; }
}
@@ -0,0 +1,3 @@
KtFunctionCall:
argumentMapping = { 42 -> (v: kotlin.Int) }
targetFunction = /JavaClass.setFoo(v: kotlin.Int): kotlin.Unit
@@ -0,0 +1,13 @@
// FILE: call.kt
fun call() {
val javaClass = JavaClass()
// Intentionally incomplete to see if `foo` refers to `setFoo`.
javaClass.<expr>foo</expr> =
}
// FILE: JavaClass.java
class JavaClass {
private int foo = -1;
int getFoo() { return foo; }
void setFoo(int v) { foo = v; }
}
@@ -0,0 +1,3 @@
KtFunctionCall:
argumentMapping = { }
targetFunction = ERR<Setter value is missing, [/JavaClass.setFoo(v: kotlin.Int): kotlin.Unit]>
@@ -0,0 +1,9 @@
fun call {
val ktClass = KtClass()
ktClass.<expr>foo</expr>
}
class KtClass {
val foo: Int
get() = 42
}
@@ -0,0 +1,3 @@
KtFunctionCall:
argumentMapping = { }
targetFunction = <getter>(): kotlin.Int
@@ -0,0 +1,13 @@
fun call() {
val ktClass = KtClass()
ktClass.<expr>instance</expr>.foo = 42
}
class KtClass {
val instance : KtSubClass = KtSubClass()
}
class KtSubClass {
var foo : Int = -1
}
@@ -0,0 +1,3 @@
KtFunctionCall:
argumentMapping = { }
targetFunction = <getter>(): KtSubClass
@@ -0,0 +1,8 @@
fun call {
val ktClass = KtClass()
ktClass.<expr>foo</expr> = 42
}
class KtClass {
var foo : Int = -1
}
@@ -0,0 +1,3 @@
KtFunctionCall:
argumentMapping = { 42 -> (value: kotlin.Int) }
targetFunction = <setter>(value: kotlin.Int): kotlin.Unit
@@ -39,6 +39,7 @@ KtFirPropertyGetterSymbol:
callableIdIfNonLocal: null
dispatchType: null
hasBody: true
hasStableParameterNames: true
isDefault: false
isExtension: false
isInline: false
@@ -47,6 +48,7 @@ KtFirPropertyGetterSymbol:
origin: SOURCE
receiverType: null
symbolKind: ACCESSOR
valueParameters: []
visibility: Public
deprecationStatus: null
@@ -49,6 +49,7 @@ KtFirPropertyGetterSymbol:
callableIdIfNonLocal: null
dispatchType: null
hasBody: true
hasStableParameterNames: true
isDefault: false
isExtension: false
isInline: false
@@ -57,6 +58,7 @@ KtFirPropertyGetterSymbol:
origin: SOURCE
receiverType: null
symbolKind: ACCESSOR
valueParameters: []
visibility: Public
deprecationStatus: null
@@ -86,6 +88,7 @@ KtFirPropertySetterSymbol:
callableIdIfNonLocal: null
dispatchType: null
hasBody: true
hasStableParameterNames: true
isDefault: false
isExtension: false
isInline: false
@@ -95,6 +98,9 @@ KtFirPropertySetterSymbol:
parameter: KtFirValueParameterSymbol(value)
receiverType: null
symbolKind: ACCESSOR
valueParameters: [
KtFirValueParameterSymbol(value)
]
visibility: Public
deprecationStatus: null
@@ -36,6 +36,7 @@ KtFirPropertyGetterSymbol:
callableIdIfNonLocal: null
dispatchType: null
hasBody: true
hasStableParameterNames: true
isDefault: false
isExtension: false
isInline: false
@@ -44,6 +45,7 @@ KtFirPropertyGetterSymbol:
origin: SOURCE
receiverType: null
symbolKind: ACCESSOR
valueParameters: []
visibility: Public
deprecationStatus: null
@@ -36,6 +36,7 @@ KtFirPropertyGetterSymbol:
callableIdIfNonLocal: null
dispatchType: null
hasBody: true
hasStableParameterNames: true
isDefault: false
isExtension: false
isInline: false
@@ -44,6 +45,7 @@ KtFirPropertyGetterSymbol:
origin: SOURCE
receiverType: null
symbolKind: ACCESSOR
valueParameters: []
visibility: Public
deprecationStatus: null
@@ -15,9 +15,7 @@ import org.jetbrains.kotlin.idea.frontend.api.calls.KtCall
import org.jetbrains.kotlin.idea.frontend.api.calls.KtDelegatedConstructorCallKind
import org.jetbrains.kotlin.idea.frontend.api.calls.KtErrorCallTarget
import org.jetbrains.kotlin.idea.frontend.api.calls.KtSuccessCallTarget
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtValueParameterSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.test.model.TestModule
@@ -46,6 +44,7 @@ abstract class AbstractResolveCallTest : AbstractHLApiSingleModuleTest() {
is KtBinaryExpression -> element.resolveCall()
is KtUnaryExpression -> element.resolveCall()
is KtArrayAccessExpression -> element.resolveCall()
is KtSimpleNameExpression -> element.resolveAccessorCall()
is KtValueArgument -> resolveCall(element.getArgumentExpression()!!)
else -> error("Selected element type (${element::class.simpleName}) is not supported for resolveCall()")
}
@@ -56,7 +55,15 @@ private fun KtCall.stringRepresentation(): String {
fun KtType.render() = asStringForDebugging().replace('/', '.')
fun Any.stringValue(): String = when (this) {
is KtFunctionLikeSymbol -> buildString {
append(if (this@stringValue is KtFunctionSymbol) callableIdIfNonLocal ?: name else "<constructor>")
append(
when (this@stringValue) {
is KtFunctionSymbol -> callableIdIfNonLocal ?: name
is KtConstructorSymbol -> "<constructor>"
is KtPropertyGetterSymbol -> callableIdIfNonLocal ?: "<getter>"
is KtPropertySetterSymbol -> callableIdIfNonLocal ?: "<setter>"
else -> error("unexpected symbol kind in KtCall: ${this@stringValue::class.java}")
}
)
append("(")
(this@stringValue as? KtFunctionSymbol)?.receiverType?.let { receiver ->
append("<receiver>: ${receiver.type.render()}")
@@ -156,6 +156,48 @@ public class ResolveCallTestGenerated extends AbstractResolveCallTest {
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt");
}
@Test
@TestMetadata("javaPropertyGetter.kt")
public void testJavaPropertyGetter() throws Exception {
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaPropertyGetter.kt");
}
@Test
@TestMetadata("javaPropertyNestedGetter.kt")
public void testJavaPropertyNestedGetter() throws Exception {
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaPropertyNestedGetter.kt");
}
@Test
@TestMetadata("javaPropertySetter.kt")
public void testJavaPropertySetter() throws Exception {
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaPropertySetter.kt");
}
@Test
@TestMetadata("javaPropertySetterIncomplete.kt")
public void testJavaPropertySetterIncomplete() throws Exception {
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaPropertySetterIncomplete.kt");
}
@Test
@TestMetadata("kotlinPropertyGetter.kt")
public void testKotlinPropertyGetter() throws Exception {
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/kotlinPropertyGetter.kt");
}
@Test
@TestMetadata("kotlinPropertyNestedGetter.kt")
public void testKotlinPropertyNestedGetter() throws Exception {
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/kotlinPropertyNestedGetter.kt");
}
@Test
@TestMetadata("kotlinPropertySetter.kt")
public void testKotlinPropertySetter() throws Exception {
runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/kotlinPropertySetter.kt");
}
@Test
@TestMetadata("resolveCallInSuperConstructorParam.kt")
public void testResolveCallInSuperConstructorParam() throws Exception {