Skip to content

Commit 478c61b

Browse files
committed
further correct generic handling and type variables
1 parent e8f4997 commit 478c61b

File tree

9 files changed

+323
-137
lines changed

9 files changed

+323
-137
lines changed

Samples/SwiftJavaExtractFFMSampleApp/src/test/java/org/swift/swiftkitffm/ThrowsFFMTest.java

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import CSwiftJavaJNI
16+
import SwiftJava
17+
18+
// FIXME: all interfaces should ahve these https://github.com/swiftlang/swift-java/issues/430
19+
extension TypeVariable {
20+
21+
@JavaMethod
22+
public func toString() -> String
23+
24+
@JavaMethod
25+
public func getClass() -> JavaClass<JavaObject>!
26+
27+
@JavaMethod
28+
public func equals(_ arg0: JavaObject?) -> Bool
29+
30+
@JavaMethod
31+
public func hashCode() -> Int32
32+
33+
}
34+
35+
// FIXME: All Java objects are Hashable, we should handle that accordingly.
36+
extension TypeVariable: Hashable {
37+
38+
public func hash(into hasher: inout Hasher) {
39+
guard let pojo = self.as(JavaObject.self) else {
40+
return
41+
}
42+
43+
hasher.combine(pojo.hashCode())
44+
}
45+
46+
public static func == (lhs: TypeVariable<D>, rhs: TypeVariable<D>) -> Bool {
47+
guard let lhpojo: JavaObject = lhs.as(JavaObject.self) else {
48+
return false
49+
}
50+
guard let rhpojo: JavaObject = rhs.as(JavaObject.self) else {
51+
return false
52+
}
53+
54+
return lhpojo.equals(rhpojo)
55+
}
56+
57+
}

