Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,14 @@ extension JNISwift2JavaGenerator {
return
}

printer.printBraceBlock("public sealed interface Case") { printer in
let caseGenericClause =
if decl.genericParameterNames.isEmpty {
""
} else {
"<\(decl.genericParameterNames.joined(separator: ", "))>"
}

printer.printBraceBlock("public sealed interface Case\(caseGenericClause)") { printer in
for enumCase in decl.cases {
guard let translatedCase = self.translatedEnumCase(for: enumCase) else {
continue
Expand All @@ -505,7 +512,7 @@ extension JNISwift2JavaGenerator {
}

// Print record
printer.print("record \(translatedCase.name)(\(members.joined(separator: ", "))) implements Case {}")
printer.print("record \(translatedCase.name)\(caseGenericClause)(\(members.joined(separator: ", "))) implements Case\(caseGenericClause) {}")
}
}
printer.println()
Expand All @@ -514,13 +521,13 @@ extension JNISwift2JavaGenerator {
self.translatedEnumCase(for: $0)
}.contains(where: \.requiresSwiftArena)

printer.printBraceBlock("public Case getCase(\(requiresSwiftArena ? "SwiftArena swiftArena" : ""))") { printer in
printer.printBraceBlock("public Case\(caseGenericClause) getCase(\(requiresSwiftArena ? "SwiftArena swiftArena" : ""))") { printer in
printer.printBraceBlock("return switch (this.getDiscriminator())", .semicolonNewLine) { printer in
for enumCase in decl.cases {
if let translatedCase = self.translatedEnumCase(for: enumCase) {
if enumCase.parameters.isEmpty {
printer.print(
"case \(enumCase.name.uppercased()) -> new Case.\(translatedCase.name)();"
"case \(enumCase.name.uppercased()) -> new Case.\(translatedCase.name)\(caseGenericClause)();"
)
} else {
let arenaArgument = translatedCase.requiresSwiftArena ? "swiftArena" : ""
Expand Down Expand Up @@ -552,12 +559,16 @@ extension JNISwift2JavaGenerator {
}

private func printEnumCases(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
let caseTypeParameters: [JavaType] = decl.genericParameterNames.map {
.class(package: nil, name: $0)
}

for enumCase in decl.cases {
guard let translatedCase = self.translatedEnumCase(for: enumCase) else {
continue
}

let caseType = JavaType.class(package: nil, name: "Case.\(translatedCase.name)")
let caseType = JavaType.class(package: nil, name: "Case.\(translatedCase.name)", typeParameters: caseTypeParameters)
let resultType = JavaType.optional(caseType)
if let getAsCaseFunctionDecl = translatedCase.getAsCaseFunction,
var getAsCaseFunction = self.translatedDecl(for: getAsCaseFunctionDecl)
Expand Down Expand Up @@ -591,7 +602,7 @@ extension JNISwift2JavaGenerator {
if (getDiscriminator() != Discriminator.\(enumCase.name.uppercased())) {
return java.util.Optional.empty();
}
return java.util.Optional.of(new Case.\(translatedCase.name)());
return java.util.Optional.of(new Case.\(translatedCase.name)\(caseTypeParameters.isEmpty ? "" : "<>")());
}
"""
)
Expand Down
57 changes: 51 additions & 6 deletions Sources/SwiftJavaToolLib/JavaTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ extension JavaTranslator {
substitution: SubstitutionMap?,
preferValueTypes: Bool,
outerOptional: OptionalKind,
eraseTypeArguments: Bool = false
eraseTypeArguments: Bool = false,
eraseRawOwnerTypeArguments: Bool = false
) throws -> String {
// Replace if it is a type variable and we have a substitution for it.
let javaType = substitution?.resolve(javaType) ?? javaType
Expand All @@ -181,7 +182,9 @@ extension JavaTranslator {
bound,
substitution: substitution,
preferValueTypes: preferValueTypes,
outerOptional: outerOptional
outerOptional: outerOptional,
eraseTypeArguments: eraseTypeArguments,
eraseRawOwnerTypeArguments: eraseRawOwnerTypeArguments
)
}

Expand All @@ -192,7 +195,9 @@ extension JavaTranslator {
arrayType.getGenericComponentType()!,
substitution: substitution,
preferValueTypes: preferValueTypes,
outerOptional: .optional
outerOptional: .optional,
eraseTypeArguments: eraseTypeArguments,
eraseRawOwnerTypeArguments: eraseRawOwnerTypeArguments
)
return "[\(elementType)]"
}
Expand All @@ -213,7 +218,9 @@ extension JavaTranslator {
rawJavaType,
substitution: substitution,
preferValueTypes: false,
outerOptional: outerOptional
outerOptional: outerOptional,
eraseTypeArguments: false,
eraseRawOwnerTypeArguments: false
)

let optionalSuffix: String
Expand All @@ -224,6 +231,20 @@ extension JavaTranslator {
optionalSuffix = ""
}

if let ownerType = parameterizedType.getOwnerType() {
let ownerSwiftType =
try getSwiftTypeNameAsString(
method: method,
ownerType,
substitution: substitution,
preferValueTypes: false,
outerOptional: .nonoptional,
eraseTypeArguments: eraseTypeArguments,
eraseRawOwnerTypeArguments: ownerType.is(JavaClass<JavaObject>.self)
)
rawSwiftType = "\(ownerSwiftType).\(rawSwiftType.splitSwiftTypeName().name)"
}

let typeArguments: [String] = try parameterizedType.getActualTypeArguments().compactMap { typeArg in
guard let typeArg else { return nil }
if eraseTypeArguments {
Expand All @@ -235,7 +256,9 @@ extension JavaTranslator {
typeArg,
substitution: substitution,
preferValueTypes: false,
outerOptional: .nonoptional
outerOptional: .nonoptional,
eraseTypeArguments: eraseTypeArguments,
eraseRawOwnerTypeArguments: eraseRawOwnerTypeArguments
)

// FIXME: improve the get instead...
Expand All @@ -252,6 +275,10 @@ extension JavaTranslator {
return mappedSwiftName
}

if typeArguments.isEmpty {
return "\(rawSwiftType)\(optionalSuffix)"
}

return "\(rawSwiftType)<\(typeArguments.joined(separator: ", "))>\(optionalSuffix)"
}
}
Expand All @@ -261,7 +288,25 @@ extension JavaTranslator {
throw TranslationError.unhandledJavaType(javaType)
}

let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes)
var (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes)
if eraseRawOwnerTypeArguments, let declaringClass = javaClass.getDeclaringClass() {
let ownerSwiftType = try getSwiftTypeNameAsString(
declaringClass.as(Type.self),
substitution: substitution,
preferValueTypes: preferValueTypes,
outerOptional: .nonoptional,
eraseTypeArguments: eraseTypeArguments,
eraseRawOwnerTypeArguments: true
)
swiftName = "\(ownerSwiftType).\(swiftName.splitSwiftTypeName().name)"
}

if eraseTypeArguments || eraseRawOwnerTypeArguments {
let typeParameterCount = javaClass.getTypeParameters().count
if typeParameterCount > 0 {
swiftName += "<\((0..<typeParameterCount).map { _ in "JavaObject" }.joined(separator: ", "))>"
}
}
let resultString =
if isOptional {
outerOptional.adjustTypeName(swiftName)
Expand Down
8 changes: 4 additions & 4 deletions Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,15 +319,15 @@ struct JNIGenericTypeTests {
}
""",
"""
public sealed interface Case {
record None() implements Case {}
public sealed interface Case<Wrapped> {
record None<Wrapped>() implements Case<Wrapped> {}
}
""",
"""
public Case getCase() {
public Case<Wrapped> getCase() {
return switch (this.getDiscriminator()) {
case SOME -> throw new UnsupportedOperationException("MyOptional.some contains unsupported values.");
case NONE -> new Case.None();
case NONE -> new Case.None<Wrapped>();
};
}
""",
Expand Down
67 changes: 67 additions & 0 deletions Tests/SwiftJavaToolLibTests/JavaTranslatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,71 @@ class JavaTranslatorTests: XCTestCase {

}
}

func testTranslateNestedParameterizedTypes() async throws {
let classpathURL = try await compileJava(
"""
package com.example;

class MyString {}
class MyInt {}

class Outer<T> {
class Inner<U> {
}

Outer<MyString>.Inner<MyInt> foo() { return null; }
Outer<MyInt>.Inner<T> bar() { return null; }
}
"""
)

try assertWrapJavaOutput(
javaClassNames: [
"com.example.MyString",
"com.example.MyInt",
"com.example.Outer$Inner",
"com.example.Outer",
],
classpath: [classpathURL],
expectedChunks: [
"func foo() -> Outer<MyString>.Inner<MyInt>!",
"func bar() -> Outer<MyInt>.Inner<T>!",
]
)
}

func testTranslateNestedParameterizedStaticTypes() async throws {
let classpathURL = try await compileJava(
"""
package com.example;

class MyOptional<Wrapped> {
interface Case<Wrapped> {
record None<Wrapped>() implements Case<Wrapped> {}
}

Case<Wrapped> getCase() { return null; }
Case.None<Wrapped> getAsNone() { return null; }
}
"""
)

try assertWrapJavaOutput(
javaClassNames: [
"com.example.MyOptional$Case",
"com.example.MyOptional$Case$None",
"com.example.MyOptional",
],
classNameMappings: [
"com.example.MyOptional$Case": "MyOptional.Case",
"com.example.MyOptional$Case$None": "MyOptional.Case.None",
],
classpath: [classpathURL],
expectedChunks: [
"func getCase() -> MyOptional<JavaObject>.Case<Wrapped>!",
"func getAsNone() -> MyOptional<JavaObject>.Case<JavaObject>.None<Wrapped>!",
]
)
}
}
Loading