Skip to content

Commit

Permalink
update: forward declarations in c and objc (#4477)
Browse files Browse the repository at this point in the history
  • Loading branch information
danil-pavlov authored Oct 22, 2024
1 parent 64b2e05 commit ec7a083
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 46 deletions.
88 changes: 44 additions & 44 deletions docs/topics/multiplatform/multiplatform-compatibility-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -914,8 +914,7 @@ Here's the planned deprecation cycle:

**What's changed?**
The JetBrains team has revamped the approach to forward declarations in Kotlin to make their behavior more predictable
and prepare this functionality for the upcoming Kotlin 2.0 release. From now on:
The JetBrains team has revamped the approach to forward declarations in Kotlin to make their behavior more predictable:
* You can only import forward declarations using the `cnames` or ` objcnames` packages.
* You need to explicitly make a cast to and from the corresponding C and Objective-C forward declaration.
Expand All @@ -927,48 +926,49 @@ and prepare this functionality for the upcoming Kotlin 2.0 release. From now on:
Now, you can only use a special forward declaration package for that: `import cnames.structs.cstructName`.
The same is true for `objcnames`.

* Consider two objcinterop libraries, one that uses `objcnames.protocols.ForwardDeclaredProtocolProtocol` and the other that has an actual definition:

```ObjC
// First objcinterop library
#import <Foundation/Foundation.h>

@protocol ForwardDeclaredProtocol;

NSString* consumeProtocol(id<ForwardDeclaredProtocol> s) {
return [NSString stringWithUTF8String:"Protocol"];
}
```

```ObjC
// Second objcinterop library
// Header:
#import <Foundation/Foundation.h>
@protocol ForwardDeclaredProtocol
@end
// Implementation:
@implementation ForwardDeclaredProtocolImpl : NSObject
@end;

id<ForwardDeclaredProtocol> produceProtocol() {
return [ForwardDeclaredProtocolImpl new];
}
```

Previously, it was possible to transfer objects between them seamlessly. Now, an explicit `as` cast is required
for the forward declaration:

```kotlin
// Kotlin code:
fun test() {
consumeProtocol(produceProtocol() as objcnames.protocols.ForwardDeclaredProtocolProtocol)
}
```

> The casting to `objcnames.protocols.ForwardDeclaredProtocolProtocol` is only allowed from the corresponding real class.
> Otherwise, you'll get an error.
>
{style="note"}
* Consider two objcinterop libraries: one that uses `objcnames.protocols.ForwardDeclaredProtocolProtocol` and another
that has an actual definition:

```ObjC
// First objcinterop library
#import <Foundation/Foundation.h>

@protocol ForwardDeclaredProtocol;

NSString* consumeProtocol(id<ForwardDeclaredProtocol> s) {
return [NSString stringWithUTF8String:"Protocol"];
}
```

```ObjC
// Second objcinterop library
// Header:
#import <Foundation/Foundation.h>
@protocol ForwardDeclaredProtocol
@end
// Implementation:
@interface ForwardDeclaredProtocolImpl : NSObject <ForwardDeclaredProtocol>
@end

id<ForwardDeclaredProtocol> produceProtocol() {
return [ForwardDeclaredProtocolImpl new];
}
```

Previously, it was possible to transfer objects between them seamlessly. Now, an explicit `as` cast is required
for the forward declaration:

```kotlin
// Kotlin code:
fun test() {
consumeProtocol(produceProtocol() as objcnames.protocols.ForwardDeclaredProtocolProtocol)
}
```

> You can only cast to `objcnames.protocols.ForwardDeclaredProtocolProtocol` from the corresponding real class.
> Otherwise, you'll get an error.
>
{style="note"}
**When do the changes take effect?**
Expand Down
48 changes: 47 additions & 1 deletion docs/topics/native/native-c-interop.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,4 +458,50 @@ fun readData(fd: Int) {
```

Here we use service function `usePinned`, which pins an object, executes block and unpins it on normal and
exception paths.
exception paths.

### Forward declarations

To import forward declarations, use the `cnames` package. For example, to import a `cstructName` forward declaration
declared in a C library with a `library.package`, use a special forward declaration package:
`import cnames.structs.cstructName`.

Consider two cinterop libraries: one that has a forward declaration of a struct and another
with an actual implementation in another package:

```C
// First C library
#include <stdio.h>

struct ForwardDeclaredStruct;

void consumeStruct(struct ForwardDeclaredStruct* s) {
printf("Struct consumed\n");
}
```
```C
// Second C library
// Header:
#include <stdlib.h>
struct ForwardDeclaredStruct {
int data;
};
// Implementation:
struct ForwardDeclaredStruct* produceStruct() {
struct ForwardDeclaredStruct* s = malloc(sizeof(struct ForwardDeclaredStruct));
s->data = 42;
return s;
}
```

To transfer objects between the two libraries, use an explicit `as` cast in you Kotlin code:

```kotlin
// Kotlin code:
fun test() {
consumeStruct(produceStruct() as CPointer<cnames.structs.ForwardDeclaredStruct>)
}
```
55 changes: 54 additions & 1 deletion docs/topics/native/native-objc-interop.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ A Swift/Objective-C initializer is imported to Kotlin as constructors or as fact
The latter happens with initializers declared in the Objective-C category or as a Swift extension,
because Kotlin has no concept of extension constructors.
Kotlin constructors are imported as initializers to Swift/Objective-C.
> Before importing Swift initializers to Kotlin, don't forget to annotate them with `@objc`.
>
{style="tip"}
Kotlin constructors are imported as initializers to Swift/Objective-C.
### Setters
Expand Down Expand Up @@ -573,6 +577,55 @@ binaries.framework {
}
```

### Forward declarations

To import forward declarations, use the `objcnames.classes` and `objcnames.protocols` packages. For example,
to import a `objcprotocolName` forward declaration declared in an Objective-C library with a `library.package`,
use a special forward declaration package: `import objcnames.protocols.objcprotocolName`.

Consider two objcinterop libraries: one that uses `objcnames.protocols.ForwardDeclaredProtocolProtocol`
and another with an actual implementation in another package:

```ObjC
// First objcinterop library
#import <Foundation/Foundation.h>

@protocol ForwardDeclaredProtocol;

NSString* consumeProtocol(id<ForwardDeclaredProtocol> s) {
return [NSString stringWithUTF8String:"Protocol"];
}
```
```ObjC
// Second objcinterop library
// Header:
#import <Foundation/Foundation.h>
@protocol ForwardDeclaredProtocol
@end
// Implementation:
@interface ForwardDeclaredProtocolImpl : NSObject <ForwardDeclaredProtocol>
@end
id<ForwardDeclaredProtocol> produceProtocol() {
return [ForwardDeclaredProtocolImpl new];
}
```

To transfer objects between the two libraries, use an explicit `as` cast in you Kotlin code:

```kotlin
// Kotlin code:
fun test() {
consumeProtocol(produceProtocol() as objcnames.protocols.ForwardDeclaredProtocolProtocol)
}
```

> Ypu can only cast to `objcnames.protocols.ForwardDeclaredProtocolProtocol` from the corresponding real class.
> Otherwise, you'll get an error.
>
{style="note"}

## Casting between mapped types

When writing Kotlin code, an object may need to be converted from a Kotlin type to the equivalent Swift/Objective-C type
Expand Down

0 comments on commit ec7a083

Please sign in to comment.