diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumCaseCollision.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumCaseCollision.swift new file mode 100644 index 000000000..6e1f5a6d7 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumCaseCollision.swift @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +public enum EnumCaseCollision { + case enumCaseCollision(Int64) + case `case`(Int64) + case plain(Int64) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumCaseCollisionTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumCaseCollisionTest.java new file mode 100644 index 000000000..850ae1762 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumCaseCollisionTest.java @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; +import static org.junit.jupiter.api.Assertions.*; + +public class EnumCaseCollisionTest { + @Test + void renamedRecordsPreserveTheirPayloads() { + try (var arena = SwiftArena.ofConfined()) { + var value = EnumCaseCollision.enumCaseCollision(42, arena); + var payload = assertInstanceOf(EnumCaseCollision.Case.EnumCaseCollisionCase.class, value.getCase()); + assertEquals(42, payload.arg0()); + var keyword = EnumCaseCollision.case_(7, arena); + assertEquals(7, assertInstanceOf(EnumCaseCollision.Case.CaseCase.class, keyword.getCase()).arg0()); + var plain = EnumCaseCollision.plain(9, arena); + assertEquals(9, assertInstanceOf(EnumCaseCollision.Case.Plain.class, plain.getCase()).arg0()); + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 9dc93d507..96607200a 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -151,7 +151,13 @@ extension JNISwift2JavaGenerator { let associatedValueTypes = enumCase.parameters.map { param in param.type.description }.joined(separator: .comma) - let javaCaseClassName = enumCase.name.firstCharacterUppercased + var javaCaseClassName = enumCase.name.firstCharacterUppercased + // Java forbids a nested record from sharing an enclosing type's name. + // For example Calendar.Component.Case.Calendar must be CalendarCase. + let enclosingNames = enumCase.enumType.nominalTypeDecl.qualifiedTypeName.components + ["Case"] + while enclosingNames.contains(javaCaseClassName) { + javaCaseClassName += "Case" + } let resultType = knownTypes.optionalSugar( .tuple( enumCase.parameters.map { diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 9bb75bb74..004383395 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -30,6 +30,29 @@ struct JNIEnumTests { } """ + @Test( + "Enum case records avoid enclosing Java type names", + arguments: [("message", "MessageCase"), ("caseCase", "CaseCaseCase"), ("`case`", "CaseCaseCase"), ("plain", "Plain")] + ) + func enclosingNameCollisions_java(caseName: String, recordName: String) throws { + try assertOutput( + input: """ + public struct CaseCase { + public enum Message { + case \(caseName)(Int64) + } + } + """, + .jni, + .java, + expectedChunks: [ + "record \(recordName)(long arg0) implements Case {}", + "public java.util.Optional getAs\(recordName)()", + ], + notExpectedChunks: ["record Message(", "record Case(", "record CaseCase("] + ) + } + @Test func generatesJavaClass() throws { try assertOutput(