Skip to content

Conversation

@NakaokaRei
Copy link
Contributor

@NakaokaRei NakaokaRei commented Nov 28, 2025

Summary

Problem

When a Swift module enables .interoperabilityMode(.Cxx), jni.h is interpreted in C++ mode where JNIEnv is a struct type. However, the generated JNI bridge code assumes C mode where JNIEnv is a pointer type, causing type mismatch errors:

cannot convert value of type 'UnsafeMutablePointer<JNIEnv_>' to expected argument type 'UnsafeMutablePointer<UnsafePointer<JNINativeInterface_>?>?'

Solution

Introduce C-compatible type aliases (CJNIEnv, Cjstring, etc.) that always resolve to pointer types regardless of C/C++ mode:

#ifdef __cplusplus
typedef const JNINativeInterface_ *CJNIEnv;
typedef _jstring *Cjstring;
#else
typedef JNIEnv CJNIEnv;
typedef jstring Cjstring;
#endif

NakaokaRei and others added 4 commits November 28, 2025 14:53
When Swift modules have C++ interoperability enabled, jni.h is parsed
in C++ mode where JNIEnv is defined as a struct (JNIEnv_). However,
SwiftJava modules are compiled without C++ interop, causing JNIEnv
to be a pointer type. This mismatch causes type errors.

This fix temporarily undefines __cplusplus before including jni.h,
forcing C-mode type definitions regardless of the caller's
interoperability settings.

Fixes: swiftlang#391
Use module.modulemap's requires !cplusplus directive instead of
preprocessor tricks to ensure JNI types are always parsed in C mode.

This fixes type mismatches when Swift modules with C++ interoperability
enabled import CSwiftJavaJNI, as jni.h defines JNIEnv differently in
C mode (pointer) vs C++ mode (struct).

Fixes: swiftlang#391

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit introduces C-compatible type aliases (CJNIEnv, Cjobject,
Cjstring, etc.) to resolve type mismatches when Swift modules with
C++ interoperability enabled import SwiftJava.

The root cause is that jni.h defines JNI types differently in C vs C++:
- C mode: JNIEnv = const struct JNINativeInterface_ * (pointer)
- C++ mode: JNIEnv = JNIEnv_ (struct)

When a module like HelloWorld has C++ interop enabled but imports
SwiftJava (which is compiled without C++ interop), the JNI type
definitions conflict, causing compilation errors.

Changes:
- CSwiftJavaJNI.h: Add C-compatible type aliases that resolve to the
  same underlying type in both C and C++ modes
- JavaType+JNI.swift: Use C-compatible types (Cjstring, Cjobject, etc.)
  in generated JNI method signatures
- JNISwift2JavaGenerator: Add unsafeBitCast for return values to convert
  between SwiftJava's internal JNI types and C-compatible types
- JavaValue.swift, JavaEnvironment.swift: Use CJNIEnv in JNIEnvironment
  typealias for consistent type definitions

Fixes: swiftlang#391

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Collaborator

@ktoso ktoso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, thank you! I guess we should start testing with C++ interoperability as well huh? 🤔

Update test expectations to match the new code generation output:
- Use CJNIEnv, Cjstring, Cjobject, CjbyteArray, CjlongArray types
- Add unsafeBitCast wrapper for return values
- Remove explicit return statement from async function expectations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@NakaokaRei
Copy link
Contributor Author

@ktoso
Thanks for your review!

I guess we should start testing with C++ interoperability as well huh? 🤔

Wrapping existing C++ code in Swift and exposing it to Java is a very useful use case, so I think it's worth adding tests to ensure this works correctly.

I've fixed the test failures - the test expectations needed to be updated to match the new code generation output (C-compatible types and unsafeBitCast wrapper). Could you please review again?

@ktoso
Copy link
Collaborator

ktoso commented Dec 1, 2025

I'll want to add a CI mode to check this before merging, thanks for the PR!

@ktoso
Copy link
Collaborator

ktoso commented Dec 1, 2025

