[ObjCExport] Constructors fixes, cloneables handling, annotations
This commit is contained in:
committed by
Space Team
parent
545753ff4c
commit
0ec9f2a8b9
+1
-1
@@ -47,7 +47,7 @@ context(KtAnalysisSession)
|
|||||||
private val KtCallableSymbol.receiverType: MethodBridgeReceiver
|
private val KtCallableSymbol.receiverType: MethodBridgeReceiver
|
||||||
get() = if (isArrayConstructor) {
|
get() = if (isArrayConstructor) {
|
||||||
MethodBridgeReceiver.Factory
|
MethodBridgeReceiver.Factory
|
||||||
} else if (isTopLevel) {
|
} else if (!isConstructor && isTopLevel) {
|
||||||
MethodBridgeReceiver.Static
|
MethodBridgeReceiver.Static
|
||||||
} else {
|
} else {
|
||||||
MethodBridgeReceiver.Instance
|
MethodBridgeReceiver.Instance
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotated
|
||||||
|
import org.jetbrains.kotlin.analysis.api.annotations.hasAnnotation
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
|
||||||
|
private val ExportForCompilerAnnotationClassId = ClassId.topLevel(FqName("kotlin.native.internal.ExportForCompiler"))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [kotlin.native.internal.ExportForCompiler] is an internal compiler annotation. Annotated symbols must not be exposed.
|
||||||
|
*/
|
||||||
|
internal val KtAnnotated.hasExportForCompilerAnnotation: Boolean
|
||||||
|
get() {
|
||||||
|
return this.hasAnnotation(ExportForCompilerAnnotationClassId)
|
||||||
|
}
|
||||||
+29
@@ -1,8 +1,28 @@
|
|||||||
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
package org.jetbrains.kotlin.objcexport.analysisApiUtils
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
||||||
|
import org.jetbrains.kotlin.analysis.api.symbols.KtDeclarationSymbol
|
||||||
|
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
|
||||||
import org.jetbrains.kotlin.builtins.StandardNames
|
import org.jetbrains.kotlin.builtins.StandardNames
|
||||||
|
import org.jetbrains.kotlin.name.CallableId
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.name.StandardClassIds
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
internal val KtDeclarationSymbol?.implementsCloneable: Boolean
|
||||||
|
get() {
|
||||||
|
return (this as? KtClassOrObjectSymbol)?.implementsCloneable ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
internal val KtClassOrObjectSymbol.implementsCloneable: Boolean
|
||||||
|
get() {
|
||||||
|
return superTypes.any {
|
||||||
|
it.expandedClassSymbol?.isCloneable ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal val KtClassOrObjectSymbol.isCloneable: Boolean
|
internal val KtClassOrObjectSymbol.isCloneable: Boolean
|
||||||
get() {
|
get() {
|
||||||
@@ -12,4 +32,13 @@ internal val KtClassOrObjectSymbol.isCloneable: Boolean
|
|||||||
internal val ClassId.isCloneable: Boolean
|
internal val ClassId.isCloneable: Boolean
|
||||||
get() {
|
get() {
|
||||||
return asSingleFqName() == StandardNames.FqNames.cloneable.toSafe()
|
return asSingleFqName() == StandardNames.FqNames.cloneable.toSafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
internal val KtFunctionSymbol.isClone: Boolean
|
||||||
|
get() {
|
||||||
|
val cloneCallableId = CallableId(StandardClassIds.Cloneable, Name.identifier("clone"))
|
||||||
|
if (this.callableIdIfNonLocal == cloneCallableId) return true
|
||||||
|
|
||||||
|
return this.getAllOverriddenSymbols().any { it.callableIdIfNonLocal == cloneCallableId }
|
||||||
}
|
}
|
||||||
+4
-2
@@ -8,11 +8,12 @@ import org.jetbrains.kotlin.analysis.api.symbols.nameOrAnonymous
|
|||||||
import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType
|
import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType
|
||||||
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.hasExportForCompilerAnnotation
|
||||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
||||||
|
|
||||||
context(KtAnalysisSession, KtObjCExportSession)
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
|
fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
|
||||||
require(classKind == KtClassKind.CLASS)
|
require(classKind == KtClassKind.CLASS || classKind == KtClassKind.ENUM_CLASS)
|
||||||
if (!isVisibleInObjC()) return null
|
if (!isVisibleInObjC()) return null
|
||||||
|
|
||||||
val enumKind = this.classKind == KtClassKind.ENUM_CLASS
|
val enumKind = this.classKind == KtClassKind.ENUM_CLASS
|
||||||
@@ -26,8 +27,9 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
|
|||||||
|
|
||||||
val superClass = translateSuperClass()
|
val superClass = translateSuperClass()
|
||||||
val superProtocols: List<String> = superProtocols()
|
val superProtocols: List<String> = superProtocols()
|
||||||
|
val constructors = getMemberScope().getConstructors().filter { !it.hasExportForCompilerAnnotation }
|
||||||
|
|
||||||
val members: List<ObjCExportStub> = getMemberScope().getCallableSymbols().plus(getMemberScope().getConstructors())
|
val members: List<ObjCExportStub> = getMemberScope().getCallableSymbols().plus(constructors)
|
||||||
.sortedWith(StableCallableOrder)
|
.sortedWith(StableCallableOrder)
|
||||||
.flatMap { it.translateToObjCExportStubs() }
|
.flatMap { it.translateToObjCExportStubs() }
|
||||||
.toList()
|
.toList()
|
||||||
|
|||||||
+6
-4
@@ -6,9 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.objcexport
|
package org.jetbrains.kotlin.objcexport
|
||||||
|
|
||||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||||
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo
|
import org.jetbrains.kotlin.analysis.api.annotations.*
|
||||||
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationsList
|
|
||||||
import org.jetbrains.kotlin.analysis.api.annotations.KtNamedAnnotationValue
|
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
|
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
|
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
|
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
|
||||||
@@ -97,12 +95,16 @@ private fun renderAnnotation(clazz: ClassId, arguments: List<KtNamedAnnotationVa
|
|||||||
append(clazz.asSingleFqName())
|
append(clazz.asSingleFqName())
|
||||||
if (arguments.isNotEmpty()) {
|
if (arguments.isNotEmpty()) {
|
||||||
append('(')
|
append('(')
|
||||||
arguments.joinTo(this)
|
arguments.joinTo(this) { arg -> arg.render() }
|
||||||
append(')')
|
append(')')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KtNamedAnnotationValue.render(): String {
|
||||||
|
return "$name=${expression.renderAsSourceCode()}"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.getEffectiveThrows]
|
* Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.getEffectiveThrows]
|
||||||
*/
|
*/
|
||||||
|
|||||||
+43
-11
@@ -4,6 +4,9 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
|||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtConstructorSymbol
|
import org.jetbrains.kotlin.analysis.api.symbols.KtConstructorSymbol
|
||||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInstanceType
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInstanceType
|
||||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCMethod
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCMethod
|
||||||
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCParameter
|
||||||
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCRawType
|
||||||
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isArrayConstructor
|
||||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
||||||
|
|
||||||
context(KtAnalysisSession, KtObjCExportSession)
|
context(KtAnalysisSession, KtObjCExportSession)
|
||||||
@@ -13,19 +16,48 @@ fun KtConstructorSymbol.translateToObjCConstructors(): List<ObjCMethod> {
|
|||||||
|
|
||||||
result.add(buildObjCMethod())
|
result.add(buildObjCMethod())
|
||||||
|
|
||||||
result.add(
|
if (isArrayConstructor) {
|
||||||
ObjCMethod(
|
|
||||||
comment = null,
|
result.add(
|
||||||
origin = getObjCExportStubOrigin(),
|
ObjCMethod(
|
||||||
isInstanceMethod = false,
|
comment = null,
|
||||||
returnType = ObjCInstanceType,
|
origin = getObjCExportStubOrigin(),
|
||||||
selectors = listOf("new"),
|
isInstanceMethod = false,
|
||||||
parameters = emptyList(),
|
returnType = ObjCInstanceType,
|
||||||
attributes = listOf(
|
selectors = listOf("alloc"),
|
||||||
"availability(swift, unavailable, message=\"use object initializers instead\")"
|
parameters = emptyList(),
|
||||||
|
attributes = listOf("unavailable")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
result.add(
|
||||||
|
ObjCMethod(
|
||||||
|
comment = null,
|
||||||
|
origin = getObjCExportStubOrigin(),
|
||||||
|
isInstanceMethod = false,
|
||||||
|
returnType = ObjCInstanceType,
|
||||||
|
selectors = listOf("allocWithZone:"),
|
||||||
|
parameters = listOf(ObjCParameter("zone", null, ObjCRawType("struct _NSZone *"), null)),
|
||||||
|
attributes = listOf("unavailable")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.size == 1 && result.first().name == "init") {
|
||||||
|
result.add(
|
||||||
|
ObjCMethod(
|
||||||
|
comment = null,
|
||||||
|
origin = getObjCExportStubOrigin(),
|
||||||
|
isInstanceMethod = false,
|
||||||
|
returnType = ObjCInstanceType,
|
||||||
|
selectors = listOf("new"),
|
||||||
|
parameters = emptyList(),
|
||||||
|
attributes = listOf(
|
||||||
|
"availability(swift, unavailable, message=\"use object initializers instead\")"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
+1
@@ -51,6 +51,7 @@ fun translateToObjCHeader(files: List<KtFile>): ObjCHeader {
|
|||||||
KtClassKind.INTERFACE -> symbol.translateToObjCProtocol()
|
KtClassKind.INTERFACE -> symbol.translateToObjCProtocol()
|
||||||
KtClassKind.CLASS -> symbol.translateToObjCClass()
|
KtClassKind.CLASS -> symbol.translateToObjCClass()
|
||||||
KtClassKind.OBJECT -> symbol.translateToObjCObject()
|
KtClassKind.OBJECT -> symbol.translateToObjCObject()
|
||||||
|
KtClassKind.ENUM_CLASS -> symbol.translateToObjCClass()
|
||||||
else -> return result
|
else -> return result
|
||||||
} ?: return result
|
} ?: return result
|
||||||
|
|
||||||
|
|||||||
+5
-6
@@ -12,9 +12,7 @@ import org.jetbrains.kotlin.backend.konan.objcexport.*
|
|||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.name.NameUtils
|
import org.jetbrains.kotlin.name.NameUtils
|
||||||
import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSelectors
|
import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSelectors
|
||||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getFunctionMethodBridge
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.*
|
||||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isArrayConstructor
|
|
||||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
internal val KtCallableSymbol.isConstructor: Boolean
|
internal val KtCallableSymbol.isConstructor: Boolean
|
||||||
@@ -25,6 +23,7 @@ fun KtFunctionSymbol.translateToObjCMethod(
|
|||||||
): ObjCMethod? {
|
): ObjCMethod? {
|
||||||
if (!isVisibleInObjC()) return null
|
if (!isVisibleInObjC()) return null
|
||||||
if (anyMethodSelectors.containsKey(this.name)) return null //temp, find replacement for org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.isReal
|
if (anyMethodSelectors.containsKey(this.name)) return null //temp, find replacement for org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.isReal
|
||||||
|
if (isClone) return null
|
||||||
|
|
||||||
return buildObjCMethod()
|
return buildObjCMethod()
|
||||||
}
|
}
|
||||||
@@ -78,7 +77,7 @@ internal fun KtFunctionLikeSymbol.buildObjCMethod(
|
|||||||
return ObjCMethod(
|
return ObjCMethod(
|
||||||
comment = comment,
|
comment = comment,
|
||||||
origin = getObjCExportStubOrigin(),
|
origin = getObjCExportStubOrigin(),
|
||||||
isInstanceMethod = bridge.isInstance || isConstructor,
|
isInstanceMethod = bridge.isInstance,
|
||||||
returnType = returnType,
|
returnType = returnType,
|
||||||
selectors = selectors,
|
selectors = selectors,
|
||||||
parameters = parameters,
|
parameters = parameters,
|
||||||
@@ -280,7 +279,7 @@ private fun String.mangleIfSpecialFamily(prefix: String): String {
|
|||||||
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.startsWithWords]
|
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.startsWithWords]
|
||||||
*/
|
*/
|
||||||
private fun String.startsWithWords(words: String) = this.startsWith(words) &&
|
private fun String.startsWithWords(words: String) = this.startsWith(words) &&
|
||||||
(this.length == words.length || !this[words.length].isLowerCase())
|
(this.length == words.length || !this[words.length].isLowerCase())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [org.jetbrains.kotlin.backend.konan.objcexport.MethodBrideExtensionsKt.valueParametersAssociated]
|
* [org.jetbrains.kotlin.backend.konan.objcexport.MethodBrideExtensionsKt.valueParametersAssociated]
|
||||||
@@ -322,7 +321,7 @@ fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue):
|
|||||||
if (!returnBridge.successMayBeZero) {
|
if (!returnBridge.successMayBeZero) {
|
||||||
check(
|
check(
|
||||||
successReturnType is ObjCNonNullReferenceType
|
successReturnType is ObjCNonNullReferenceType
|
||||||
|| (successReturnType is ObjCPointerType && !successReturnType.nullable)
|
|| (successReturnType is ObjCPointerType && !successReturnType.nullable)
|
||||||
) {
|
) {
|
||||||
"Unexpected return type: $successReturnType in $this"
|
"Unexpected return type: $successReturnType in $this"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -7,7 +7,9 @@ package org.jetbrains.kotlin.objcexport
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
|
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
|
||||||
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCIdType
|
||||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProperty
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProperty
|
||||||
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCType
|
||||||
import org.jetbrains.kotlin.backend.konan.objcexport.swiftNameAttribute
|
import org.jetbrains.kotlin.backend.konan.objcexport.swiftNameAttribute
|
||||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getPropertyMethodBridge
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getPropertyMethodBridge
|
||||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
|
||||||
@@ -57,7 +59,7 @@ fun KtPropertySymbol.buildProperty(): ObjCProperty {
|
|||||||
name = name,
|
name = name,
|
||||||
comment = null,
|
comment = null,
|
||||||
origin = getObjCExportStubOrigin(),
|
origin = getObjCExportStubOrigin(),
|
||||||
type = type!!,
|
type = type ?: ObjCIdType, //[ObjCIdType] temp fix, should be translated properly, see KT-65709
|
||||||
propertyAttributes = attributes,
|
propertyAttributes = attributes,
|
||||||
setterName = setterName,
|
setterName = setterName,
|
||||||
getterName = getterName,
|
getterName = getterName,
|
||||||
|
|||||||
+2
-2
@@ -61,6 +61,6 @@ fun KtScope.getPropertyOrFail(name: String): KtPropertySymbol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context(KtAnalysisSession)
|
context(KtAnalysisSession)
|
||||||
fun KtClassOrObjectSymbol.getFunctionOrFail(name: String) : KtFunctionSymbol {
|
fun KtClassOrObjectSymbol.getFunctionOrFail(name: String): KtFunctionSymbol {
|
||||||
return this.getMemberScope().getFunctionOrFail(name)
|
return this.getMemberScope().getFunctionOrFail(name)
|
||||||
}
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package org.jetbrains.kotlin.objcexport.tests
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
||||||
|
import org.jetbrains.kotlin.analysis.api.analyze
|
||||||
|
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
|
||||||
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.hasExportForCompilerAnnotation
|
||||||
|
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
|
||||||
|
import org.jetbrains.kotlin.objcexport.testUtils.getPropertyOrFail
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
import kotlin.test.fail
|
||||||
|
|
||||||
|
class HasExportForCompilerAnnotationTest(
|
||||||
|
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
fun `test - has ExportForCompiler annotation`() {
|
||||||
|
val ktFile = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
class Foo
|
||||||
|
val array: Array<Int>
|
||||||
|
val iterator: Iterator<Int>
|
||||||
|
val foo: Foo
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
analyze(ktFile) {
|
||||||
|
assertTrue(
|
||||||
|
verifyHasExportForCompilerAnnotation(ktFile.getPropertyOrFail("array"))
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
verifyHasExportForCompilerAnnotation(ktFile.getPropertyOrFail("iterator"))
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
verifyHasExportForCompilerAnnotation(ktFile.getPropertyOrFail("foo"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context(KtAnalysisSession)
|
||||||
|
private fun verifyHasExportForCompilerAnnotation(property: KtPropertySymbol): Boolean {
|
||||||
|
return property
|
||||||
|
.returnType
|
||||||
|
.getTypeScope()?.getConstructors()?.toList()?.any { it.hasExportForCompilerAnnotation }
|
||||||
|
?: fail("Property return type has no constructors: ${property.returnType}")
|
||||||
|
}
|
||||||
+77
-13
@@ -1,15 +1,16 @@
|
|||||||
package org.jetbrains.kotlin.objcexport.tests
|
package org.jetbrains.kotlin.objcexport.tests
|
||||||
|
|
||||||
import junit.framework.TestCase.assertFalse
|
|
||||||
import junit.framework.TestCase.assertTrue
|
|
||||||
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
|
|
||||||
import org.jetbrains.kotlin.analysis.api.analyze
|
import org.jetbrains.kotlin.analysis.api.analyze
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.implementsCloneable
|
||||||
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isClone
|
||||||
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCloneable
|
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isCloneable
|
||||||
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
|
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.jetbrains.kotlin.objcexport.testUtils.getPropertyOrFail
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class IsCloneableTest(
|
class IsCloneableTest(
|
||||||
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
|
||||||
@@ -20,7 +21,7 @@ class IsCloneableTest(
|
|||||||
val file = inlineSourceCodeAnalysis.createKtFile("val foo: Cloneable")
|
val file = inlineSourceCodeAnalysis.createKtFile("val foo: Cloneable")
|
||||||
analyze(file) {
|
analyze(file) {
|
||||||
val foo = file.getPropertyOrFail("foo")
|
val foo = file.getPropertyOrFail("foo")
|
||||||
assertTrue(foo.type.isCloneable)
|
assertTrue(foo.getter?.returnType?.expandedClassSymbol?.isCloneable ?: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,13 +35,76 @@ class IsCloneableTest(
|
|||||||
)
|
)
|
||||||
analyze(file) {
|
analyze(file) {
|
||||||
val foo = file.getPropertyOrFail("foo")
|
val foo = file.getPropertyOrFail("foo")
|
||||||
assertFalse(foo.type.isCloneable)
|
assertFalse(foo.getter?.returnType?.expandedClassSymbol?.isCloneable ?: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
context(KtAnalysisSession)
|
@Test
|
||||||
private val KtPropertySymbol.type: KtClassOrObjectSymbol
|
fun `test - fake clone method`() {
|
||||||
get() {
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
return getter?.returnType?.expandedClassSymbol!!
|
"""
|
||||||
}
|
interface FakeCloneable {
|
||||||
|
fun clone(): Any
|
||||||
|
}
|
||||||
|
class Foo : FakeCloneable {
|
||||||
|
override fun clone(): Any {
|
||||||
|
return "any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
analyze(file) {
|
||||||
|
val foo = file.getClassOrFail("Foo")
|
||||||
|
assertFalse(foo.getFunctionOrFail("clone").isClone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - implements cloneable`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
class Foo : Cloneable {
|
||||||
|
override fun clone(): Foo {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
analyze(file) {
|
||||||
|
val foo = file.getClassOrFail("Foo")
|
||||||
|
assertTrue(foo.implementsCloneable)
|
||||||
|
assertTrue(foo.getFunctionOrFail("clone").isClone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - fake clone function`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
fun clone(): Any {}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
analyze(file) {
|
||||||
|
val clone = file.getFunctionOrFail("clone")
|
||||||
|
assertFalse(clone.isClone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - clone method`() {
|
||||||
|
val file = inlineSourceCodeAnalysis.createKtFile(
|
||||||
|
"""
|
||||||
|
val array: Array<Int>
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
analyze(file) {
|
||||||
|
assertTrue(
|
||||||
|
file
|
||||||
|
.getPropertyOrFail("array")
|
||||||
|
.getter?.returnType?.expandedClassSymbol?.getMemberScope()
|
||||||
|
?.getFunctionOrFail("clone")
|
||||||
|
?.isClone ?: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-6
@@ -26,8 +26,6 @@ class ObjCDependenciesTypesTest(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - Wrong translation of constructors KT-65365
|
|
||||||
* - Bad parsing of versions (SinceKotlin Annotation)
|
|
||||||
* - Missing implementation of mangling
|
* - Missing implementation of mangling
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -41,14 +39,27 @@ class ObjCDependenciesTypesTest(
|
|||||||
doTest(dependenciesDir.resolve("iterator"))
|
doTest(dependenciesDir.resolve("iterator"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - array`() {
|
||||||
|
doTest(dependenciesDir.resolve("array"))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - Wrong translation of constructors KT-65365
|
* Fails because of KT-65709
|
||||||
* - Exposing unwanted 'clone' method KT-65629
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@TodoAnalysisApi
|
@TodoAnalysisApi
|
||||||
fun `test - array`() {
|
fun `test - arrayList`() {
|
||||||
doTest(dependenciesDir.resolve("array"))
|
doTest(dependenciesDir.resolve("arrayList"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Int* type conversion issue: KT-65687
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@TodoAnalysisApi
|
||||||
|
fun `test - implementIterator`() {
|
||||||
|
doTest(dependenciesDir.resolve("implementIterator"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doTest(root: File) {
|
private fun doTest(root: File) {
|
||||||
|
|||||||
+19
@@ -101,6 +101,11 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
|
|||||||
doTest(headersTestDataDir.resolve("classImplementingInterface"))
|
doTest(headersTestDataDir.resolve("classImplementingInterface"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - classExtendsAbstractClass`() {
|
||||||
|
doTest(headersTestDataDir.resolve("classExtendsAbstractClass"))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TodoAnalysisApi
|
@TodoAnalysisApi
|
||||||
fun `test - interfaceImplementingInterface`() {
|
fun `test - interfaceImplementingInterface`() {
|
||||||
@@ -258,6 +263,20 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
|
|||||||
doTest(headersTestDataDir.resolve("objectWithGenericSuperclass"))
|
doTest(headersTestDataDir.resolve("objectWithGenericSuperclass"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test - since version annotation`() {
|
||||||
|
doTest(headersTestDataDir.resolve("sinceVersionAnnotation"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - requires mangling
|
||||||
|
*/
|
||||||
|
@TodoAnalysisApi
|
||||||
|
@Test
|
||||||
|
fun `test - constructors`() {
|
||||||
|
doTest(headersTestDataDir.resolve("constructors"))
|
||||||
|
}
|
||||||
|
|
||||||
private fun doTest(root: File, configuration: Configuration = Configuration()) {
|
private fun doTest(root: File, configuration: Configuration = Configuration()) {
|
||||||
if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory")
|
if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory")
|
||||||
val generatedHeaders = generator.generateHeaders(root, configuration).toString()
|
val generatedHeaders = generator.generateHeaders(root, configuration).toString()
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#import <Foundation/NSArray.h>
|
||||||
|
#import <Foundation/NSDictionary.h>
|
||||||
|
#import <Foundation/NSError.h>
|
||||||
|
#import <Foundation/NSObject.h>
|
||||||
|
#import <Foundation/NSSet.h>
|
||||||
|
#import <Foundation/NSString.h>
|
||||||
|
#import <Foundation/NSValue.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
||||||
|
#pragma clang diagnostic ignored "-Wincompatible-property-type"
|
||||||
|
#pragma clang diagnostic ignored "-Wnullability"
|
||||||
|
|
||||||
|
#pragma push_macro("_Nullable_result")
|
||||||
|
#if !__has_feature(nullability_nullable_result)
|
||||||
|
#undef _Nullable_result
|
||||||
|
#define _Nullable_result _Nullable
|
||||||
|
#endif
|
||||||
|
|
||||||
|
__attribute__((objc_subclassing_restricted))
|
||||||
|
@interface FooKt : Base
|
||||||
|
@property (class, readonly) NSMutableArray<Int *> *a __attribute__((swift_name("a")));
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma pop_macro("_Nullable_result")
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
val a: ArrayList<Int>
|
||||||
Vendored
+39
@@ -0,0 +1,39 @@
|
|||||||
|
#import <Foundation/NSArray.h>
|
||||||
|
#import <Foundation/NSDictionary.h>
|
||||||
|
#import <Foundation/NSError.h>
|
||||||
|
#import <Foundation/NSObject.h>
|
||||||
|
#import <Foundation/NSSet.h>
|
||||||
|
#import <Foundation/NSString.h>
|
||||||
|
#import <Foundation/NSValue.h>
|
||||||
|
|
||||||
|
@protocol Iterator;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
||||||
|
#pragma clang diagnostic ignored "-Wincompatible-property-type"
|
||||||
|
#pragma clang diagnostic ignored "-Wnullability"
|
||||||
|
|
||||||
|
#pragma push_macro("_Nullable_result")
|
||||||
|
#if !__has_feature(nullability_nullable_result)
|
||||||
|
#undef _Nullable_result
|
||||||
|
#define _Nullable_result _Nullable
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@protocol Iterator
|
||||||
|
@required
|
||||||
|
- (BOOL)hasNext __attribute__((swift_name("hasNext()")));
|
||||||
|
- (id _Nullable)next __attribute__((swift_name("next()")));
|
||||||
|
@end
|
||||||
|
|
||||||
|
__attribute__((objc_subclassing_restricted))
|
||||||
|
@interface Foo : Base <Iterator>
|
||||||
|
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||||
|
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||||
|
- (BOOL)hasNext __attribute__((swift_name("hasNext()")));
|
||||||
|
- (Int *)next __attribute__((swift_name("next()")));
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma pop_macro("_Nullable_result")
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
class Foo: Iterator<Int> {
|
||||||
|
override fun hasNext(): Boolean { return false }
|
||||||
|
override fun next(): Int { return 0 }
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
#import <Foundation/NSArray.h>
|
||||||
|
#import <Foundation/NSDictionary.h>
|
||||||
|
#import <Foundation/NSError.h>
|
||||||
|
#import <Foundation/NSObject.h>
|
||||||
|
#import <Foundation/NSSet.h>
|
||||||
|
#import <Foundation/NSString.h>
|
||||||
|
#import <Foundation/NSValue.h>
|
||||||
|
|
||||||
|
@class A;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
||||||
|
#pragma clang diagnostic ignored "-Wincompatible-property-type"
|
||||||
|
#pragma clang diagnostic ignored "-Wnullability"
|
||||||
|
|
||||||
|
#pragma push_macro("_Nullable_result")
|
||||||
|
#if !__has_feature(nullability_nullable_result)
|
||||||
|
#undef _Nullable_result
|
||||||
|
#define _Nullable_result _Nullable
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@interface A : Base
|
||||||
|
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||||
|
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||||
|
- (void)a0 __attribute__((swift_name("a0()")));
|
||||||
|
- (void)a1I:(int32_t)i __attribute__((swift_name("a1(i:)")));
|
||||||
|
- (void)a2B:(BOOL)b a:(id)a __attribute__((swift_name("a2(b:a:)")));
|
||||||
|
@end
|
||||||
|
|
||||||
|
__attribute__((objc_subclassing_restricted))
|
||||||
|
@interface B : A
|
||||||
|
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||||
|
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||||
|
- (void)a0 __attribute__((swift_name("a0()")));
|
||||||
|
- (void)a1I:(int32_t)i __attribute__((swift_name("a1(i:)")));
|
||||||
|
- (void)a2B:(BOOL)b a:(id)a __attribute__((swift_name("a2(b:a:)")));
|
||||||
|
- (void)b0 __attribute__((swift_name("b0()")));
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma pop_macro("_Nullable_result")
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
abstract class A {
|
||||||
|
abstract fun a0()
|
||||||
|
abstract fun a1(i: Int)
|
||||||
|
abstract fun a2(b: Boolean, a: Any)
|
||||||
|
}
|
||||||
|
|
||||||
|
class B: A() {
|
||||||
|
override fun a0() {}
|
||||||
|
override fun a1(i: Int) {}
|
||||||
|
override fun a2(b: Boolean, a: Any) {}
|
||||||
|
|
||||||
|
fun b0() {}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#import <Foundation/NSArray.h>
|
||||||
|
#import <Foundation/NSDictionary.h>
|
||||||
|
#import <Foundation/NSError.h>
|
||||||
|
#import <Foundation/NSObject.h>
|
||||||
|
#import <Foundation/NSSet.h>
|
||||||
|
#import <Foundation/NSString.h>
|
||||||
|
#import <Foundation/NSValue.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
||||||
|
#pragma clang diagnostic ignored "-Wincompatible-property-type"
|
||||||
|
#pragma clang diagnostic ignored "-Wnullability"
|
||||||
|
|
||||||
|
#pragma push_macro("_Nullable_result")
|
||||||
|
#if !__has_feature(nullability_nullable_result)
|
||||||
|
#undef _Nullable_result
|
||||||
|
#define _Nullable_result _Nullable
|
||||||
|
#endif
|
||||||
|
|
||||||
|
__attribute__((objc_subclassing_restricted))
|
||||||
|
@interface Constructor : Base
|
||||||
|
@end
|
||||||
|
|
||||||
|
__attribute__((objc_subclassing_restricted))
|
||||||
|
@interface ConstructorDouble : Base
|
||||||
|
- (instancetype)initWithA:(int32_t)a __attribute__((swift_name("init(a:)"))) __attribute__((objc_designated_initializer));
|
||||||
|
- (instancetype)initWithA_:(int32_t)a __attribute__((swift_name("init(a_:)"))) __attribute__((objc_designated_initializer));
|
||||||
|
- (instancetype)initWithA:(int32_t)a b:(int32_t)b __attribute__((swift_name("init(a:b:)"))) __attribute__((objc_designated_initializer));
|
||||||
|
- (instancetype)initWithA:(int32_t)a b:(int32_t)b c:(int32_t)c __attribute__((swift_name("init(a:b:c:)"))) __attribute__((objc_designated_initializer));
|
||||||
|
@end
|
||||||
|
|
||||||
|
__attribute__((objc_subclassing_restricted))
|
||||||
|
@interface ConstructorParam0 : Base
|
||||||
|
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||||
|
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||||
|
@end
|
||||||
|
|
||||||
|
__attribute__((objc_subclassing_restricted))
|
||||||
|
@interface ConstructorParam1 : Base
|
||||||
|
- (instancetype)initWithA:(int32_t)a __attribute__((swift_name("init(a:)"))) __attribute__((objc_designated_initializer));
|
||||||
|
@property (readonly) int32_t a __attribute__((swift_name("a")));
|
||||||
|
@end
|
||||||
|
|
||||||
|
__attribute__((objc_subclassing_restricted))
|
||||||
|
@interface ConstructorParam2 : Base
|
||||||
|
- (instancetype)initWithA:(int32_t)a b:(int32_t)b __attribute__((swift_name("init(a:b:)"))) __attribute__((objc_designated_initializer));
|
||||||
|
@property (readonly) int32_t a __attribute__((swift_name("a")));
|
||||||
|
@property (readonly) int32_t b __attribute__((swift_name("b")));
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma pop_macro("_Nullable_result")
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
class Constructor private constructor() {}
|
||||||
|
class ConstructorParam0() {}
|
||||||
|
class ConstructorParam1(val a: Int) {}
|
||||||
|
class ConstructorParam2(val a: Int, val b: Int) {}
|
||||||
|
|
||||||
|
class ConstructorDouble(a: Int) {
|
||||||
|
constructor(a: Int): this(a)
|
||||||
|
constructor(a: Int, b: Int): this(b)
|
||||||
|
constructor(a: Int, b: Int, c: Int): this(c)
|
||||||
|
}
|
||||||
native/objcexport-header-generator/testData/headers/sinceVersionAnnotation/!sinceVersionAnnotation.h
Vendored
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#import <Foundation/NSArray.h>
|
||||||
|
#import <Foundation/NSDictionary.h>
|
||||||
|
#import <Foundation/NSError.h>
|
||||||
|
#import <Foundation/NSObject.h>
|
||||||
|
#import <Foundation/NSSet.h>
|
||||||
|
#import <Foundation/NSString.h>
|
||||||
|
#import <Foundation/NSValue.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
||||||
|
#pragma clang diagnostic ignored "-Wincompatible-property-type"
|
||||||
|
#pragma clang diagnostic ignored "-Wnullability"
|
||||||
|
|
||||||
|
#pragma push_macro("_Nullable_result")
|
||||||
|
#if !__has_feature(nullability_nullable_result)
|
||||||
|
#undef _Nullable_result
|
||||||
|
#define _Nullable_result _Nullable
|
||||||
|
#endif
|
||||||
|
|
||||||
|
__attribute__((objc_subclassing_restricted))
|
||||||
|
@interface FooKt : Base
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note annotations
|
||||||
|
* kotlin.SinceKotlin(version="1.4")
|
||||||
|
*/
|
||||||
|
+ (void)foo __attribute__((swift_name("foo()")));
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma pop_macro("_Nullable_result")
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
@SinceKotlin(version="1.4")
|
||||||
|
fun foo() {}
|
||||||
Reference in New Issue
Block a user