From bda1111ed79aca6cf2358cd43f521bda73ab2660 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 1 May 2026 12:28:17 +0900 Subject: [PATCH 1/4] Fix wrap-java swiftname escaping behavior --- .../JavaClassTranslator.swift | 38 +++++++++++------- Sources/SwiftJavaToolLib/StringExtras.swift | 18 +++++---- .../WrapJavaTests/BasicWrapJavaTests.swift | 40 +++++++++++++++++++ 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 4742cd8b1..566a8e178 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -259,11 +259,6 @@ struct JavaClassTranslator { continue } - guard method.getName().isValidSwiftFunctionName else { - log.warning("Skipping method \(method.getName()) because it is not a valid Swift function name") - continue - } - addMethod(method, isNative: false) } @@ -949,8 +944,8 @@ extension JavaClassTranslator { // --- Handle other effects let throwsStr = javaMethod.throwsCheckedException ? "throws" : "" - let swiftMethodName = javaMethod.getName().escapedSwiftName - let swiftOptionalMethodName = "\(javaMethod.getName())Optional".escapedSwiftName + let (swiftMethodName, swiftMethodNameEscaped) = javaMethod.getName().escapedSwiftName + let (swiftOptionalMethodName, _) = "\(javaMethod.getName())Optional".escapedSwiftName // --- Handle docs for the generated method. // Include the original Java signature @@ -987,10 +982,8 @@ extension JavaClassTranslator { } // Do we need to record any generic information, in order to enable type-erasure for the upcalls? var parameters: [String] = [] - // If the method name is "init", we need to explicitly specify it in the annotation - // because "init" is a Swift keyword and will be escaped in the function name via `init` - if javaMethod.getName() == "init" { - parameters.append("\"init\"") + if swiftMethodNameEscaped { + parameters.append("\"\(javaMethod.getName())\"") } if hasTypeEraseGenericResultType { let returnType = javaMethod.getReturnType()! @@ -1085,8 +1078,23 @@ extension JavaClassTranslator { preferValueTypes: true, outerOptional: .implicitlyUnwrappedOptional ) - let fieldAttribute: AttributeSyntax = javaField.isStatic ? "@JavaStaticField" : "@JavaField" - let swiftFieldName = javaField.getName().escapedSwiftName + let (swiftFieldName, swiftFieldNameIsEscaped) = javaField.getName().escapedSwiftName + var fieldAttributeStr = if javaField.isStatic { + "@JavaStaticField" + } else { + "@JavaField" + } + var parameters: [String] = [] + if swiftFieldNameIsEscaped { + parameters.append("\"\(javaField.getName())\"") + } + parameters.append("isFinal: \(javaField.isFinal)") + if !parameters.isEmpty { + fieldAttributeStr += "(" + fieldAttributeStr.append(parameters.joined(separator: ", ")) + fieldAttributeStr += ")" + } + let fieldAttribute: AttributeSyntax = "\(raw: fieldAttributeStr)" let fieldAnnotations = javaField.getDeclaredAnnotations().compactMap(\.self) let invisibleFieldAnnotations = runtimeInvisibleAnnotations.annotationsFor(field: javaField.getName()) let availableAttributes = swiftAvailableAttributes( @@ -1109,7 +1117,7 @@ extension JavaClassTranslator { "" } return """ - \(raw: availableAttributes.render())\(fieldAttribute)(isFinal: \(raw: javaField.isFinal)) + \(raw: availableAttributes.render())\(fieldAttribute) public var \(raw: swiftFieldName): \(raw: typeName) @@ -1121,7 +1129,7 @@ extension JavaClassTranslator { """ } else { return """ - \(raw: availableAttributes.render())\(fieldAttribute)(isFinal: \(raw: javaField.isFinal)) + \(raw: availableAttributes.render())\(fieldAttribute) public var \(raw: swiftFieldName): \(raw: typeName) """ } diff --git a/Sources/SwiftJavaToolLib/StringExtras.swift b/Sources/SwiftJavaToolLib/StringExtras.swift index 37cf4ffae..b5fb75dde 100644 --- a/Sources/SwiftJavaToolLib/StringExtras.swift +++ b/Sources/SwiftJavaToolLib/StringExtras.swift @@ -27,17 +27,19 @@ extension String { } /// Escape a name with backticks if it's a Swift keyword. - var escapedSwiftName: String { - if isValidSwiftIdentifier(for: .variableName) { - return self + var escapedSwiftName: (swiftName: String, escaped: Bool) { + var escaped = false + var copy = self + if starts(with: "$") { + copy = "_\(self.dropFirst())" + escaped = true } - return "`\(self)`" - } + if copy.isValidSwiftIdentifier(for: .variableName) { + return (copy, escaped) + } - /// Returns whether this is a valid Swift function name - var isValidSwiftFunctionName: Bool { - !self.starts(with: "$") + return ("`\(copy)`", true) } /// Replace all occurrences of one character in the string with another. diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift index d2fefc3b8..43bbda741 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift @@ -281,4 +281,44 @@ final class BasicWrapJavaTests: XCTestCase { ] ) } + + func test_wrapJava_escapedSwiftName() async throws { + let classpathURL = try await compileJava( + """ + package com.example; + + class MyClass { + public long init; + public static boolean $foo; + public void func() {} + public static void $bar() {} + } + """ + ) + + try assertWrapJavaOutput( + javaClassNames: [ + "com.example.MyClass", + ], + classpath: [classpathURL], + expectedChunks: [ + """ + @JavaField("init", isFinal: false) + public var `init`: Int64 + """, + """ + @JavaStaticField("$foo", isFinal: false) + public var _foo: Bool + """, + """ + @JavaMethod("func") + open func `func`() + """, + """ + @JavaStaticMethod("$bar") + public func _bar() + """ + ] + ) + } } From a6dba3555989122be88a517ab3a95de83d19e9d1 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 1 May 2026 12:36:19 +0900 Subject: [PATCH 2/4] Add JavaSwiftInstance and re-run wrap-java --- .../JavaIO/generated/FileDescriptor.swift | 2 +- Sources/SwiftJava/generated/JavaString.swift | 2 +- .../generated/JavaJNISwiftInstance.swift | 27 ++++++++++++++++++- .../generated/JavaSwiftArena.swift | 9 ++++++- .../generated/JavaSwiftInstance.swift | 24 +++++++++++++++++ .../SwiftJavaRuntimeSupport/swift-java.config | 1 + 6 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftInstance.swift diff --git a/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift index 413f93a86..25cfd223b 100644 --- a/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift +++ b/Sources/JavaStdlib/JavaIO/generated/FileDescriptor.swift @@ -26,7 +26,7 @@ open class FileDescriptor: JavaObject { open func valid() -> Bool } extension JavaClass { - @JavaStaticField(isFinal: true) + @JavaStaticField("in", isFinal: true) public var `in`: FileDescriptor! @JavaStaticField(isFinal: true) diff --git a/Sources/SwiftJava/generated/JavaString.swift b/Sources/SwiftJava/generated/JavaString.swift index 44736eb97..0da5fa20f 100644 --- a/Sources/SwiftJava/generated/JavaString.swift +++ b/Sources/SwiftJava/generated/JavaString.swift @@ -483,7 +483,7 @@ open class JavaString: JavaObject { /// ```java /// public java.lang.String java.lang.String.repeat(int) /// ``` - @JavaMethod + @JavaMethod("repeat") open func `repeat`(_ arg0: Int32) -> String /// Java method `isBlank`. diff --git a/Sources/SwiftJavaRuntimeSupport/generated/JavaJNISwiftInstance.swift b/Sources/SwiftJavaRuntimeSupport/generated/JavaJNISwiftInstance.swift index 50a0502fd..746a23b23 100644 --- a/Sources/SwiftJavaRuntimeSupport/generated/JavaJNISwiftInstance.swift +++ b/Sources/SwiftJavaRuntimeSupport/generated/JavaJNISwiftInstance.swift @@ -2,7 +2,32 @@ import SwiftJava import SwiftJavaJNICore -@JavaInterface("org.swift.swiftkit.core.JNISwiftInstance") +@JavaInterface("org.swift.swiftkit.core.JNISwiftInstance", extends: JavaSwiftInstance.self) public struct JavaJNISwiftInstance { + /// Java method `$typeMetadataAddress`. + /// + /// ### Java method signature + /// ```java + /// public abstract long org.swift.swiftkit.core.JNISwiftInstance.$typeMetadataAddress() + /// ``` + @JavaMethod("$typeMetadataAddress") + public func _typeMetadataAddress() -> Int64 + /// Java method `$memoryAddress`. + /// + /// ### Java method signature + /// ```java + /// public abstract long org.swift.swiftkit.core.SwiftInstance.$memoryAddress() + /// ``` + @JavaMethod("$memoryAddress") + public func _memoryAddress() -> Int64 + + /// Java method `$ensureAlive`. + /// + /// ### Java method signature + /// ```java + /// public default void org.swift.swiftkit.core.SwiftInstance.$ensureAlive() + /// ``` + @JavaMethod("$ensureAlive") + public func _ensureAlive() } diff --git a/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftArena.swift b/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftArena.swift index bb5a13944..a50cbbc9a 100644 --- a/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftArena.swift +++ b/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftArena.swift @@ -4,7 +4,14 @@ import SwiftJavaJNICore @JavaInterface("org.swift.swiftkit.core.SwiftArena") public struct JavaSwiftArena { - + /// Java method `register`. + /// + /// ### Java method signature + /// ```java + /// public abstract void org.swift.swiftkit.core.SwiftArena.register(org.swift.swiftkit.core.SwiftInstance) + /// ``` + @JavaMethod + public func register(_ arg0: JavaSwiftInstance?) } extension JavaClass { /// Java method `ofAuto`. diff --git a/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftInstance.swift b/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftInstance.swift new file mode 100644 index 000000000..60dac7f23 --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftInstance.swift @@ -0,0 +1,24 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import SwiftJava +import SwiftJavaJNICore + +@JavaInterface("org.swift.swiftkit.core.SwiftInstance") +public struct JavaSwiftInstance { + /// Java method `$memoryAddress`. + /// + /// ### Java method signature + /// ```java + /// public abstract long org.swift.swiftkit.core.SwiftInstance.$memoryAddress() + /// ``` +@JavaMethod("$memoryAddress") + public func _memoryAddress() -> Int64 + + /// Java method `$ensureAlive`. + /// + /// ### Java method signature + /// ```java + /// public default void org.swift.swiftkit.core.SwiftInstance.$ensureAlive() + /// ``` +@JavaMethod("$ensureAlive") + public func _ensureAlive() +} diff --git a/Sources/SwiftJavaRuntimeSupport/swift-java.config b/Sources/SwiftJavaRuntimeSupport/swift-java.config index 5b924ef05..4fa790e26 100644 --- a/Sources/SwiftJavaRuntimeSupport/swift-java.config +++ b/Sources/SwiftJavaRuntimeSupport/swift-java.config @@ -1,6 +1,7 @@ { "classpath" : "SwiftKitCore/build/classes/java/main", "classes" : { + "org.swift.swiftkit.core.SwiftInstance" : "JavaSwiftInstance", "org.swift.swiftkit.core.JNISwiftInstance" : "JavaJNISwiftInstance", "org.swift.swiftkit.core.SwiftArena" : "JavaSwiftArena" } From 2f4121f5442e423648ae7366b02ed26b18b335d7 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 1 May 2026 12:42:59 +0900 Subject: [PATCH 3/4] Add test to JavaKit example --- .../Sources/JavaKitExample/JavaKitExample.swift | 4 ++++ .../Sources/JavaKitExample/com/example/swift/HelloSwift.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift b/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift index 3e2ff1814..2c7ee67aa 100644 --- a/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift +++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/JavaKitExample.swift @@ -62,6 +62,10 @@ extension HelloSwift: HelloSwiftNativeMethods { fatalError("Expected subclass here") } + // Check escaped name + assert(self.`init`(42) == 42) + assert(self._echo("Hello") == "Hello") + // Check "is" behavior assert(newHello.is(HelloSwift.self)) assert(!newHello.is(HelloSubclass.self)) diff --git a/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java index 5ac8a2f69..94f41d9a3 100644 --- a/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java +++ b/Samples/JavaKitSampleApp/Sources/JavaKitExample/com/example/swift/HelloSwift.java @@ -47,6 +47,10 @@ public long init(long value) { return value; } + public String $echo(String value) { + return value; + } + public Predicate lessThanTen() { Predicate predicate = i -> (i < 10); return predicate; From 6be8b13918e164fa886d8ca35f3a621cb5001b35 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 1 May 2026 12:43:32 +0900 Subject: [PATCH 4/4] swift format --- .../generated/JavaSwiftInstance.swift | 4 ++-- Sources/SwiftJavaToolLib/JavaClassTranslator.swift | 11 ++++++----- .../WrapJavaTests/BasicWrapJavaTests.swift | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftInstance.swift b/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftInstance.swift index 60dac7f23..90a64c9a8 100644 --- a/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftInstance.swift +++ b/Sources/SwiftJavaRuntimeSupport/generated/JavaSwiftInstance.swift @@ -10,7 +10,7 @@ public struct JavaSwiftInstance { /// ```java /// public abstract long org.swift.swiftkit.core.SwiftInstance.$memoryAddress() /// ``` -@JavaMethod("$memoryAddress") + @JavaMethod("$memoryAddress") public func _memoryAddress() -> Int64 /// Java method `$ensureAlive`. @@ -19,6 +19,6 @@ public struct JavaSwiftInstance { /// ```java /// public default void org.swift.swiftkit.core.SwiftInstance.$ensureAlive() /// ``` -@JavaMethod("$ensureAlive") + @JavaMethod("$ensureAlive") public func _ensureAlive() } diff --git a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift index 566a8e178..e2f6ab81d 100644 --- a/Sources/SwiftJavaToolLib/JavaClassTranslator.swift +++ b/Sources/SwiftJavaToolLib/JavaClassTranslator.swift @@ -1079,11 +1079,12 @@ extension JavaClassTranslator { outerOptional: .implicitlyUnwrappedOptional ) let (swiftFieldName, swiftFieldNameIsEscaped) = javaField.getName().escapedSwiftName - var fieldAttributeStr = if javaField.isStatic { - "@JavaStaticField" - } else { - "@JavaField" - } + var fieldAttributeStr = + if javaField.isStatic { + "@JavaStaticField" + } else { + "@JavaField" + } var parameters: [String] = [] if swiftFieldNameIsEscaped { parameters.append("\"\(javaField.getName())\"") diff --git a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift index 43bbda741..90b803294 100644 --- a/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift +++ b/Tests/SwiftJavaToolLibTests/WrapJavaTests/BasicWrapJavaTests.swift @@ -286,7 +286,7 @@ final class BasicWrapJavaTests: XCTestCase { let classpathURL = try await compileJava( """ package com.example; - + class MyClass { public long init; public static boolean $foo; @@ -298,7 +298,7 @@ final class BasicWrapJavaTests: XCTestCase { try assertWrapJavaOutput( javaClassNames: [ - "com.example.MyClass", + "com.example.MyClass" ], classpath: [classpathURL], expectedChunks: [ @@ -317,7 +317,7 @@ final class BasicWrapJavaTests: XCTestCase { """ @JavaStaticMethod("$bar") public func _bar() - """ + """, ] ) }