[K/N] Implement objc overridablity condition for IR

^KT-61511
This commit is contained in:
Pavel Kunyavskiy
2023-10-09 11:16:48 +02:00
committed by Space Team
parent 202f30f910
commit 156c341eeb
16 changed files with 219 additions and 11 deletions
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.overrides.IrExternalOverridabilityCondition
import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmentSymbol
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
@@ -32,6 +33,7 @@ class JvmFir2IrExtensions(
private val irDeserializer: JvmIrDeserializer,
private val mangler: KotlinMangler.IrMangler,
) : Fir2IrExtensions, JvmGeneratorExtensions {
override val externalOverridabilityConditions: List<IrExternalOverridabilityCondition> = emptyList() // TODO: KT-61370, KT-61804
override val classNameOverride: MutableMap<IrClass, JvmClassName> = mutableMapOf()
override val cachedFields = CachedFieldsForObjectInstances(IrFactoryImpl, configuration.languageVersionSettings)
@@ -561,7 +561,7 @@ class Fir2IrConverter(
IrFakeOverrideBuilder(
typeContextProvider(irBuiltins),
Fir2IrFakeOverrideStrategy(friendModulesMap(session), commonMemberStorage.symbolTable, irMangler),
externalOverridabilityConditions = emptyList(), // TODO: KT-61370, KT-61804.
fir2IrExtensions.externalOverridabilityConditions
)
},
moduleDescriptor, commonMemberStorage, irMangler, specialSymbolProvider, initializedIrBuiltIns
@@ -8,10 +8,12 @@ package org.jetbrains.kotlin.fir.backend
import org.jetbrains.kotlin.fir.references.FirReference
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrMemberWithContainerSource
import org.jetbrains.kotlin.ir.overrides.IrExternalOverridabilityCondition
import org.jetbrains.kotlin.ir.util.SymbolTable
interface Fir2IrExtensions {
val irNeedsDeserialization: Boolean
val externalOverridabilityConditions: List<IrExternalOverridabilityCondition>
fun generateOrGetFacadeClass(declaration: IrMemberWithContainerSource, components: Fir2IrComponents): IrClass?
fun deserializeToplevelClass(irClass: IrClass, components: Fir2IrComponents): Boolean
@@ -22,6 +24,7 @@ interface Fir2IrExtensions {
override val irNeedsDeserialization: Boolean
get() = false
override val externalOverridabilityConditions: List<IrExternalOverridabilityCondition> = emptyList()
override fun generateOrGetFacadeClass(declaration: IrMemberWithContainerSource, components: Fir2IrComponents): IrClass? = null
override fun deserializeToplevelClass(irClass: IrClass, components: Fir2IrComponents): Boolean = false
override fun registerDeclarations(symbolTable: SymbolTable) {}
@@ -14,6 +14,12 @@ import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.scopes.FirOverrideChecker
import org.jetbrains.kotlin.fir.scopes.impl.FirStandardOverrideChecker
/**
* The same code, as in this class also exists in [org.jetbrains.kotlin.ir.objcinterop.ObjCOverridabilityCondition]
* and in [org.jetbrains.kotlin.ir.objcinterop.IrObjCOverridabilityCondition].
*
* When modifying, all three copies should be synchronized.
*/
class FirNativeOverrideChecker(private val session: FirSession) : FirOverrideChecker {
private val standardOverrideChecker = FirStandardOverrideChecker(session)
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2023 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.ir.objcinterop
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrOverridableMember
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.overrides.IrExternalOverridabilityCondition
/**
* Describes method overriding rules for Objective-C methods.
*
* The same code, as in this class also exists in [org.jetbrains.kotlin.fir.backend.native.FirNativeOverrideChecker]
* and in [org.jetbrains.kotlin.ir.objcinterop.ObjCOverridabilityCondition].
*
* When modifying, all three copies should be synchronized.
*
*/
object IrObjCOverridabilityCondition : IrExternalOverridabilityCondition {
override val contract = IrExternalOverridabilityCondition.Contract.BOTH
override fun isOverridable(
superMember: IrOverridableMember,
subMember: IrOverridableMember
): IrExternalOverridabilityCondition.Result {
if (superMember.name == subMember.name) { // Slow path:
if (superMember is IrFunction && subMember is IrFunction) {
superMember.getExternalObjCMethodInfo()?.let { superInfo ->
val subInfo = subMember.getExternalObjCMethodInfo()
if (subInfo != null) {
// Overriding Objective-C method by Objective-C method in interop stubs.
// Don't even check method signatures:
return if (superInfo.selector == subInfo.selector) {
IrExternalOverridabilityCondition.Result.OVERRIDABLE
} else {
IrExternalOverridabilityCondition.Result.INCOMPATIBLE
}
} else {
// Overriding Objective-C method by Kotlin method.
if (!parameterNamesMatch(superMember, subMember)) {
return IrExternalOverridabilityCondition.Result.INCOMPATIBLE
}
}
}
} else if (superMember.isExternalObjCClassProperty() && subMember.isExternalObjCClassProperty()) {
return IrExternalOverridabilityCondition.Result.OVERRIDABLE
}
}
return IrExternalOverridabilityCondition.Result.UNKNOWN
}
private fun IrOverridableMember.isExternalObjCClassProperty() = this is IrProperty &&
(this.parent as? IrClass)?.isExternalObjCClass() == true
private fun parameterNamesMatch(first: IrFunction, second: IrFunction): Boolean {
// The original Objective-C method selector is represented as
// function name and parameter names (except first).
if (first.valueParameters.size != second.valueParameters.size) {
return false
}
first.valueParameters.forEachIndexed { index, parameter ->
if (index > 0 && parameter.name != second.valueParameters[index].name) {
return false
}
}
return true
}
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.ir.objcinterop
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.declarations.*
@@ -18,7 +17,6 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NativeForwardDeclarationKind
import org.jetbrains.kotlin.name.NativeStandardInteropNames
import org.jetbrains.kotlin.native.interop.ObjCMethodInfo
import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
import org.jetbrains.kotlin.resolve.annotations.getAnnotationStringValue
import org.jetbrains.kotlin.resolve.annotations.getArgumentValueOrNull
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
@@ -27,7 +25,8 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.utils.atMostOne
private fun IrFunction.isFakeOverrideInProgressOfBuilding() = this is IrFunctionWithLateBinding && !isBound && isFakeOverride
internal val interopPackageName = NativeStandardInteropNames.cInteropPackage
internal val objCObjectFqName = NativeStandardInteropNames.objCObjectClassId.asSingleFqName()
@@ -118,7 +117,7 @@ private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? {
}
private fun IrFunction.decodeObjCMethodAnnotation(): ObjCMethodInfo? {
assert (this.isReal)
require(this.isReal || this.isFakeOverrideInProgressOfBuilding())
val methodInfo = this.annotations.findAnnotation(objCMethodFqName)?.let {
ObjCMethodInfo(
@@ -158,6 +157,11 @@ private fun FunctionDescriptor.getObjCMethodInfo(onlyExternal: Boolean): ObjCMet
* @param onlyExternal indicates whether to accept overriding methods from Kotlin classes
*/
private fun IrSimpleFunction.getObjCMethodInfo(onlyExternal: Boolean): ObjCMethodInfo? {
// During fake override building we need this method, but overriddenSymbols are not valid yet.
// So let's pretend this override is real. It has annotation copied, if it exists
if (this.isFakeOverrideInProgressOfBuilding()) {
decodeObjCMethodAnnotation()?.let { return it }
}
if (this.isReal) {
this.decodeObjCMethodAnnotation()?.let { return it }
@@ -17,6 +17,10 @@ import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
* This class is applied at [org.jetbrains.kotlin.resolve.OverridingUtil] as configured with
* `META-INF/services/org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition` resource.
*
* The same code, as in this class also exists in [org.jetbrains.kotlin.fir.backend.native.FirNativeOverrideChecker]
* and in [org.jetbrains.kotlin.ir.objcinterop.IrObjCOverridabilityCondition].
*
* When modifying, all three copies should be synchronized.
*
*/
class ObjCOverridabilityCondition : ExternalOverridabilityCondition {
@@ -26,11 +26,15 @@ import org.jetbrains.kotlin.fir.backend.*
import org.jetbrains.kotlin.fir.backend.native.FirNativeKotlinMangler
import org.jetbrains.kotlin.fir.descriptors.FirModuleDescriptor
import org.jetbrains.kotlin.fir.pipeline.convertToIrAndActualize
import org.jetbrains.kotlin.fir.references.FirReference
import org.jetbrains.kotlin.fir.signaturer.Ir2FirManglerAdapter
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrExternalPackageFragment
import org.jetbrains.kotlin.ir.declarations.IrMemberWithContainerSource
import org.jetbrains.kotlin.ir.objcinterop.IrObjCOverridabilityCondition
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptVoid
@@ -40,11 +44,18 @@ import org.jetbrains.kotlin.storage.LockBasedStorageManager
internal val KlibFactories = KlibMetadataFactories(::KonanBuiltIns, DynamicTypeDeserializer)
internal object NativeFir2IrExtensions : Fir2IrExtensions {
override val irNeedsDeserialization = false
override val externalOverridabilityConditions = listOf(IrObjCOverridabilityCondition)
override fun generateOrGetFacadeClass(declaration: IrMemberWithContainerSource, components: Fir2IrComponents) = null
override fun deserializeToplevelClass(irClass: IrClass, components: Fir2IrComponents) = false
override fun registerDeclarations(symbolTable: SymbolTable) {}
override fun findInjectedValue(calleeReference: FirReference, conversionScope: Fir2IrConversionScope) = null
}
internal fun PhaseContext.fir2Ir(
input: FirOutput.Full,
): Fir2IrOutput {
val fir2IrExtensions = Fir2IrExtensions.Default
var builtInsModule: KotlinBuiltIns? = null
val resolvedLibraries = config.resolvedLibraries.getFullResolvedList()
@@ -85,7 +96,7 @@ internal fun PhaseContext.fir2Ir(
useIrFakeOverrideBuilder = configuration.getBoolean(CommonConfigurationKeys.USE_IR_FAKE_OVERRIDE_BUILDER),
)
val (irModuleFragment, components, pluginContext, irActualizedResult) = input.firResult.convertToIrAndActualize(
fir2IrExtensions,
NativeFir2IrExtensions,
fir2IrConfiguration,
IrGenerationExtension.getInstances(config.project),
signatureComposer = DescriptorSignatureComposerStub(KonanManglerDesc),
@@ -172,6 +172,7 @@ class KonanDriver(
copy(BinaryOptions.objcExportDisableSwiftMemberNameMangling)
copy(BinaryOptions.objcExportIgnoreInterfaceMethodCollisions)
copy(KonanConfigKeys.OBJC_GENERICS)
copy(CommonConfigurationKeys.USE_IR_FAKE_OVERRIDE_BUILDER)
}
// For the second stage, remove already compiled source files from the configuration.
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.ir.builders.TranslationPluginContext
import org.jetbrains.kotlin.ir.declarations.DescriptorMetadataSource
import org.jetbrains.kotlin.ir.linkage.IrDeserializer
import org.jetbrains.kotlin.ir.linkage.partial.partialLinkageConfig
import org.jetbrains.kotlin.ir.objcinterop.IrObjCOverridabilityCondition
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptVoid
@@ -154,7 +155,8 @@ internal fun PsiToIrContext.psiToIr(
cachedLibraries = config.cachedLibraries,
lazyIrForCaches = config.lazyIrForCaches,
libraryBeingCached = config.libraryToCache,
userVisibleIrModulesSupport = config.userVisibleIrModulesSupport
userVisibleIrModulesSupport = config.userVisibleIrModulesSupport,
externalOverridabilityConditions = listOf(IrObjCOverridabilityCondition)
).also { linker ->
// context.config.librariesWithDependencies could change at each iteration.
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.builders.TranslationPluginContext
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyClass
import org.jetbrains.kotlin.ir.overrides.IrExternalOverridabilityCondition
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrPublicSymbolBase
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
@@ -306,7 +307,8 @@ internal class KonanIrLinker(
private val cachedLibraries: CachedLibraries,
private val lazyIrForCaches: Boolean,
private val libraryBeingCached: PartialCacheInfo?,
override val userVisibleIrModulesSupport: UserVisibleIrModulesSupport
override val userVisibleIrModulesSupport: UserVisibleIrModulesSupport,
externalOverridabilityConditions: List<IrExternalOverridabilityCondition>,
) : KotlinIrLinker(currentModule, messageLogger, builtIns, symbolTable, exportedDependencies) {
override fun isBuiltInModule(moduleDescriptor: ModuleDescriptor): Boolean = moduleDescriptor.isNativeStdlib()
@@ -321,7 +323,8 @@ internal class KonanIrLinker(
typeSystem = IrTypeSystemContextImpl(builtIns),
friendModules = friendModules,
partialLinkageSupport = partialLinkageSupport,
platformSpecificClassFilter = KonanFakeOverrideClassFilter
platformSpecificClassFilter = KonanFakeOverrideClassFilter,
externalOverridabilityConditions = externalOverridabilityConditions,
)
val moduleDeserializers = mutableMapOf<ModuleDescriptor, KonanPartialModuleDeserializer>()
@@ -4101,6 +4101,11 @@ if (PlatformInfo.isAppleTarget(project)) {
createInterop("kt59167") {
it.defFile 'interop/kt59167/kt59167.def'
}
createInterop("overridabilityCondition") {
it.defFile 'interop/objc/overridabilityCondition/lib.def'
it.headers "$projectDir/interop/objc/overridabilityCondition/lib.h"
it.extraOpts "-Xcompile-source", "$projectDir/interop/objc/overridabilityCondition/lib.m"
}
}
createInterop("withSpaces") {
@@ -4673,6 +4678,12 @@ if (PlatformInfo.isAppleTarget(project)) {
interop = "objcDirect"
}
interopTest("interop_objc_overridabilityCondition") {
interop = "overridabilityCondition"
source = "interop/objc/overridabilityCondition/main.kt"
flags = [ "-Xuse-ir-fake-override-builder" ]
}
interopTest("interop_objc_foreignException") {
source = "interop/objc/foreignException/objc_wrap.kt"
interop = 'foreignException'
@@ -0,0 +1,2 @@
language = Objective-C
headersFilter = **/lib.h
@@ -0,0 +1,10 @@
#import <Foundation/NSObject.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSUUID.h>
@interface ObjCClass : NSObject
- (NSString*)fooWithArg:(int)arg arg2:(NSString*)arg2;
- (NSString*)fooWithArg:(int)ohNoOtherName name2:(NSString*)name2;
- (NSString*)fooWithArg:(int)arg name3:(NSString*)name3;
@end
@@ -0,0 +1,18 @@
#import "lib.h"
@implementation ObjCClass {
}
- (NSString*)fooWithArg:(int)arg arg2:(NSString*)arg2 {
return @"A";
}
- (NSString*)fooWithArg:(int)ohNoOtherName name2:(NSString*)name2 {
return @"B";
}
- (NSString*)fooWithArg:(int)arg name3:(NSString*)name3 {
return @"C";
}
@end
@@ -0,0 +1,54 @@
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
import lib.ObjCClass
@Suppress("CONFLICTING_OVERLOADS")
class OverrideAll : ObjCClass() {
override fun fooWithArg(arg: Int, arg2: String?) = "D"
override fun fooWithArg(ohNoOtherName: Int, name2: String?) = "E"
override fun fooWithArg(arg: Int, name3: String?) = "F"
}
@Suppress("CONFLICTING_OVERLOADS")
class OverrideNone : ObjCClass() {
}
@Suppress("CONFLICTING_OVERLOADS")
class OverrideOne : ObjCClass() {
override fun fooWithArg(arg: Int, arg2: String?) = "G"
}
@Suppress("CONFLICTING_OVERLOADS")
class OverrideWithDifferentFirstArgName : ObjCClass() {
override fun fooWithArg(a: Int, arg2: String?) = "H"
override fun fooWithArg(b: Int, name2: String?) = "I"
override fun fooWithArg(c: Int, name3: String?) = "J"
}
fun test(x: ObjCClass, expected: String) {
val res = x.fooWithArg(arg = 0, arg2 = "") +
x.fooWithArg(ohNoOtherName = 0, name2="") +
x.fooWithArg(arg = 0, name3 = "")
if (res != expected) throw IllegalStateException("Fail ${x::class}: ${res} instead of $expected")
}
fun main() {
test(ObjCClass(), "ABC")
test(OverrideAll(), "DEF")
test(OverrideNone(), "ABC")
test(OverrideOne(), "GBC")
test(OverrideWithDifferentFirstArgName(), "HIJ")
// Also test non-virtual calls
val x1 = OverrideAll()
val res1 = x1.fooWithArg(arg = 0, arg2 = "") +
x1.fooWithArg(ohNoOtherName = 0, name2="") +
x1.fooWithArg(arg = 0, name3 = "")
if (res1 != "DEF") throw IllegalStateException("Fail OverrideAll non-virtual: ${res1} instead of DEF")
val x2 = OverrideNone()
val res2 = x2.fooWithArg(arg = 0, arg2 = "") +
x2.fooWithArg(ohNoOtherName = 0, name2="") +
x2.fooWithArg(arg = 0, name3 = "")
if (res2 != "ABC") throw IllegalStateException("Fail OverrideNone non-virtual: ${res2} instead of ABC")
}