Skip to main content

0.6.0

New features

Wrappers for global functions and properties

Kotlin exposes global functions under a namespace named after the file that contains the global functions.

So, for example, the following Kotlin code:

Kotlin (File.kt)
fun globalFunction(i: Int): Int = i

can be called from Swift like this:

Swift without SKIE
FileKt.globalFunction(i: 1)

SKIE now improves the syntax by generating wrappers for these global functions and properties. So the same function can now be called simply:

Swift with SKIE
globalFunction(i: 1)

Read more about Global Functions and Properties

Wrappers for interface extension functions and properties

Interface extensions experience a similar problem as global functions.

For example:

Kotlin (File.kt)
interface I

class C : I

fun I.interfaceExtension(i: Int): Int = i

has to be called like this:

Swift without SKIE
FileKt.interfaceExtension(C(), i: 1)

The solution to this problem is also similar, so now interface extensions can be called using the same syntax as class extensions:

Swift with SKIE
C().interfaceExtension(i: 1)

Read more about Interface Extensions

Improvements

Readability of the generated Swift code

This release brings significant improvements to the readability of the code that SKIE generates.

The most apparent change is that we were able to reduce the usage of the SKIE type aliases. These were generated for every Kotlin type exposed to Swift and used exclusively in the generated code. Now, the Kotlin types are referenced using their fully qualified names wherever possible.

Additionally, the generated code now does not use function references when calling functions. Instead, it uses the direct call syntax. For example, SKIE now produces foo(i: 1) instead of foo(i:)(1).

We also fixed a few bugs in the code generator that were affecting the code formatting.

For example:

  • The '}' character was not always on the correct line.
  • New lines were missing in some places.

Enum case names

Kotlin and Swift have different conventions for naming enum cases. Swift uses camelCase, whereas Kotlin uses UPPER_SNAKE_CASE or PascalCase.

To make the interop between Kotlin and Swift enums more natural, Kotlin renames the enum cases such that they follow the Swift naming convention. However, the algorithm that Kotlin uses for renaming the enum cases accounts only for UPPER_SNAKE_CASE, not PascalCase.

So, for example, the cases of the following Kotlin enum:

Kotlin
enum class E {
camelCase, PascalCase, UPPER_SNAKE_CASE;
}

would be renamed to:

Swift without SKIE
case camelcase
case pascalcase
case upperSnakeCase

Notice that the PascalCase and camelCase cases are missing the capitalized 'C'.

SKIE now implements a more sophisticated algorithm for renaming the enum cases, which produces the expected:

Swift with SKIE
case camelCase
case pascalCase
case upperSnakeCase

At the same time SKIE now improves the support for names that collide with Swift keywords and some Obj-C methods.

For example:

  • return was previously renamed to return_, which is unnecessary because return is a soft keyword in Swift.
  • zone was previously not renamed, which is incorrect because it collides with an Obj-C method. So, it now has the the prefix as some other Obj-C methods.
caution

This is a breaking change; you might need to update your Swift code accordingly. Alternatively, you can disable this behavior using the SKIE configuration.

Read more about Enums

Optional overload for the onEnum function

The onEnum functions are part of the sealed class interop feature. They are used to convert the Kotlin sealed class to a Swift enum generated by SKIE.

For example:

Kotlin
sealed interface I

class X : I

class Y : I
Swift with SKIE
let i: I = X()

switch onEnum(of: i) {
case .x:
print("x")
case .y:
print("y")
}

This approach simulates the sealed class pattern in Swift quite well. However, it has one limitation - it doesn't natively support optional values, which is a relatively common use case.

To solve this problem, SKIE now generates an additional overload for the onEnum function that accepts an optional value and returns an optional enum case. So, it's now possible to use optional values without manually unwrapping them first. For example:

Swift with SKIE
let i: I? = X()

switch onEnum(of: i) {
case .x:
print("x")
case .y:
print("y")
case .none:
print("none")
}

Read more about Sealed Classes

Support for @Deprecated annotation

Kotlin propagates the @Deprecated annotation to the Obj-C header it produces. SKIE now does the same for the generated Swift code.

For example:

Kotlin
@Deprecated("Some message")
fun foo() {
}

now produces:

Swift generated by SKIE
@available(*, deprecated, message: "Some message")
public func foo() {
return Kotlin.AKt.foo()
}

And similarly, for the 'ERROR' level:

Kotlin
@Deprecated("Some message", level = DeprecationLevel.ERROR)
fun foo() {
}
Swift generated by SKIE
@available(*, unavailable, message: "Some message")
public func foo() {
fatalError("Unavailable")
}

Warnings about name collisions

SKIE now prints a warning when it has to rename a declaration due to a name collision. Be aware that these warnings are not produced in all situations yet. For example, SKIE currently does not print a warning when the renaming is done by the Kotlin compiler and not SKIE.

Read more about overloading functions and name collisions

Configurable function overload resolution

SKIE uses a different function overload resolution than Kotlin in order to reduce the number of unnecessary name changes. Starting with this release, SKIE can now also use the original Kotlin algorithm, which can be configured using the SKIE configuration. The primary use case for switching back to the Kotlin algorithm is during the migration to SKIE. In all other cases it's recommended to use the SKIE algorithm.

Read more about overloading functions and name collisions

Cinterop support

SKIE can now generate code that uses types provided by custom cinterop bindings. The problem with these types was that SKIE doesn't know what framework will provide their implementation at runtime.

To avoid compilation issues, SKIE now generates only a placeholder declaration, which cannot be called if it doesn't know the framework name. The framework name can be manually configured using the SKIE configuration; in that case, SKIE will be able to generate the correct declaration. Providing the framework name is only necessary if you need to use the declaration generated by SKIE because you can still call the original Kotlin declaration.

You can read more about the Cinterop support here.

Bug fixes

  • Fix a runtime crash caused by returning a Flow from a suspend function (#42)
  • Fix some edge cases in the collision detection algorithm
  • Fix support for @ShouldRefineInSwift annotation
  • Fix compile-time crash caused by importing some builtin frameworks with Apinotes in an unexpected format
  • Fix some edge cases in the type translation for the ObjCProtocol type
  • Fix type translation for external Obj-C types that are renamed in Swift, for example: NSURLSession is now correctly translated to URLSession
  • Fix type translation for external Obj-C types that are generic in Obj-C but not in Swift
  • Fix that non-optional return types in throwing functions were, in some cases, incorrectly converted to an optional type
  • SKIE Swift Flows parametrized by bridged types can now be converted between each other using the conversion constructors, for example: SkieSwiftStateFlow<String> can now be converted to SkieSwiftFlow<String>
  • Fix that SKIE added dependency on the Coroutines library even when the Coroutines interop was disabled