Skip to content

JavaKit: Macro and translator support for mapping Java classes to Swift classes #140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Oct 31, 2024

Conversation

DougGregor
Copy link
Member

@DougGregor DougGregor commented Oct 31, 2024

Java classes are currently translated in Swift structs, and we use is/as
functions to cast up and down the hierarchy. Start down an alternative
path where we translate Java classes into Swift classes, making proper use
of inheritance in Swift to reflect inheritance in Java.

This step updates the @javaclass macro implementation to check whether
it is being applied to a class (rather than a struct) and adjust its
behavior accordingly:

  • The superclass is extracted from the inheritance clause of the class
    rather than the "extends" argument to the macro.
  • The "full Java class name" static member becomes a "class" member.
    For everything other than JavaObject itself, it is an override.
  • The javaHolder property is only emitted into JavaObject, nowhere else.
  • The init(javaHolder:) initializer becomes required. For everything
    other than JavaObject, it's implemented as a super.init call.
  • The JavaSuperclass typealias is no longer emitted.

All of this is keyed off the subject of the attribute being a "class"
rather than a "struct", so we keep the existing struct versions working.

Relatedly, extend the translator with an option to map Java classes to
Swift classes. This involves a number of changes:

  • Emitting each as an "open class" rather than "public struct"
  • Emitting the superclass into the inheritance clause rather than the "extends" argument
  • Emitting methods as "open" rather than "public", and "override" when we are overriding a method declared in a superclass
  • Emitting initializers as "convenience" since we still always build a Java object behind the scenes
  • Only emit methods and fields declared in the class, not inherited ones

This option is only currently only enabled in test cases while we stage in this functionality.

This is for issue #132.

…ft classes

Java classes are currently translated in Swift structs, and we use is/as
functions to cast up and down the hierarchy. Start down an alternative
path where we translate Java classes into Swift classes, making proper use
of inheritance in Swift to reflect inheritance in Java.

This step updates the @javaclass macro implementation to check whether
it is being applied to a class (rather than a struct) and adjust its
behavior accordingly:

* The superclass is extracted from the inheritance clause of the class
rather than the "extends" argument to the macro.
* The "full Java class name" static member becomes a "class" member.
For everything other than JavaObject itself, it is an override.
* The `javaHolder` property is only emitted into JavaObject, nowhere else.
* The `init(javaHolder:)` initializer becomes required. For everything
other than JavaObject, it's implemented as a super.init call.
* The `JavaSuperclass` typealias is no longer emitted.

All of this is keyed off the subject of the attribute being a "class"
rather than a "struct", so we keep the existing struct versions working.

Relatedly, extend the translator with an option to map Java classes to
Swift classes. This involves a number of changes:

* Emitting each as an "open class" rather than "public struct"
* Emitting the superclass into the inheritance clause rather than the
"extends" argument
* Emitting methods as "open" rather than "public"
* Only emit methods and fields declared in the class, not inherited ones

This option is only currently only enabled in test cases while we stage
in this functionality.
…lass

When we import a class from Java into Swift, we check whether its superclass
was also imported before generating a reference to that superclass. If it
isn't there, we fell back to JavaObject. That's too conservative, because
there might be a superclass in between that we could use.

Instead, walk up the superclass chain until we find the most-specific
superclass that *is* mapped into Swift, and use that as the generated
superclass. Additionally, use this as the basis for determining when we
need the "override" keyword when in the class-generating mode.
No functionality changes here; we're just more consistent about putting
JavaObject in as the extended type.
We don't need this function when generating classes, because we'll already
get the subtype conversion for free.
@ktoso
Copy link
Collaborator

ktoso commented Oct 31, 2024

Hm, definitely promising direction. I like that the object holder is in the super class, that makes sense. open also makes sense though maybe I'm unnecessarily worried about actually using this ability to subclass those 😅

So far looking good :)

When looking for a declared method with a given signature, we need to
check all superclasses that have been mapped into Swift, not just the
closest one.
Swift doesn't have the notion of "protected", so treat these as "open"
(or "public") as approriate.
Keep interfaces as structs for now. Their future is yet unwritten.
…va and Swift

Java allows more subtyping relationships for the result types in a covariant
method override than Swift does, such as covariant arrays and wildcards. Take
the Swift semantics into account when determining whether to apply the
`override` keyword.
Swift has inheritance of initializers, while Java does not have inheritance
of constructors. This means that Swift's attempt to treat a convenience
initializer as an override within a subclass can cause problems with
mismatched signatures. Use `@_nonoverride` to avoid this problem.
@@ -314,7 +314,7 @@ class Java2SwiftTests: XCTestCase {
""",
"""
@JavaMethod
public convenience init(environment: JNIEnvironment? = nil)
@_nonoverride public convenience init(environment: JNIEnvironment? = nil)
Copy link
Collaborator

Choose a reason for hiding this comment

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

hah sneaky 👍

@ktoso
Copy link
Collaborator

ktoso commented Oct 31, 2024

Looks good 👍

@DougGregor
Copy link
Member Author

Merging this one. Aside from bringing in another Java class or two into JavaKit, it doesn't affect the current behavior.

@DougGregor DougGregor merged commit 37bce97 into swiftlang:main Oct 31, 2024
11 checks passed
@DougGregor DougGregor deleted the java-classes-to-swift-classes branch October 31, 2024 15:59
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