[WIP] Swift export: support Optional<Range> via MixedAST

#KT-82564 Fixed
diff --git a/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/BridgeProvider/TypeBridging.kt b/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/BridgeProvider/TypeBridging.kt
index 037b386..1e4e2e2 100644
--- a/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/BridgeProvider/TypeBridging.kt
+++ b/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/BridgeProvider/TypeBridging.kt
@@ -654,18 +654,37 @@
     class AsOptionalWrapper(
         val wrappedObject: AnyBridge,
     ) : Bridge(
-        wrappedObject.swiftType.optional(),
-        wrappedObject.typeList.map { AnyBridge.TypePair(it.kotlinType, it.cType.nullable) },
+        swiftType = wrappedObject.swiftType.optional(),
+        typeList = wrappedObject.typeList.map {
+            // We can't use _Nullable on primitive numbers
+            val cType = if (it.kotlinType.isPrimitiveNumber) CType.NSNumber.nullable else it.cType.nullable
+            // If we use NSNumber* in ObjC we have to use NativePtr in Kotlin
+            val kotlinType = if (it.kotlinType.isPrimitiveNumber) KotlinType.KotlinObject else it.kotlinType
+            AnyBridge.TypePair(kotlinType, cType)
+        },
     ) {
+        private val nsNumberBridge by lazy { AsNSNumber((wrappedObject.swiftType as SirNominalType).typeArguments.single()) }
 
         override val inKotlinSources: ValueConversion
             get() = object : ValueConversion {
                 override fun swiftToKotlin(typeNamer: SirTypeNamer, valueExpression: MixedAST): MixedAST {
                     require(wrappedObject is SwiftToKotlinBridge)
-                    return valueExpression.eq(MixedAST.NativeNull).cond(
-                        MixedAST.Null,
-                        wrappedObject.inKotlinSources.swiftToKotlin(typeNamer, valueExpression)
-                    )
+
+                    val wrappedResult = wrappedObject.inKotlinSources.swiftToKotlin(typeNamer, valueExpression)
+                    val condition = if (typeList.size > 1 && wrappedResult is MixedAST.BinaryOp) {
+                        wrappedResult.replace(operator = MixedAST.Operator.OR) {
+                            it.op(MixedAST.Operator.EQUALS, MixedAST.NativeNull)
+                        }
+                    } else {
+                        valueExpression.op(MixedAST.Operator.EQUALS, MixedAST.NativeNull)
+                    }
+                    val result = if (wrappedResult is MixedAST.BinaryOp) {
+                        wrappedResult.replace { nsNumberBridge.inKotlinSources.swiftToKotlin(typeNamer, it) }
+                    } else {
+                        wrappedResult
+                    }
+
+                    return condition.cond(MixedAST.Null, result)
                 }
 
                 override fun kotlinToSwift(typeNamer: SirTypeNamer, valueExpression: MixedAST): MixedAST {
@@ -684,6 +703,18 @@
                             wrappedObject is AsExistential || wrappedObject is AsAnyBridgeable || wrappedObject is AsBlock ||
                             wrappedObject is SirCustomTypeTranslatorImpl.RangeBridge
                 )
+                val wrappedResult = wrappedObject.inSwiftSources.swiftToKotlin(typeNamer, valueExpression)
+                return wrappedResult.replace(operator = MixedAST.Operator.COMMA) {
+                    valueExpression.mapSwift {
+                        if (it is MixedAST.BinaryOp) {
+                            it
+                        } else {
+                            nsNumberBridge.inSwiftSources.swiftToKotlin(typeNamer, it)
+                        }
+                    }.op(MixedAST.Operator.SWIFT_ELVIS, wrappedObject.nil())
+                }
+
+
                 return valueExpression.mapSwift {
                     wrappedObject.inSwiftSources.swiftToKotlin(typeNamer, it)
                 }.op(MixedAST.Operator.SWIFT_ELVIS, wrappedObject.nil())
diff --git a/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/BridgeProvider/Types.kt b/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/BridgeProvider/Types.kt
index b1c1fe3..3797555 100644
--- a/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/BridgeProvider/Types.kt
+++ b/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/BridgeProvider/Types.kt
@@ -8,8 +8,12 @@
 internal sealed class CType {
     abstract fun render(name: String): String
 
-    val nullable: CType get() = ((this as? NullabilityAnnotated)?.wrapped ?: this).let { NullabilityAnnotated(it, Nullability.NULLABLE) }
-    val nonnulll: CType get() = ((this as? NullabilityAnnotated)?.wrapped ?: this).let { NullabilityAnnotated(it, Nullability.NONNULL) }
+    val nullable: CType
+        get() = NullabilityAnnotated(unwrapAnnotated(), Nullability.NULLABLE)
+    val nonnulll: CType
+        get() = NullabilityAnnotated(unwrapAnnotated(), Nullability.NONNULL)
+
+    fun unwrapAnnotated(): CType = (this as? NullabilityAnnotated)?.wrapped ?: this
 
     sealed class Predefined(private val repr: String) : CType() {
         override fun render(name: String): String = if (name.isBlank()) repr else "$repr $name"
diff --git a/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/tree/MixedAST.kt b/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/tree/MixedAST.kt
index 97e2929..66f6b9f 100644
--- a/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/tree/MixedAST.kt
+++ b/native/swift/sir-providers/src/org/jetbrains/kotlin/sir/providers/impl/tree/MixedAST.kt
@@ -137,6 +137,7 @@
         AS_EXCL(" as! "),
         ASSIGN(" = "),
         EQUALS(" == "),
+        OR(representation = " || "),
         IN(" in "),
         SWIFT_ELVIS(" ?? "),
         RANGE_KOTLIN(" .. "),
diff --git a/native/swift/swift-export-standalone-integration-tests/simple/testData/execution/ranges/ranges.kt b/native/swift/swift-export-standalone-integration-tests/simple/testData/execution/ranges/ranges.kt
index 0d04ee6..fd5a463 100644
--- a/native/swift/swift-export-standalone-integration-tests/simple/testData/execution/ranges/ranges.kt
+++ b/native/swift/swift-export-standalone-integration-tests/simple/testData/execution/ranges/ranges.kt
@@ -9,6 +9,6 @@
 
 fun simple(some: IntRange): IntRange = some.start - 1 .. some.endInclusive + 1
 
-//fun nullable(some: IntRange?): IntRange? = some?.let { simple(it) }
+fun nullable(some: IntRange?): IntRange? = some?.let { simple(it) }
 
 //fun total(list: List<IntRange>): IntRange = list.map { it.start }.min() .. list.map { it.endInclusive }.max()
diff --git a/native/swift/swift-export-standalone-integration-tests/simple/testData/execution/ranges/ranges.swift b/native/swift/swift-export-standalone-integration-tests/simple/testData/execution/ranges/ranges.swift
index fb8a406..30cd54e 100644
--- a/native/swift/swift-export-standalone-integration-tests/simple/testData/execution/ranges/ranges.swift
+++ b/native/swift/swift-export-standalone-integration-tests/simple/testData/execution/ranges/ranges.swift
@@ -16,11 +16,10 @@
 
 @Test
 func nullable() throws {
-    // Currently crashes with SIGSEGV
-    //let n1 = nullable(some: -10 ... -5)
-    //let n2 = nullable(some: nil)
-    //#expect(n1 == -11 ... -4)
-    //#expect(n2 == nil)
+    let n1 = nullable(some: -10 ... -5)
+    let n2 = nullable(some: nil)
+    #expect(n1 == -11 ... -4)
+    #expect(n2 == nil)
 }
 
 @Test
diff --git a/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.h b/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.h
index 66b3e33..1b55c0e 100644
--- a/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.h
+++ b/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.h
@@ -7,12 +7,18 @@
 
 void * generation_ranges_ranges_acceptClosed__TypesOfArguments__Swift_ClosedRange_Swift_Int32___(int32_t range_1, int32_t range_2);
 
+void * generation_ranges_ranges_acceptNullable__TypesOfArguments__Swift_Optional_Swift_ClosedRange_Swift_Int32____(NSNumber * _Nullable range_1, NSNumber * _Nullable range_2);
+
 void * generation_ranges_ranges_bar();
 
 void * generation_ranges_ranges_baz();
 
 void * generation_ranges_ranges_foo();
 
+NSArray<id> * generation_ranges_ranges_mapper__TypesOfArguments__Swift_Array_Swift_ClosedRange_Swift_Int64____(NSArray<id> * arg);
+
+void * generation_ranges_ranges_total__TypesOfArguments__Swift_Array_Swift_ClosedRange_Swift_Int32____(NSArray<id> * list);
+
 void * generation_ranges_ranges_unsupported();
 
 int32_t kotlin_ranges_closedRange_getEndInclusive_int(void * nativePtr);
diff --git a/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.kt b/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.kt
index adfae94..c50552c 100644
--- a/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.kt
+++ b/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.kt
@@ -18,6 +18,13 @@
     return kotlin.native.internal.ref.createRetainedExternalRCRef(_result)
 }
 
+@ExportedBridge("generation_ranges_ranges_acceptNullable__TypesOfArguments__Swift_Optional_Swift_ClosedRange_Swift_Int32____")
+public fun generation_ranges_ranges_acceptNullable__TypesOfArguments__Swift_Optional_Swift_ClosedRange_Swift_Int32____(range_1: kotlin.native.internal.NativePtr, range_2: kotlin.native.internal.NativePtr): kotlin.native.internal.NativePtr {
+    val __range = if (range_1 == kotlin.native.internal.NativePtr.NULL || range_2 == kotlin.native.internal.NativePtr.NULL) null else interpretObjCPointer<Int>(range_1) .. interpretObjCPointer<Int>(range_2)
+    val _result = generation.ranges.ranges.acceptNullable(__range)
+    return if (_result == null) kotlin.native.internal.NativePtr.NULL else kotlin.native.internal.ref.createRetainedExternalRCRef(_result)
+}
+
 @ExportedBridge("generation_ranges_ranges_bar")
 public fun generation_ranges_ranges_bar(): kotlin.native.internal.NativePtr {
     val _result = generation.ranges.ranges.bar()
@@ -36,6 +43,20 @@
     return kotlin.native.internal.ref.createRetainedExternalRCRef(_result)
 }
 
+@ExportedBridge("generation_ranges_ranges_mapper__TypesOfArguments__Swift_Array_Swift_ClosedRange_Swift_Int64____")
+public fun generation_ranges_ranges_mapper__TypesOfArguments__Swift_Array_Swift_ClosedRange_Swift_Int64____(arg: kotlin.native.internal.NativePtr): kotlin.native.internal.NativePtr {
+    val __arg = interpretObjCPointer<kotlin.collections.List<kotlin.ranges.LongRange>>(arg)
+    val _result = generation.ranges.ranges.mapper(__arg)
+    return _result.objcPtr()
+}
+
+@ExportedBridge("generation_ranges_ranges_total__TypesOfArguments__Swift_Array_Swift_ClosedRange_Swift_Int32____")
+public fun generation_ranges_ranges_total__TypesOfArguments__Swift_Array_Swift_ClosedRange_Swift_Int32____(list: kotlin.native.internal.NativePtr): kotlin.native.internal.NativePtr {
+    val __list = interpretObjCPointer<kotlin.collections.List<kotlin.ranges.IntRange>>(list)
+    val _result = generation.ranges.ranges.total(__list)
+    return kotlin.native.internal.ref.createRetainedExternalRCRef(_result)
+}
+
 @ExportedBridge("generation_ranges_ranges_unsupported")
 public fun generation_ranges_ranges_unsupported(): kotlin.native.internal.NativePtr {
     val _result = generation.ranges.ranges.unsupported()
diff --git a/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.swift b/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.swift
index 00942ab..87a1102 100644
--- a/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.swift
+++ b/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/golden_result/main/main.swift
@@ -17,6 +17,12 @@
         let _result = generation_ranges_ranges_acceptClosed__TypesOfArguments__Swift_ClosedRange_Swift_Int32___(range.lowerBound, range.upperBound)
         return kotlin_ranges_openEndRange_getStart_int(_result) ..< kotlin_ranges_openEndRange_getEndExclusive_int(_result)
     }
+    public static func acceptNullable(
+        range: Swift.ClosedRange<Swift.Int32>?
+    ) -> Swift.ClosedRange<Swift.Int32>? {
+        let _result = generation_ranges_ranges_acceptNullable__TypesOfArguments__Swift_Optional_Swift_ClosedRange_Swift_Int32____(range.map { it in NSNumber(value: it.lowerBound) } ?? nil, range.map { it in NSNumber(value: it.upperBound) } ?? nil)
+        return { switch _result { case nil: .none; case let res: kotlin_ranges_intRange_getStart_int(res) ... kotlin_ranges_intRange_getEndInclusive_int(res); } }()
+    }
     public static func bar() -> Swift.ClosedRange<Swift.Int32> {
         let _result = generation_ranges_ranges_bar()
         return kotlin_ranges_closedRange_getStart_int(_result) ... kotlin_ranges_closedRange_getEndInclusive_int(_result)
@@ -29,6 +35,17 @@
         let _result = generation_ranges_ranges_foo()
         return kotlin_ranges_intRange_getStart_int(_result) ... kotlin_ranges_intRange_getEndInclusive_int(_result)
     }
+    public static func mapper(
+        arg: [Swift.ClosedRange<Swift.Int64>]
+    ) -> [Swift.ClosedRange<Swift.Int32>] {
+        return generation_ranges_ranges_mapper__TypesOfArguments__Swift_Array_Swift_ClosedRange_Swift_Int64____(arg) as! Swift.Array<Swift.ClosedRange<Swift.Int32>>
+    }
+    public static func total(
+        list: [Swift.ClosedRange<Swift.Int32>]
+    ) -> Swift.ClosedRange<Swift.Int32> {
+        let _result = generation_ranges_ranges_total__TypesOfArguments__Swift_Array_Swift_ClosedRange_Swift_Int32____(list)
+        return kotlin_ranges_intRange_getStart_int(_result) ... kotlin_ranges_intRange_getEndInclusive_int(_result)
+    }
     public static func unsupported() -> any ExportedKotlinPackages.kotlin.ranges.ClosedRange {
         return KotlinRuntime.KotlinBase.__createProtocolWrapper(externalRCRef: generation_ranges_ranges_unsupported()) as! any ExportedKotlinPackages.kotlin.ranges.ClosedRange
     }
diff --git a/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/ranges.kt b/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/ranges.kt
index cba0978..c32cfc2 100644
--- a/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/ranges.kt
+++ b/native/swift/swift-export-standalone-integration-tests/simple/testData/generation/ranges/ranges.kt
@@ -8,8 +8,16 @@
     return 5L ..< 10L
 }
 
+fun mapper(arg: List<LongRange>): List<IntRange> =
+    arg.map { it.start.toInt() .. it.endInclusive.toInt() }
+
+fun total(list: List<IntRange>): IntRange = list.map { it.start }.min() .. list.map { it.endInclusive }.max()
+
 fun accept(range: IntRange): LongRange = range.start.toLong() .. range.endInclusive.toLong()
 
+fun acceptNullable(range: IntRange?): IntRange? =
+    if (range == null || range.isEmpty()) null else range
+
 fun acceptClosed(range: ClosedRange<Int>): OpenEndRange<Int> = range.start ..< range.endInclusive
 
 fun unsupported(): ClosedRange<String> = "alpha" .. "omega"