[ObjCExport] Add inner classes translation

KT-66339
This commit is contained in:
eugene.levenetc
2024-03-07 15:08:21 +01:00
committed by Space Team
parent 5fee662223
commit f7f779df53
20 changed files with 497 additions and 76 deletions
@@ -48,12 +48,15 @@ internal fun KtVariableLikeSymbol.getPropertyMethodBridge(): MethodBridge {
context(KtAnalysisSession)
private val KtCallableSymbol.receiverType: MethodBridgeReceiver
get() = if (isArrayConstructor) {
MethodBridgeReceiver.Factory
} else if (!isConstructor && isTopLevel && !isExtension) {
MethodBridgeReceiver.Static
} else {
MethodBridgeReceiver.Instance
get() {
val isProperty = getContainingSymbol() as? KtPropertySymbol != null
return if (isArrayConstructor) {
MethodBridgeReceiver.Factory
} else if (!isConstructor && isTopLevel && !isExtension && !isProperty) {
MethodBridgeReceiver.Static
} else {
MethodBridgeReceiver.Instance
}
}
/**
@@ -148,12 +151,6 @@ private fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue {
return MethodBridge.ReturnValue.HashCode
}
//TODO: handle getter
// descriptor is PropertyGetterDescriptor -> {
// assert(!convertExceptionsToErrors)
// MethodBridge.ReturnValue.Mapped(bridgePropertyType(descriptor.correspondingProperty))
// }
if (returnType.isUnit || returnType.isNothing) {
return if (hasThrowsAnnotation) {
MethodBridge.ReturnValue.WithError.Success
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2024 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.objcexport.analysisApiUtils
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertyAccessorSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.tooling.core.linearClosure
context(KtAnalysisSession)
internal fun KtPropertyAccessorSymbol.getPropertySymbol(): KtPropertySymbol {
return this.linearClosure<KtSymbol> { it.getContainingSymbol() }.filterIsInstance<KtPropertySymbol>().firstOrNull()
?: error("Missing '${KtPropertySymbol::class} on ${this.render()}")
}
@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportFunctionName
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getPropertySymbol
context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionLikeSymbol.getObjCFunctionName(): ObjCExportFunctionName {
@@ -13,20 +14,41 @@ fun KtFunctionLikeSymbol.getObjCFunctionName(): ObjCExportFunctionName {
)
}
context(KtAnalysisSession)
private fun KtFunctionLikeSymbol.getObjCName(resolvedNameAnnotation: KtResolvedObjCNameAnnotation?): String {
return resolvedNameAnnotation?.objCName ?: translationName
}
context(KtAnalysisSession)
private fun KtFunctionLikeSymbol.getSwiftName(resolvedNameAnnotation: KtResolvedObjCNameAnnotation?): String {
return resolvedNameAnnotation?.swiftName ?: translationName
}
context(KtAnalysisSession)
private val KtFunctionLikeSymbol.translationName: String
get() = when (this) {
is KtFunctionSymbol -> name.asString()
is KtConstructorSymbol -> "init"
is KtPropertyGetterSymbol -> ""
is KtAnonymousFunctionSymbol -> ""
is KtPropertySetterSymbol -> ""
is KtSamConstructorSymbol -> ""
get() {
return when (this) {
is KtFunctionSymbol -> name.asString()
is KtConstructorSymbol -> "init"
is KtPropertyAccessorSymbol -> this.objCPropertyName
is KtAnonymousFunctionSymbol -> ""
is KtSamConstructorSymbol -> ""
}
}
context(KtAnalysisSession)
private val KtPropertyAccessorSymbol.objCPropertyName: String
get() {
return if (!this.getPropertySymbol().isObjCProperty) {
val containingSymbol = getContainingSymbol()
when (this) {
is KtPropertyGetterSymbol -> {
(containingSymbol as KtPropertySymbol).name.asString()
}
is KtPropertySetterSymbol -> {
"set" + ((containingSymbol as KtPropertySymbol).name.asString()).replaceFirstChar(kotlin.Char::uppercaseChar)
}
else -> ""
}
} else ""
}
@@ -0,0 +1,24 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.tooling.core.linearClosure
/**
* Property needs to be translated and support special naming in 2 cases:
* 1. It has no receiver type and no extension
* 2. It is a property of an inner class
*
* See K1 [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.isObjCProperty]
*/
context(KtAnalysisSession)
internal val KtPropertySymbol.isObjCProperty: Boolean
get() {
val hasReceiver = receiverParameter != null && !isExtension
return !hasReceiver && !isPropertyInInnerClass
}
context(KtAnalysisSession)
private val KtPropertySymbol.isPropertyInInnerClass: Boolean
get() = linearClosure<KtSymbol> { symbol -> symbol.getContainingSymbol() }
.any { it is KtNamedClassOrObjectSymbol && it.isInner }
@@ -36,7 +36,7 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
this += getCallableSymbolsForObjCMemberTranslation()
.sortedWith(StableCallableOrder)
.mapNotNull { it.translateToObjCExportStub() }
.flatMap { it.translateToObjCExportStub() }
if (classKind == KtClassKind.ENUM_CLASS) {
this += translateEnumMembers()
@@ -4,14 +4,24 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClass
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub
import org.jetbrains.kotlin.utils.addIfNotNull
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtCallableSymbol.translateToObjCExportStub(): ObjCExportStub? {
return when (this) {
is KtPropertySymbol -> translateToObjCProperty()
is KtFunctionSymbol -> translateToObjCMethod()
else -> null
internal fun KtCallableSymbol.translateToObjCExportStub(): List<ObjCExportStub> {
val result = mutableListOf<ObjCExportStub>()
when (this) {
is KtPropertySymbol -> {
if (isObjCProperty) {
result.addIfNotNull(translateToObjCProperty())
} else {
result.addIfNotNull(this.getter?.translateToObjCMethod())
result.addIfNotNull(this.setter?.translateToObjCMethod())
}
}
is KtFunctionSymbol -> result.addIfNotNull(translateToObjCMethod())
else -> Unit
}
return result
}
context(KtAnalysisSession, KtObjCExportSession)
@@ -64,7 +64,7 @@ fun KtResolvedObjCExportFile.translateToObjCExtensionFacades(): List<ObjCInterfa
origin = null,
attributes = emptyList(),
superProtocols = emptyList(),
members = extensionSymbols.mapNotNull { ext -> ext.translateToObjCExportStub() },
members = extensionSymbols.flatMap { ext -> ext.translateToObjCExportStub() },
categoryName = extensionsCategoryName,
generics = emptyList(),
superClass = null,
@@ -6,7 +6,6 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.annotations.annotationInfos
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
import org.jetbrains.kotlin.backend.konan.KonanFqNames
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.name.Name
@@ -20,10 +19,10 @@ internal val KtCallableSymbol.isConstructor: Boolean
get() = this is KtConstructorSymbol
context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionSymbol.translateToObjCMethod(): ObjCMethod? {
fun KtFunctionLikeSymbol.translateToObjCMethod(): ObjCMethod? {
if (!isVisibleInObjC()) return null
if (isFakeOverride) return null
if (isClone) return null
if (this is KtFunctionSymbol && isClone) return null
return buildObjCMethod()
}
@@ -48,7 +47,7 @@ internal fun KtFunctionLikeSymbol.buildObjCMethod(
val returnType: ObjCType = mapReturnType(bridge.returnBridge)
val parameters = translateToObjCParameters(bridge)
val selector = getSelector(bridge)
val selectors: List<String> = splitSelector(selector)
val selectors = splitSelector(selector)
val swiftName = getSwiftName(bridge)
val attributes = mutableListOf<String>()
val returnBridge = bridge.returnBridge
@@ -127,15 +126,18 @@ internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): Stri
append(getMangledName(forSwift = true))
append("(")
parameters@ for ((bridge, symbol) in parameters) {
parameters@ for ((bridge, parameter: KtObjCParameterData?) in parameters) {
val label = when (bridge) {
is MethodBridgeValueParameter.Mapped -> when {
//it is ReceiverParameterDescriptor -> it.getObjCName().asIdentifier(true) { "_" }
parameter?.isReceiver == true -> "_"
method is KtPropertySetterSymbol -> when (parameters.size) {
1 -> "_"
else -> "value"
}
else -> symbol!!.name
else -> {
if (parameter == null) continue@parameters
else if (parameter.isReceiver) "_" else parameter.name
}
}
MethodBridgeValueParameter.ErrorOutParameter -> continue@parameters
is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler"
@@ -149,7 +151,6 @@ internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): Stri
}
return sb.toString()
}
@@ -190,23 +191,23 @@ fun KtFunctionLikeSymbol.getSelector(methodBridge: MethodBridge): String {
}
val parameters = methodBridge.valueParametersAssociated(this)
val method = this
val sb = StringBuilder()
sb.append(method.getMangledName(forSwift = false))
parameters.forEachIndexed { index, (bridge, typeParameterSymbol) ->
parameters.forEachIndexed { index, (bridge, parameter) ->
val name = when (bridge) {
is MethodBridgeValueParameter.Mapped -> when {
parameter?.isReceiver == true -> ""
method is KtPropertySetterSymbol -> when (parameters.size) {
1 -> ""
else -> "value"
}
else -> {
typeParameterSymbol!!.name.toString()
if (parameter == null) return@forEachIndexed
else if (parameter.isReceiver) "" else parameter.name.toString()
}
}
MethodBridgeValueParameter.ErrorOutParameter -> "error"
@@ -255,26 +256,6 @@ private fun String.handleSpecialNames(prefix: String): String {
return this
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.MethodBrideExtensionsKt.valueParametersAssociated]
*/
@InternalKotlinNativeApi
fun MethodBridge.valueParametersAssociated(
function: KtFunctionLikeSymbol,
): List<Pair<MethodBridgeValueParameter, KtValueParameterSymbol?>> {
val allParameters = function.valueParameters
return this.valueParameters.mapIndexed { index, valueParameterBridge ->
when (valueParameterBridge) {
is MethodBridgeValueParameter.Mapped -> valueParameterBridge to allParameters[index]
is MethodBridgeValueParameter.SuspendCompletion,
is MethodBridgeValueParameter.ErrorOutParameter,
-> valueParameterBridge to null
}
}
}
private fun String.startsWithWords(words: String) = this.startsWith(words) &&
(this.length == words.length || !this[words.length].isLowerCase())
@@ -33,7 +33,7 @@ fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? {
objectMembers += getDefaultMembers()
objectMembers += getDeclaredMemberScope().getCallableSymbols()
.sortedWith(StableCallableOrder)
.mapNotNull { it.translateToObjCExportStub() }
.flatMap { it.translateToObjCExportStub() }
return ObjCInterfaceImpl(
name = name.objCName,
@@ -5,11 +5,9 @@ import org.jetbrains.kotlin.analysis.api.components.buildClassType
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySetterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol
import org.jetbrains.kotlin.backend.konan.cKeywords
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.KtParameter
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: MethodBridge): List<ObjCParameter> {
@@ -27,13 +25,19 @@ internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: Me
val usedNames = mutableSetOf<String>()
valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, parameter: KtValueParameterSymbol?) ->
val candidateName: String = when (bridge) {
valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, parameter: KtObjCParameterData?) ->
val candidateName = when (bridge) {
is MethodBridgeValueParameter.Mapped -> {
if (parameter == null) throw IllegalStateException("Parameter shouldn't be null")
if (parameter == null) return@forEach
when {
this is KtPropertySetterSymbol -> "value"
else -> parameter.name.toString()
this is KtPropertySetterSymbol -> {
if (parameter.isReceiver) "receiver"
else "value"
}
else -> {
if (parameter.isReceiver) "receiver"
else parameter.name.toString()
}
}
}
MethodBridgeValueParameter.ErrorOutParameter -> "error"
@@ -45,10 +49,10 @@ internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: Me
val type = when (bridge) {
is MethodBridgeValueParameter.Mapped -> {
val returnType = parameter!!.returnType
val returnType = parameter!!.type
if (parameter.isVararg) {
//vararg is a special case, [parameter.returnType] is T, we need Array<T>
buildClassType(StandardClassIds.Array) { argument(parameter.returnType) }.translateToObjCType(bridge.bridge)
buildClassType(StandardClassIds.Array) { argument(parameter.type) }.translateToObjCType(bridge.bridge)
} else {
returnType.translateToObjCType(bridge.bridge)
}
@@ -28,7 +28,7 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? {
val members = getCallableSymbolsForObjCMemberTranslation()
.filter { it.isObjCBaseCallable() }
.sortedWith(StableCallableOrder)
.mapNotNull { it.translateToObjCExportStub() }
.flatMap { it.translateToObjCExportStub() }
val comment: ObjCComment? = annotationsList.translateToObjCComment()
@@ -60,7 +60,7 @@ fun KtResolvedObjCExportFile.translateToObjCTopLevelFacade(): ObjCInterface? {
origin = null,
attributes = listOf(OBJC_SUBCLASSING_RESTRICTED) + fileName.toNameAttributes(),
superProtocols = emptyList(),
members = extensions.mapNotNull { it.translateToObjCExportStub() },
members = extensions.flatMap { it.translateToObjCExportStub() },
categoryName = null,
generics = emptyList(),
superClass = getDefaultSuperClassOrProtocolName().objCName,
@@ -6,13 +6,12 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
internal val StableFileOrder: Comparator<KtObjCExportFile>
get() = compareBy<KtObjCExportFile> { file -> file.packageFqName.asString() }
.thenComparing { file -> file.fileName }
internal val StablePropertyOrder: Comparator<KtPropertySymbol> = compareBy { it.name }
internal val StableFunctionOrder: Comparator<KtFunctionSymbol>
get() = compareBy(
{ it.isConstructor },
@@ -42,6 +41,20 @@ internal val StableClassifierOrder: Comparator<KtClassifierSymbol> =
else ""
}
//Figure out when name is special and enable logic. See KT-66510
// when (it) {
// is KtPropertySymbol -> {
// /**
// * K1 property names are special: [org.jetbrains.kotlin.name.Name.special] == true
// * So we need to wrap setter/getter
// */
// if (it.setter == null) "<get-${it.name}>"
// else "<set-${it.name}>"
// }
// else -> it.name.toString()
// }
internal val StableNamedOrder: Comparator<KtNamedSymbol> = compareBy { it.name.toString() }
internal val StableCallableOrder: Comparator<KtCallableSymbol> = compareBy<KtCallableSymbol> {
when (it) {
is KtConstructorSymbol -> 0
@@ -49,12 +62,11 @@ internal val StableCallableOrder: Comparator<KtCallableSymbol> = compareBy<KtCal
is KtPropertySymbol -> 2
else -> 3
}
}
.thenComparing(StableConstructorOrder)
.thenComparing(StablePropertyOrder)
}.thenComparing(StableConstructorOrder)
.thenComparing(StableFunctionOrder)
.thenComparing(StableNamedOrder)
private inline fun <T, reified R> Comparator<T>.thenComparing(comparator: Comparator<R>): Comparator<T> where R : T {
private inline fun <T, reified R> Comparator<T>.thenComparing(comparator: Comparator<R>): Comparator<T> {
return thenComparing { a, b ->
if (a is R && b is R) comparator.compare(a, b) else 0
}
@@ -0,0 +1,95 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.backend.konan.objcexport.MethodBridge
import org.jetbrains.kotlin.backend.konan.objcexport.MethodBridgeValueParameter
import org.jetbrains.kotlin.backend.konan.objcexport.ReferenceBridge
import org.jetbrains.kotlin.name.Name
/**
* See K1 implementation [org.jetbrains.kotlin.backend.konan.objcexport.MethodBrideExtensionsKt.valueParametersAssociated]
*/
context(KtAnalysisSession)
internal fun MethodBridge.valueParametersAssociated(
function: KtFunctionLikeSymbol,
): List<Pair<MethodBridgeValueParameter, KtObjCParameterData?>> {
val result = mutableListOf<Pair<MethodBridgeValueParameter, KtObjCParameterData?>>()
val valueParameters = function.valueParameters
val receiverType = function.objCReceiverType
val receiverTypeName = receiverType?.expandedClassSymbol?.name
if (receiverType != null && receiverTypeName != null) {
result.add(
MethodBridgeValueParameter.Mapped(ReferenceBridge) to KtObjCParameterData(
name = receiverTypeName,
isVararg = false,
type = receiverType,
isReceiver = true
)
)
}
this.valueParameters.forEachIndexed { index, valueParameterBridge ->
if (valueParameterBridge is MethodBridgeValueParameter.Mapped) {
result.add(
valueParameterBridge to KtObjCParameterData(
name = valueParameters[index].name,
isVararg = valueParameters[index].isVararg,
type = valueParameters[index].returnType,
isReceiver = false
)
)
} else {
result.add(valueParameterBridge to null)
}
}
return result
}
internal data class KtObjCParameterData(
val name: Name,
val isVararg: Boolean,
val type: KtType,
val isReceiver: Boolean,
)
/**
* Not null in 3 cases:
* 1. constructor of inner class
* 2. function extension of inner class
* 3. property extension of inner class
*
* Members with non null [objCReceiverType] will have name `receiver`:
* ```objective-c
* - (int32_t)foo:(NSString *)receiver __attribute__((swift_name("foo(_:)")));
* ```
*
* Also see [isObjCProperty]
*/
context(KtAnalysisSession)
internal val KtFunctionLikeSymbol.objCReceiverType: KtType?
get() {
return if (isConstructor) {
/**
* Edge case for supporting inner classes parameter.
* See details at KT-66339
*/
@Suppress("DEPRECATION")
getDispatchReceiverType()
} else if (isExtension) {
if ((getContainingSymbol() as? KtNamedClassOrObjectSymbol)?.isInner == true) receiverParameter?.type
else null
} else if (this is KtPropertyGetterSymbol || this is KtPropertySetterSymbol) {
val property = this.getContainingSymbol() as KtPropertySymbol
val isExtension = property.isExtension
val isInner = (property.getContainingSymbol() as? KtNamedClassOrObjectSymbol)?.isInner == true
if (isExtension && isInner) property.receiverType else null
} else {
null
}
}
@@ -0,0 +1,152 @@
/*
* Copyright 2010-2024 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.objcexport.tests
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.objcexport.objCReceiverType
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
import org.jetbrains.kotlin.objcexport.testUtils.getFunctionOrFail
import org.jetbrains.kotlin.objcexport.testUtils.getPropertyOrFail
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class ObjCReceiverTypeTest(
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
) {
@Test
fun `test - inner constructor`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Outer {
inner class Inner
}
""".trimIndent()
)
analyze(file) {
val outerClass = file.getClassOrFail("Outer")
val innerClass = outerClass.getMemberScope().getClassOrFail("Inner")
val innerClassConstructor = innerClass.getMemberScope().getConstructors().first()
assertEquals(
innerClassConstructor.objCReceiverType?.expandedClassSymbol?.classIdIfNonLocal,
outerClass.classIdIfNonLocal
)
}
}
@Test
fun `test - inner function`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Outer {
inner class Inner {
fun String.foo() = Unit
}
}
""".trimIndent()
)
analyze(file) {
val outerClass = file.getClassOrFail("Outer")
val innerClass = outerClass.getMemberScope().getClassOrFail("Inner")
val foo = innerClass.getMemberScope().getFunctionOrFail("foo")
assertEquals(
ClassId.topLevel(StandardNames.FqNames.string.toSafe()),
foo.objCReceiverType?.expandedClassSymbol?.classIdIfNonLocal
)
}
}
@Test
fun `test - inner getter`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Outer {
inner class Inner {
val Boolean.prop: Boolean
get() = false
}
}
""".trimIndent()
)
analyze(file) {
val outerClass = file.getClassOrFail("Outer")
val innerClass = outerClass.getMemberScope().getClassOrFail("Inner")
val getter = innerClass.getMemberScope().getPropertyOrFail("prop").getter
assertEquals(
ClassId.topLevel(StandardNames.FqNames._boolean.toSafe()),
getter?.objCReceiverType?.expandedClassSymbol?.classIdIfNonLocal
)
}
}
@Test
fun `test - inner setter`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Outer {
inner class Inner {
var String.prop: Int
get() = 42
set(value) {}
}
}
""".trimIndent()
)
analyze(file) {
val outerClass = file.getClassOrFail("Outer")
val innerClass = outerClass.getMemberScope().getClassOrFail("Inner")
val setter = innerClass.getMemberScope().getPropertyOrFail("prop").setter
assertEquals(
ClassId.topLevel(StandardNames.FqNames.string.toSafe()),
setter?.objCReceiverType?.expandedClassSymbol?.classIdIfNonLocal
)
}
}
@Test
fun `test - regular extensions`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Foo {
fun Boolean.foo() = Unit
var String.prop: Int
get() = 42
set(value) {}
}
""".trimIndent()
)
analyze(file) {
val fooClass = file.getClassOrFail("Foo")
val foo = fooClass.getMemberScope().getFunctionOrFail("foo")
val setter = fooClass.getMemberScope().getPropertyOrFail("prop").setter
val getter = fooClass.getMemberScope().getPropertyOrFail("prop").getter
assertNull(foo.objCReceiverType)
assertNull(setter?.objCReceiverType)
assertNull(getter?.objCReceiverType)
}
}
}