diff --git a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift index c0808c37..86ed4211 100644 --- a/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/SwiftSyntax+Extensions.swift @@ -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 diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index dc28006b..b2cde4b6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -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, diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 57a9cd9c..1a583b1d 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -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 } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 13e7aada..5a12376f 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -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 { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index f2f9eb77..28b0e4d0 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -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: diff --git a/Sources/SwiftJavaConfigurationShared/Configuration.swift b/Sources/SwiftJavaConfigurationShared/Configuration.swift index 610094ef..502996c4 100644 --- a/Sources/SwiftJavaConfigurationShared/Configuration.swift +++ b/Sources/SwiftJavaConfigurationShared/Configuration.swift @@ -123,9 +123,9 @@ public struct Configuration: Codable { /// "base": "Box", /// "typeArgs": {"Element": "Fish"} /// }, - /// "PetBox": { + /// "ToolBox": { /// "base": "Box", - /// "typeArgs": {"Element": "Pet"} + /// "typeArgs": {"Element": "Tool"} /// } /// } /// } diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 87bb5047..890e2752 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -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` | ❌ | ✅ | | Functions or properties using generic type param: `struct S { func f(_: T) {} }` | ❌ | ❌ | -| Generic type specialization and conditional extensions: `struct S{} extension S where T == Value {}` | ✅ | ❌ | +| Generic type specialization and conditional extensions: `struct S{} extension S where T == Value {}` | ❌ | ✅ | | Static functions or properties in generic type | ❌ | ❌ | | Generic parameters in functions: `func f(x: T)` | ❌ | ✅ | | Generic return values in functions: `func f() -> T` | ❌ | ❌ | diff --git a/Tests/JExtractSwiftTests/SpecializationTests.swift b/Tests/JExtractSwiftTests/SpecializationTests.swift index e8670e36..aa975dc9 100644 --- a/Tests/JExtractSwiftTests/SpecializationTests.swift +++ b/Tests/JExtractSwiftTests/SpecializationTests.swift @@ -43,7 +43,7 @@ struct SpecializationTests { public var name: String } - public struct Pet { + public struct Tool { public var name: String } @@ -52,7 +52,7 @@ struct SpecializationTests { } public typealias FishBox = Box - public typealias PetBox = Box + public typealias ToolBox = Box """# // ==== ----------------------------------------------------------------------- @@ -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"]) @@ -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") - #expect(petBox.effectiveSwiftTypeName == "Box") + #expect(toolBox.effectiveSwiftTypeName == "Box") // Verify new generic-model properties #expect(fishBox.genericParameterNames == ["Element"]) @@ -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") } @@ -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"]) } // ==== ----------------------------------------------------------------------- @@ -151,8 +151,8 @@ 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, @@ -160,20 +160,20 @@ struct SpecializationTests { detectChunkByInitialLines: 1, expectedChunks: [ // Class declaration - "public final class PetBox implements JNISwiftInstance {", + "public final class ToolBox implements JNISwiftInstance {", // Base method from Box "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")