Skip to content

JNI: exclude protocol extension members from callback requirements - #2

Merged
teamhimeh merged 1 commit into
qoncept-forkfrom
codex/fix-jni-protocol-extension-requirements
Oct 23, 2026
Merged

teamhimeh merged 1 commit into
qoncept-forkfrom
codex/fix-jni-protocol-extension-requirements

Conversation

@teamhimeh

@teamhimeh teamhimeh commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Protocol extension members are currently treated as JNI callback requirements. An extra method becomes an abstract Java method, while a computed property, static helper, or initializer in an extension can disable Java callback implementations entirely, even though Swift already provides its implementation.

Example: Swift extension helpers with a Java implementation

Consider this Swift API:

public protocol MessageProvider {
  func message() -> String
}

extension MessageProvider {
  public func decoratedMessage() -> String {
    "[\(message())]"
  }

  public var length: Int64 {
    Int64(message().count)
  }
}

public func decorate(_ provider: some MessageProvider) -> String {
  provider.decoratedMessage()
}

message() is a protocol requirement that a conformer must implement. decoratedMessage() and length already have implementations in Swift.

Before this change, the generator incorrectly adds decoratedMessage() as an abstract Java method. Adding an extension property such as length also makes the protocol fail the callback-support check, so it cannot be implemented as an ordinary Java callback.

With this change, the Java callback contract contains only the actual requirement:

public interface MessageProvider extends SwiftDowncastable {
    String message();
}

A Java caller can therefore supply just that implementation:

MessageProvider provider = () -> "hello";
String result = MySwiftLibrary.decorate(provider);
// result is "[hello]"

The call goes from Java to Swift's decorate(), then to Swift's decoratedMessage(), and finally back to the Java implementation of message(). Swift supplies the formatting logic; Java supplies the message. Extension properties and static helpers likewise keep their Swift implementations without becoming additional Java callback requirements.

The new runtime tests exercise this round trip and also verify that a protocol value returned from Swift can still dispatch its requirements through the generated Java box.

Changes

  • Distinguish requirements declared in a protocol body from members declared in extensions.
  • Apply that distinction consistently to Java interfaces, existential boxes, and Swift callback wrappers.
  • Reject unsupported static/property/initializer requirements only when they are actual protocol requirements.
  • Add extraction and generator regression tests, Java runtime tests for extension methods/properties/static helpers and returned boxes, and user documentation.

Future work: expose Swift implementations as Java default methods

The intended end state is to expose supported protocol extension instance methods and property accessors as Java interface default methods that forward to the existing Swift implementations. Swift would remain the source of the implementation, while Java callers could use the same convenience API directly without implementing additional abstract methods.

For the example above, the desired Java API would allow:

MessageProvider provider = () -> "hello";
String decorated = provider.decoratedMessage(); // "[hello]"
long length = provider.getLength();             // 5

These direct calls are a future API, not generated by this PR. Today, the extension implementation is exercised through a Swift entry point such as MySwiftLibrary.decorate(provider).

A future default-method bridge must preserve Swift dispatch semantics: extension-only helpers execute their Swift bodies, and calls from those bodies to protocol requirements dispatch to the Java conformer's implementation. A requirement with a default implementation in a Swift extension remains a protocol requirement; exposing that implementation must also preserve overrides. Static helpers and initializers need separate treatment because Java interface instance default methods cannot represent them.

This PR establishes the prerequisite distinction between protocol requirements and extension implementations. Generating the default-method bridges is intentionally left to a follow-up change.

Validation

  • Confirmed the generator regressions and JNI sample fail before the fix.
  • swift test --disable-experimental-prebuilts --disable-sandbox: passed.
  • ./gradlew :Samples:SwiftJavaExtractJNISampleApp:test --no-daemon: passed, including the three new Java runtime tests.
  • swift-format lint --strict on changed Swift files and git diff --check: passed.

Tested with Apple Swift 6.4 and JetBrains Runtime 25.0.2 on macOS.

Protocol extensions provide Swift implementations rather than additional
Java callback requirements. Distinguish those members at extraction time
and filter them when generating Java interfaces, existential boxes, and
Swift callback wrappers. Only unsupported requirements declared in the
protocol body should prevent a Java implementation.

Add regression tests and Java runtime coverage for calling Swift extension
helpers through Java callbacks and returned protocol boxes.
@teamhimeh
teamhimeh merged commit a0f0ae8 into qoncept-fork Oct 23, 2026
33 of 42 checks passed
@teamhimeh

Copy link
Copy Markdown
Collaborator Author

related: swiftlang#934

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant