From f64175170a89370e670f0ef502d0a6abb45e1cef Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 10 Apr 2026 17:50:28 +0900 Subject: [PATCH 1/6] Remove backtick of enum case name --- .../{EnumWithValueCases.swift => Enums.swift} | 4 ++++ .../JExtractSwiftLib/Swift2JavaVisitor.swift | 5 +++-- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) rename Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/{EnumWithValueCases.swift => Enums.swift} (92%) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumWithValueCases.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift similarity index 92% rename from Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumWithValueCases.swift rename to Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift index f3a7f45e..ef4efbf4 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumWithValueCases.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift @@ -16,3 +16,7 @@ public enum EnumWithValueCases { case firstCase(UInt) case secondCase } + +public enum EnumWithBacktick { + case `let` +} diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index c6390bc1..720e428f 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -227,16 +227,17 @@ final class Swift2JavaVisitor { lookupContext: translator.lookupContext, ) + let caseName = caseElement.name.text.trimmingCharacters(in: .init(charactersIn: "`")) let caseFunction = ImportedFunc( module: translator.swiftModuleName, swiftDecl: node, - name: caseElement.name.text, + name: caseName, apiKind: .enumCase, functionSignature: signature, ) let importedCase = ImportedEnumCase( - name: caseElement.name.text, + name: caseName, parameters: parameters ?? [], swiftDecl: node, enumType: SwiftNominalType(nominalTypeDecl: typeContext.swiftNominal), diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 153d1fa5..9780080b 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -374,4 +374,22 @@ struct JNIEnumTests { ] ) } + + @Test + func removeBacktick() throws { + try assertOutput( + input: """ + public enum MyEnum { + case `let` + } + """, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + "public static MyEnum let(SwiftArena swiftArena) {", + "public record Let() implements Case {", + ], + ) + } } From 1228a3aaf2683da481751a66f8c2a20d3e420898 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 10 Apr 2026 18:09:37 +0900 Subject: [PATCH 2/6] Avoid using java keyword for identifier --- .../Sources/MySwiftLibrary/Enums.swift | 1 + ...mWithValueCasesTest.java => EnumTest.java} | 17 ++++++++----- .../JavaIdentifierFactory.swift | 24 ++++++++++++++++++- .../JExtractSwiftTests/JavaKeywordTests.swift | 17 +++++++++++++ 4 files changed, 52 insertions(+), 7 deletions(-) rename Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/{EnumWithValueCasesTest.java => EnumTest.java} (76%) create mode 100644 Tests/JExtractSwiftTests/JavaKeywordTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift index ef4efbf4..d4498590 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift @@ -19,4 +19,5 @@ public enum EnumWithValueCases { public enum EnumWithBacktick { case `let` + case `default` } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumWithValueCasesTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java similarity index 76% rename from Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumWithValueCasesTest.java rename to Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java index 840e7b64..4cd2d288 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumWithValueCasesTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java @@ -15,20 +15,25 @@ package com.example.swift; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.ConfinedSwiftMemorySession; import org.swift.swiftkit.core.SwiftArena; -import java.util.Optional; - import static org.junit.jupiter.api.Assertions.*; -public class EnumWithValueCasesTest { +public class EnumTest { @Test - void fn() { + void enumWithValueCases() { try (var arena = SwiftArena.ofConfined()) { EnumWithValueCases e = EnumWithValueCases.firstCase(48, arena); EnumWithValueCases.FirstCase c = (EnumWithValueCases.FirstCase) e.getCase(); assertNotNull(c); } } -} \ No newline at end of file + + @Test + void enumWithBacktick() { + try (var arena = SwiftArena.ofConfined()) { + EnumWithBacktick e = EnumWithBacktick.default_(arena); + assertTrue(e.getAsDefault().isPresent()); + } + } +} diff --git a/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift b/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift index 68fdb9c8..266e6bbe 100644 --- a/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift +++ b/Sources/JExtractSwiftLib/JavaIdentifierFactory.swift @@ -68,7 +68,11 @@ package struct JavaIdentifierFactory { case .setter, .subscriptSetter: decl.javaSetterName case .function, .initializer, .enumCase: decl.name } - return baseName + paramsSuffix(decl, baseName: baseName) + var methodName = baseName + paramsSuffix(decl, baseName: baseName) + if Self.javaKeywords.contains(methodName) { + methodName += "_" + } + return methodName } private func paramsSuffix(_ decl: ImportedFunc, baseName: String) -> String { @@ -86,4 +90,22 @@ package struct JavaIdentifierFactory { return labels.map { $0.prefix(1).uppercased() + $0.dropFirst() }.joined() } } + + static let javaKeywords: Set = [ + /// https://docs.oracle.com/javase/specs/jls/se25/html/jls-3.html#jls-3.9 + "abstract", "continue", "for", "new", "switch", + "assert", "default", "if", "package", "synchronized", + "boolean", "do", "goto", "private", "this", + "break", "double", "implements", "protected", "throw", + "byte", "else", "import", "public", "throws", + "case", "enum", "instanceof", "return", "transient", + "catch", "extends", "int", "short", "try", + "char", "final", "interface", "static", "void", + "class", "finally", "long", "strictfp", "volatile", + "const", "float", "native", "super", "while", + "_", + + /// literals + "true", "false", "null", + ] } diff --git a/Tests/JExtractSwiftTests/JavaKeywordTests.swift b/Tests/JExtractSwiftTests/JavaKeywordTests.swift new file mode 100644 index 00000000..7b19c6a1 --- /dev/null +++ b/Tests/JExtractSwiftTests/JavaKeywordTests.swift @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Testing + +// TODO: From 5e8b14ef7ae68e554aef6992576392d41fe212d2 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 13 Apr 2026 09:47:51 +0900 Subject: [PATCH 3/6] implement JavaKeywordTests --- .../JExtractSwiftTests/JavaKeywordTests.swift | 126 +++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/Tests/JExtractSwiftTests/JavaKeywordTests.swift b/Tests/JExtractSwiftTests/JavaKeywordTests.swift index 7b19c6a1..3a7eabd9 100644 --- a/Tests/JExtractSwiftTests/JavaKeywordTests.swift +++ b/Tests/JExtractSwiftTests/JavaKeywordTests.swift @@ -14,4 +14,128 @@ import Testing -// TODO: +@Suite +struct JavaKeywordTests { + @Test + func functionName() throws { + let text = + """ + public struct Foo { + public func final() + } + """ + + try assertOutput( + input: text, + .ffm, + .java, + expectedChunks: [ + """ + private static final MemorySegment ADDR = + SwiftModule.findOrThrow("swiftjava_SwiftModule_Foo_final"); + """, + """ + public void final_() { + """ + ] + ) + + try assertOutput( + input: text, + .ffm, + .swift, + expectedChunks: [ + """ + @_cdecl("swiftjava_SwiftModule_Foo_final") + """, + ] + ) + } + + @Test + func enumCase() throws { + let text = + """ + public enum MyEnum { + case null + } + """ + + try assertOutput( + input: text, + .jni, + .java, + expectedChunks: [ + """ + public static MyEnum null_(SwiftArena swiftArena) { + """, + """ + public record Null() implements Case { + """, + ] + ) + } + + @Test + func enumCaseWithAssociatedValue() throws { + let text = + """ + public enum MyEnumWithValue { + case instanceof(String) + case none + } + """ + + try assertOutput( + input: text, + .jni, + .java, + expectedChunks: [ + """ + public static MyEnumWithValue instanceof_(java.lang.String arg0, SwiftArena swiftArena) { + """, + """ + public record Instanceof(java.lang.String arg0) implements Case { + """, + """ + private static native Instanceof._NativeParameters $getAsInstanceof(long selfPointer); + """, + ] + ) + + try assertOutput( + input: text, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyEnumWithValue__00024getAsInstanceof__J") + """, + ] + ) + } + + @Test + func enumCaseWithBacktick() throws { + let text = + """ + public enum MyEnum { + case `default` + } + """ + + try assertOutput( + input: text, + .jni, + .java, + expectedChunks: [ + """ + public static MyEnum default_(SwiftArena swiftArena) { + """, + """ + public record Default() implements Case { + """, + ] + ) + } +} From 53d7b615d345d6eae394966b03ddd857b379af11 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 13 Apr 2026 10:20:35 +0900 Subject: [PATCH 4/6] Add function name support --- .../Convenience/String+Extensions.swift | 8 +++ .../JExtractSwiftLib/Swift2JavaVisitor.swift | 4 +- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 18 ------- .../SwiftEscapedNameTests.swift | 54 +++++++++++++++++++ 4 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index f7439e79..9fdb6a64 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -87,6 +87,14 @@ extension String { return .class(package: javaPackageName, name: javaClassName) } + + /// Unescapes the name if it is surrounded by backticks. + var unescapedSwiftName: String { + if count >= 2 && hasPrefix("`") && hasSuffix("`") { + return String(dropFirst().dropLast()) + } + return self + } } extension Array where Element == String { diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 720e428f..38f92d11 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -186,7 +186,7 @@ final class Swift2JavaVisitor { let imported = ImportedFunc( module: translator.swiftModuleName, swiftDecl: node, - name: node.name.text, + name: node.name.text.unescapedSwiftName, apiKind: .function, functionSignature: signature, ) @@ -227,7 +227,7 @@ final class Swift2JavaVisitor { lookupContext: translator.lookupContext, ) - let caseName = caseElement.name.text.trimmingCharacters(in: .init(charactersIn: "`")) + let caseName = caseElement.name.text.unescapedSwiftName let caseFunction = ImportedFunc( module: translator.swiftModuleName, swiftDecl: node, diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 9780080b..153d1fa5 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -374,22 +374,4 @@ struct JNIEnumTests { ] ) } - - @Test - func removeBacktick() throws { - try assertOutput( - input: """ - public enum MyEnum { - case `let` - } - """, - .jni, - .java, - detectChunkByInitialLines: 1, - expectedChunks: [ - "public static MyEnum let(SwiftArena swiftArena) {", - "public record Let() implements Case {", - ], - ) - } } diff --git a/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift b/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift new file mode 100644 index 00000000..5f2c636f --- /dev/null +++ b/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Testing + +@Suite +struct SwiftEscapedNameTests { + @Test + func function() throws { + try assertOutput( + input: """ + public struct MyStruct { + public func `guard`() + } + """, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + "public void guard() {", + "private static native void $guard(long selfPointer);" + ], + ) + } + + @Test + func enumCase() throws { + try assertOutput( + input: """ + public enum MyEnum { + case `let` + } + """, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + "public static MyEnum let(SwiftArena swiftArena) {", + "public record Let() implements Case {", + ], + ) + } +} From 10ad8b21977e1cb886ca9445cf6e068b8b677954 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 13 Apr 2026 10:20:50 +0900 Subject: [PATCH 5/6] swift format --- Tests/JExtractSwiftTests/JavaKeywordTests.swift | 6 +++--- Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/JExtractSwiftTests/JavaKeywordTests.swift b/Tests/JExtractSwiftTests/JavaKeywordTests.swift index 3a7eabd9..d305c022 100644 --- a/Tests/JExtractSwiftTests/JavaKeywordTests.swift +++ b/Tests/JExtractSwiftTests/JavaKeywordTests.swift @@ -36,7 +36,7 @@ struct JavaKeywordTests { """, """ public void final_() { - """ + """, ] ) @@ -47,7 +47,7 @@ struct JavaKeywordTests { expectedChunks: [ """ @_cdecl("swiftjava_SwiftModule_Foo_final") - """, + """ ] ) } @@ -110,7 +110,7 @@ struct JavaKeywordTests { expectedChunks: [ """ @_cdecl("Java_com_example_swift_MyEnumWithValue__00024getAsInstanceof__J") - """, + """ ] ) } diff --git a/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift b/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift index 5f2c636f..c388584b 100644 --- a/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift +++ b/Tests/JExtractSwiftTests/SwiftEscapedNameTests.swift @@ -29,7 +29,7 @@ struct SwiftEscapedNameTests { detectChunkByInitialLines: 1, expectedChunks: [ "public void guard() {", - "private static native void $guard(long selfPointer);" + "private static native void $guard(long selfPointer);", ], ) } From 78f307cec09c204bed02e2ac64e30a922783ec14 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 13 Apr 2026 10:30:24 +0900 Subject: [PATCH 6/6] Fix copyright year --- Tests/JExtractSwiftTests/JavaKeywordTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/JExtractSwiftTests/JavaKeywordTests.swift b/Tests/JExtractSwiftTests/JavaKeywordTests.swift index d305c022..eea72348 100644 --- a/Tests/JExtractSwiftTests/JavaKeywordTests.swift +++ b/Tests/JExtractSwiftTests/JavaKeywordTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information