Would you mind having a look at the verification jobs eb8bfe4 (#463) and following up so we pass it in CI please @NakaokaRei ?

Seems this caused some real issues?

/__w/swift-java/swift-java/.build/checkouts/swift-system/Sources/System/Internals/Constants.swift:146:30: error: var 'EAGAIN' cannot be used in an '@_alwaysEmitIntoClient' function because 'CSystem' was not imported by this file
144 | 
145 | @_alwaysEmitIntoClient
146 | internal var _EAGAIN: CInt { EAGAIN }
    |                              `- error: var 'EAGAIN' cannot be used in an '@_alwaysEmitIntoClient' function because 'CSystem' was not imported by this file
147 | 
148 | @_alwaysEmitIntoClient
Error: Process completed with exit code 1.

Since you're familiar with the C++ interop it'd be helpful if you could have a look, thanks

NakaokaRei and others added 3 commits December 2, 2025 10:13
… package

The previous approach tried to build swift-java itself with C++ interop
mode enabled, which fails because dependencies like swift-system don't
support being built in C++ mode.

The actual goal is to verify that projects depending on swift-java can
build with C++ interop enabled. This change:

- Replaces test-swift-cxx-interop jobs with verify-samples-cxx-interop
- Adds CXX_INTEROP environment variable support to all sample Package.swift
- Uses .interoperabilityMode(.Cxx) conditionally when CXX_INTEROP=1
- Tests all 7 sample apps with C++ interop on Linux (6.1.3, 6.2, nightly)
  and macOS (6.2)

Fixes: swiftlang#391

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@NakaokaRei
Copy link
Contributor Author

@ktoso

Thanks for flagging this!
I think the real-world use case we should be testing is: can projects that depend on swift-java build successfully when they enable C++ interoperability mode? This is what users actually do - they enable C++ interop in their own projects, not in swift-java's source.

So I've reworked the approach in 11ae2af

  • Replaced test-swift-cxx-interop jobs with verify-samples-cxx-interop
  • Added CXX_INTEROP environment variable support to all sample Package.swift files
  • When CXX_INTEROP=1, samples enable .interoperabilityMode(.Cxx) in their Swift settings
  • Tests all 7 sample apps with C++ interop on Linux (6.1.3, 6.2, nightly) and macOS (6.2)

This properly validates that swift-java's public API is compatible with C++ interop mode without requiring swift-java's dependencies to support it.

Could you review it again ??

'SwiftJavaExtractJNISampleApp',
]
env:
SWIFT_JAVA_VERBOSE: true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We lost the verbose here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ktoso Fixed here 🙏
9cd3191

NakaokaRei and others added 2 commits December 2, 2025 19:17
- Update ImplementsJavaMacro to generate CJNIEnv instead of JNIEnv
  to ensure compatibility with C++ interoperability mode
- Update JNIAsyncTests LegacyFuture test expectations to use
  C-compatible types (CJNIEnv, Cjobject)

See: swiftlang#391

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@NakaokaRei NakaokaRei requested a review from ktoso December 2, 2025 06:35
In C++ interop mode, jobject is UnsafeMutablePointer<_jobject>, but
the swift-java library (compiled without C++ interop) expects jobject
as OpaquePointer. Use unsafeBitCast to ensure compatibility in both
modes since both have the same memory representation (a pointer).

This fixes the "cannot convert value of type 'jobject' to expected
argument type 'OpaquePointer'" error when building sample apps with
C++ interoperability enabled.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@ktoso
Copy link
Collaborator

ktoso commented Dec 2, 2025

Tests all 7 sample apps with C++ interop on Linux (6.1.3, 6.2, nightly) and macOS (6.2)

This is likely too many CI jobs, they are pretty expensive to run so we should just pick the stable 6.2 for C++ validation please.

- Reduce C++ interop CI jobs to Swift 6.2 only (Linux)
- Add JNI header path to JavaProbablyPrime sample
- Fix CSwiftJavaJNI.h to use struct JNINativeInterface_ for Android NDK compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Collaborator

@ktoso ktoso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little bit concerned about the last few commits, they seem to be touching a bunch of things and are generated by Claude Code.

Would you mind verifying the changes some more locally?

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.

JavaKitSampleApp can't build with .interoperabilityMode(.Cxx) in swiftSettings

2 participants