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 @@ -131,7 +131,7 @@ extension AttributeListSyntax.Element {
/// Whether this node has `SwiftJava` wrapping attributes (types that wrap Java classes).
/// These are skipped during jextract because they represent Java->Swift wrappers.
/// Note: `@JavaExport` is NOT included here — it forces export of Swift types to Java.
var isJava: Bool {
var isSwiftJavaMacro: Bool {
guard case let .attribute(attr) = self else {
// FIXME: Handle #if.
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1195,39 +1195,6 @@ extension JNISwift2JavaGenerator {
)
}

/// Translate a single element type for tuple results on the Java side.
private func translateTupleElementResult(
type: SwiftType,
genericParameters: [SwiftGenericParameterDeclaration],
genericRequirements: [SwiftGenericRequirement],
) throws -> (JavaType, JavaNativeConversionStep) {
switch type {
case .nominal(let nominalType):
if let knownType = nominalType.nominalTypeDecl.knownTypeKind {
guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else {
throw JavaTranslationError.unsupportedSwiftType(type)
}
// Primitives: just read from array
return (javaType, .placeholder)
}

guard !nominalType.isSwiftJavaWrapper else {
throw JavaTranslationError.unsupportedSwiftType(type)
}

let javaType = try translateGenericTypeParameter(
type,
genericParameters: genericParameters,
genericRequirements: genericRequirements,
)
// JExtract class: wrap memory address
return (.long, .constructSwiftValue(.placeholder, javaType))

default:
throw JavaTranslationError.unsupportedSwiftType(type)
}
}

func translateOptionalResult(
wrappedType swiftType: SwiftType,
resultName: String,
Expand Down
2 changes: 1 addition & 1 deletion Sources/JExtractSwiftLib/Swift2JavaVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyn
)
return false
}
guard !attributes.contains(where: { $0.isJava }) else {
guard !attributes.contains(where: { $0.isSwiftJavaMacro }) else {
log.debug("Skip import '\(self.qualifiedNameForDebug)': is Java")
return false
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ extension SwiftNominalType.Parent: CustomStringConvertible {

extension SwiftNominalType {
var isSwiftJavaWrapper: Bool {
nominalTypeDecl.syntax?.attributes.contains(where: \.isJava) ?? false
nominalTypeDecl.syntax?.attributes.contains(where: \.isSwiftJavaMacro) ?? false
}

var isProtocol: Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,23 @@ class SwiftTypeLookupContext {
case .protocolDecl(let node):
typeDecl = try nominalTypeDeclaration(for: node, sourceFilePath: sourceFilePath)
case .extensionDecl(let node):
// For extensions, we have to perform a unqualified lookup,
// as the extentedType is just the identifier of the type.
// For extensions, we need to resolve the extended type to find the
// actual nominal type declaration. The extended type might be a simple
// identifier (e.g. `extension Foo`) or a member type
// (e.g. `extension P256._ARCV1`).

guard case .identifierType(let id) = Syntax(node.extendedType).as(SyntaxEnum.self),
if case .identifierType(let id) = Syntax(node.extendedType).as(SyntaxEnum.self),
let lookupResult = try unqualifiedLookup(name: Identifier(id.name)!, from: node)
else {
throw TypeLookupError.notType(Syntax(node))
{
typeDecl = lookupResult
} else {
// For member types (e.g. P256._ARCV1), resolve through SwiftType
let swiftType = try SwiftType(node.extendedType, lookupContext: self)
guard let nominalDecl = swiftType.asNominalTypeDeclaration else {
throw TypeLookupError.notType(Syntax(node))
}
typeDecl = nominalDecl
}

typeDecl = lookupResult
case .typeAliasDecl:
fatalError("typealias not implemented")
case .associatedTypeDecl:
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftJavaConfigurationShared/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ public struct Configuration: Codable {
/// "base": "Box",
/// "typeArgs": {"Element": "Fish"}
/// },
/// "PetBox": {
/// "ToolBox": {
/// "base": "Box",
/// "typeArgs": {"Element": "Pet"}
/// "typeArgs": {"Element": "Tool"}
/// }
/// }
/// }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S
| Dictionaries: `[String: Int]`, `[K:V]` | ❌ | ✅ |
| Generic type: `struct S<T>` | ❌ | ✅ |
| Functions or properties using generic type param: `struct S<T> { func f(_: T) {} }` | ❌ | ❌ |
| Generic type specialization and conditional extensions: `struct S<T>{} extension S where T == Value {}` | ✅ | ❌ |
| Generic type specialization and conditional extensions: `struct S<T>{} extension S where T == Value {}` | ❌ | ✅ |
| Static functions or properties in generic type | ❌ | ❌ |
| Generic parameters in functions: `func f<T: A & B>(x: T)` | ❌ | ✅ |
| Generic return values in functions: `func f<T: A & B>() -> T` | ❌ | ❌ |
Expand Down
42 changes: 21 additions & 21 deletions Tests/JExtractSwiftTests/SpecializationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct SpecializationTests {
public var name: String
}

public struct Pet {
public struct Tool {
public var name: String
}

Expand All @@ -52,7 +52,7 @@ struct SpecializationTests {
}

public typealias FishBox = Box<Fish>
public typealias PetBox = Box<Pet>
public typealias ToolBox = Box<Tool>
"""#

// ==== -----------------------------------------------------------------------
Expand All @@ -67,7 +67,7 @@ struct SpecializationTests {

// Both specialized types should be registered
#expect(translator.importedTypes["FishBox"] != nil, "FishBox should be in importedTypes")
#expect(translator.importedTypes["PetBox"] != nil, "PetBox should be in importedTypes")
#expect(translator.importedTypes["ToolBox"] != nil, "ToolBox should be in importedTypes")

// The base generic type remains in importedTypes (not removed)
let baseBox = try #require(translator.importedTypes["Box"])
Expand All @@ -78,16 +78,16 @@ struct SpecializationTests {

// Specialized types link back to their base
let fishBox = try #require(translator.importedTypes["FishBox"])
let petBox = try #require(translator.importedTypes["PetBox"])
let toolBox = try #require(translator.importedTypes["ToolBox"])
#expect(fishBox.isSpecialization)
#expect(petBox.isSpecialization)
#expect(toolBox.isSpecialization)

// Verify effective names are distinct
#expect(fishBox.effectiveJavaName == "FishBox")
#expect(petBox.effectiveJavaName == "PetBox")
#expect(toolBox.effectiveJavaName == "ToolBox")

#expect(fishBox.effectiveSwiftTypeName == "Box<Fish>")
#expect(petBox.effectiveSwiftTypeName == "Box<Pet>")
#expect(toolBox.effectiveSwiftTypeName == "Box<Tool>")

// Verify new generic-model properties
#expect(fishBox.genericParameterNames == ["Element"])
Expand All @@ -96,14 +96,14 @@ struct SpecializationTests {
#expect(fishBox.baseTypeName == "Box")
#expect(fishBox.specializedTypeName == "FishBox")

#expect(petBox.genericParameterNames == ["Element"])
#expect(petBox.genericArguments == ["Element": "Pet"])
#expect(petBox.isFullySpecialized)
#expect(petBox.baseTypeName == "Box")
#expect(petBox.specializedTypeName == "PetBox")
#expect(toolBox.genericParameterNames == ["Element"])
#expect(toolBox.genericArguments == ["Element": "Tool"])
#expect(toolBox.isFullySpecialized)
#expect(toolBox.baseTypeName == "Box")
#expect(toolBox.specializedTypeName == "ToolBox")

// Both wrappers delegate to the same base type
#expect(fishBox.specializationBaseType === petBox.specializationBaseType, "Both should wrap the same base Box type")
#expect(fishBox.specializationBaseType === toolBox.specializationBaseType, "Both should wrap the same base Box type")
#expect(fishBox.specializationBaseType === translator.importedTypes["Box"], "Base should be the original Box")
}

Expand All @@ -119,7 +119,7 @@ struct SpecializationTests {
#expect(specializations.count == 2, "Should have exactly 2 specializations for Box")

let javaNames = specializations.map(\.effectiveJavaName).sorted()
#expect(javaNames == ["FishBox", "PetBox"])
#expect(javaNames == ["FishBox", "ToolBox"])
}

// ==== -----------------------------------------------------------------------
Expand Down Expand Up @@ -151,29 +151,29 @@ struct SpecializationTests {
)
}

@Test("PetBox Java class has base methods but not Fish-constrained methods")
func petBoxJavaClass() throws {
@Test("ToolBox Java class has base methods but not Fish-constrained methods")
func toolBoxJavaClass() throws {
try assertOutput(
input: multiSpecializationInput,
.jni,
.java,
detectChunkByInitialLines: 1,
expectedChunks: [
// Class declaration
"public final class PetBox implements JNISwiftInstance {",
"public final class ToolBox implements JNISwiftInstance {",
// Base method from Box<Element>
"public long count()",
],
)

// Verify observeTheFish does NOT appear inside PetBox's class body
// Verify observeTheFish does NOT appear inside ToolBox's class body
var config = Configuration()
config.swiftModule = "SwiftModule"
let translator = Swift2JavaTranslator(config: config)
try translator.analyze(path: "/fake/Fake.swiftinterface", text: multiSpecializationInput)
let petBox = try #require(translator.importedTypes["PetBox"])
let methodNames = petBox.methods.map(\.name)
#expect(!methodNames.contains("observeTheFish"), "PetBox should not have Fish-constrained method")
let toolBox = try #require(translator.importedTypes["ToolBox"])
let methodNames = toolBox.methods.map(\.name)
#expect(!methodNames.contains("observeTheFish"), "ToolBox should not have Fish-constrained method")
}

@Test("Single specialization generates expected Java class")
Expand Down
Loading