Sources/JavaStdlib/JavaLangReflect/generated/TypeVariable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import CSwiftJavaJNI
66
public struct TypeVariable<D: AnyJavaObject> {
77
@JavaMethod
88
public func getGenericDeclaration() -> GenericDeclaration!
9-
9+
1010
@JavaMethod
1111
public func getAnnotatedBounds() -> [AnnotatedType?]
1212

Sources/SwiftJavaToolLib/JavaClassTranslator.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,6 @@ extension JavaClassTranslator {
879879
/// method.
880880
func isOverride(_ method: Method) -> Bool {
881881
var currentSuperclass = effectiveJavaSuperclass
882-
print("currentSuperclass = \(currentSuperclass)")
883882
while let currentSuperclassNonOpt = currentSuperclass {
884883
// Set the loop up for the next run.
885884
defer {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import JavaLangReflect
16+
import JavaTypes
17+
import SwiftBasicFormat
18+
import SwiftJava
19+
import SwiftJavaConfigurationShared
20+
import SwiftSyntax
21+
import SwiftSyntaxBuilder
22+
23+
struct GenericJavaTypeOriginInfo {
24+
enum GenericSource {
25+
/// The source of the generic
26+
case `class`([Type])
27+
case method
28+
}
29+
30+
var source: GenericSource
31+
var type: Type
32+
}
33+
34+
private func collectTypeVariables(_ type: Type) -> Set<TypeVariable<JavaObject>> {
35+
var result: Set<TypeVariable<JavaObject>> = []
36+
37+
return result
38+
}
39+
40+
/// if the type (that is used by the Method) is generic, return if the use originates from the method, or a surrounding class.
41+
func getGenericJavaTypeOriginInfo(_ type: Type?, from method: Method) -> [GenericJavaTypeOriginInfo] {
42+
guard let type else {
43+
return []
44+
}
45+
46+
guard isGenericJavaType(type) else {
47+
return [] // it's not a generic type, no "origin" of the use to detect
48+
}
49+
50+
var methodTypeVars = method.getTypeParameters()
51+
52+
// TODO: also handle nested classes here...
53+
var classTypeVars = method.getDeclaringClass().getTypeParameters()
54+
55+
var usedTypeVars: [TypeVariable<JavaObject>] = []
56+
57+
return []
58+
}
59+
60+
func isGenericJavaType(_ type: Type?) -> Bool {
61+
guard let type else {
62+
return false
63+
}
64+
65+
// Check if it's a type variable (e.g., T, E, etc.)
66+
if type.as(TypeVariable<JavaObject>.self) != nil {
67+
return true
68+
}
69+
70+
// Check if it's a parameterized type (e.g., List<T>, Map<K,V>)
71+
if let paramType = type.as(ParameterizedType.self) {
72+
let typeArgs: [Type?] = paramType.getActualTypeArguments()
73+
74+
// Check if any of the type arguments are generic
75+
for typeArg in typeArgs {
76+
guard let typeArg else { continue }
77+
if isGenericJavaType(typeArg) {
78+
return true
79+
}
80+
}
81+
}
82+
83+
// Check if it's a generic array type (e.g., T[], List<T>[])
84+
if let arrayType = type.as(GenericArrayType.self) {
85+
let componentType = arrayType.getGenericComponentType()
86+
return isGenericJavaType(componentType)
87+
}
88+
89+
// Check if it's a wildcard type (e.g., ? extends Number, ? super String)
90+
if type.as(WildcardType.self) != nil {
91+
return true
92+
}
93+
94+
return false
95+
}

Sources/SwiftJavaToolLib/JavaTranslator.swift

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -120,20 +120,22 @@ extension JavaTranslator {
120120

121121
// MARK: Type translation
122122
extension JavaTranslator {
123-
func getSwiftReturnTypeNameAsString(
123+
124+
125+
func getSwiftReturnTypeNameAsString(
124126
method: JavaLangReflect.Method,
125127
preferValueTypes: Bool,
126128
outerOptional: OptionalKind
127129
) throws -> String {
128-
let returnType = method.getReturnType()
130+
// let returnType = method.getReturnType()
129131
let genericReturnType = method.getGenericReturnType()
130132

131133
// Special handle the case when the return type is the generic type of the method: `<T> T foo()`
132-
if returnType?.getCanonicalName() == "java.lang.Object" {
133-
if let genericReturnType {
134-
return genericReturnType.getTypeName()
135-
}
136-
}
134+
135+
// if isGenericJavaType(genericReturnType) {
136+
// print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())")
137+
// getGenericJavaTypeOriginInfo(genericReturnType, from: method)
138+
// }
137139

138140
return try getSwiftTypeNameAsString(method: method, genericReturnType!, preferValueTypes: preferValueTypes, outerOptional: outerOptional)
139141
}
@@ -154,23 +156,46 @@ func getSwiftReturnTypeNameAsString(
154156
// }
155157
// }
156158

159+
if isGenericJavaType(javaType) {
160+
if let method {
161+
print("[swift] generic method! \(method.getDeclaringClass().getName()).\(method.getName())")
162+
let genericOriginInfos = getGenericJavaTypeOriginInfo(javaType, from: method)
163+
print("genericOriginInfos = \(genericOriginInfos)")
164+
}
165+
}
166+
157167
// Replace type variables with their bounds.
158168
if let typeVariable = javaType.as(TypeVariable<GenericDeclaration>.self),
159169
typeVariable.getBounds().count == 1,
160170
let bound = typeVariable.getBounds()[0]
161171
{
162-
return try getSwiftTypeNameAsString(
163-
bound,
164-
preferValueTypes: preferValueTypes,
165-
outerOptional: outerOptional
166-
)
172+
print("[swift] was type var: \(typeVariable)")
173+
print("[swift] was type var: \(typeVariable.toString())")
174+
print("[swift] was type var: \(typeVariable.getClass().getName())")
175+
print("[swift] was type var bound: \(bound)")
176+
// let typeName = try getSwiftTypeNameAsString(
177+
// bound,
178+
// preferValueTypes: preferValueTypes,
179+
// outerOptional: outerOptional
180+
// )
181+
// print("[swift] was type var: \(typeVariable.toString()) ----> \(typeName)")
182+
return outerOptional.adjustTypeName(typeVariable.getName())
183+
}
184+
185+
if let paramType = javaType.as(ParameterizedType.self) {
186+
print("[swift] paramType = \(paramType)")
187+
let typeArgs: [Type?] = paramType.getActualTypeArguments()
188+
for typeArg in typeArgs {
189+
print("Type arg = \(typeArg)")
190+
}
167191
}
168192

169193
// Replace wildcards with their upper bound.
170194
if let wildcardType = javaType.as(WildcardType.self),
171195
wildcardType.getUpperBounds().count == 1,
172196
let bound = wildcardType.getUpperBounds()[0]
173197
{
198+
print("[swift] was wildcard")
174199
// Replace a wildcard type with its first bound.
175200
return try getSwiftTypeNameAsString(
176201
bound,
@@ -181,6 +206,7 @@ func getSwiftReturnTypeNameAsString(
181206

182207
// Handle array types by recursing into the component type.
183208
if let arrayType = javaType.as(GenericArrayType.self) {
209+
print("[swift] was array")
184210
if preferValueTypes {
185211
let elementType = try getSwiftTypeNameAsString(
186212
arrayType.getGenericComponentType()!,
@@ -238,11 +264,19 @@ func getSwiftReturnTypeNameAsString(
238264
}
239265
}
240266

267+
print("[swift][swift-java][debug] Convert direct \(javaType)")
268+
241269
// Handle direct references to Java classes.
242270
guard let javaClass = javaType.as(JavaClass<JavaObject>.self) else {
243271
throw TranslationError.unhandledJavaType(javaType)
244272
}
245273

274+
print("[swift][swift-java][debug] Java class \(javaClass) | \(javaClass.toString())")
275+
if isGenericJavaType(javaType) {
276+
print("[swift][swift-java][debug] is generic \(javaClass.toString())")
277+
}
278+
279+
246280
let (swiftName, isOptional) = try getSwiftTypeName(javaClass, preferValueTypes: preferValueTypes)
247281
var resultString = swiftName
248282
if isOptional {

Tests/SwiftJavaToolLibTests/CompileJavaWrapTools.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func withJavaTranslator(
9292
func assertWrapJavaOutput(
9393
javaClassNames: [String],
9494
classpath: [Foundation.URL],
95+
assert assertBody: (JavaTranslator) throws -> Void = { _ in },
9596
expectedChunks: [String],
9697
function: String = #function,
9798
file: StaticString = #filePath,
@@ -143,17 +144,20 @@ func assertWrapJavaOutput(
143144
let swiftClassDecls = try translator.translateClass(javaClass)
144145
let importDecls = translator.getImportDecls()
145146

146-
let swiftFileText =
147-
"""
148-
// ---------------------------------------------------------------------------
149-
// Auto-generated by Java-to-Swift wrapper generator.
150-
\(importDecls.map { $0.description }.joined())
151-
\(swiftClassDecls.map { $0.description }.joined(separator: "\n"))
152-
\n
153-
"""
154-
swiftCompleteOutputText += swiftFileText
147+
let swiftFileText =
148+
"""
149+
// ---------------------------------------------------------------------------
150+
// Auto-generated by Java-to-Swift wrapper generator.
151+
\(importDecls.map { $0.description }.joined())
152+
\(swiftClassDecls.map { $0.description }.joined(separator: "\n"))
153+
\n
154+
"""
155+
swiftCompleteOutputText += swiftFileText
155156
}
156157

158+
// Run any additional user defined assertions:
159+
try assertBody(translator)
160+
157161
for expectedChunk in expectedChunks {
158162
// We make the matching in-sensitive to whitespace:
159163
let checkAgainstText = swiftCompleteOutputText.replacing(" ", with: "")

0 commit comments

Comments
 (0)