Skip to content

wrap-java: Supporting return type erasure bounds#626

Merged
ktoso merged 10 commits intoswiftlang:mainfrom
sidepelican:returngeneric_bound
Mar 17, 2026
Merged

wrap-java: Supporting return type erasure bounds#626
ktoso merged 10 commits intoswiftlang:mainfrom
sidepelican:returngeneric_bound

Conversation

@sidepelican
Copy link
Contributor

This PR is part of the improvements for #599.

Calling getGenericDeclaration() on a generated java.lang.reflect.TypeVariable currently results in a runtime crash:

JavaLangReflect/TypeVariable.swift:14: Fatal error: Java call threw unhandled exception: java.lang.NoSuchMethodError: Lsun/reflect/generics/reflectiveObjects/TypeVariableImpl;.getGenericDeclaration()Ljava/lang/Object;

This is because the JNI method lookup is searching for a signature returning Object, whereas the actual erased signature in Java returns GenericDeclaration (the upper bound of the type parameter D).

Currently, the @JavaMethod macro expands using JavaObject?.self as the resultType for the erased generic return type.
Since the macro cannot infer the original Java bounds from the Swift method signature alone, it defaults to Object.

I have introduced a new macro argument, typeErasedResultBound, to explicitly provide the bound type.

With this change, we can specify the correct type for JNI method resolution:

@JavaMethod(typeErasedResult: "D!", typeErasedResultBound: GenericDeclaration?.self)
public func getGenericDeclaration() -> D!
  let result$ = {
    do {
      return try dynamicJavaMethodCall(methodName: "getGenericDeclaration", resultType: /*type-erased:D*/ GenericDeclaration?.self)
    } catch {
  ...

@sidepelican sidepelican requested a review from ktoso as a code owner March 17, 2026 07:16
sidepelican and others added 2 commits March 17, 2026 16:21
Co-authored-by: Konrad `ktoso` Malawski <konrad.malawski@project13.pl>
typeErasedResult: String? = nil
) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")

@attached(body)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could need some docs why this one does require the erased result types and then do a See: to the "main one" for more docs.

) = #externalMacro(module: "SwiftJavaMacros", type: "JavaMethodMacro")

@attached(body)
public macro JavaMethod<Result: JavaValue>(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually... how about we keep ONE macro, and diagnose from inside the macro if typeErasedResultBound was provided but the typeErasedResult wasn't? Since that's what you're trying to enforce here. I'd like to avoid having many overloads here in the sources so that docs are easy to find

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.

Very nice, one nitpick about the new macro declaration, best if we could keep the single declaration, wdyt?

@sidepelican
Copy link
Contributor Author

I was able to unify the macro definitions.
I have added an explanation for typeErasedResultBound. Let me know what you think.

@ktoso
Copy link
Collaborator

ktoso commented Mar 17, 2026

Very nice, LGTM; only nitpickt that the docs were not showing a wildcard, a wildcard is ?

@ktoso ktoso merged commit f308466 into swiftlang:main Mar 17, 2026
61 checks passed
@sidepelican sidepelican deleted the returngeneric_bound branch March 18, 2026 02:52
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.

2 participants