// InterfaceConformanceTests.swift // Covers interface conformance under the extension-default model (Phase E1): // classes emit `: Parent, Interface` headers; the interface's methods/properties // render as protocol-extension DEFAULT implementations (not bare requirements), // so a class's own method of the same name coexists without redeclaration // errors, and a concrete `Ref` wrapper is emitted so `any Interface` // values can be constructed from a raw C pointer. import Testing @testable import GObjectGeneratorCore @Suite("Interface conformance") struct InterfaceConformanceTests { func makeContext() -> MapContext { let gobject = Repository(namespaces: [ Namespace( name: "GObject", version: "2.0", classes: [ Class(name: "Object", cType: "GObject", parent: nil, getTypeFunction: "g_object_get_type"), ], interfaces: [ Interface(name: "TypePlugin", cType: "GTypePlugin", getTypeFunction: "g_type_plugin_get_type"), ] ) ]) let registry = TypeRegistry(repositories: ["GObject": gobject]) return MapContext(registry: registry, currentModule: "GObject", currentNamespace: "GObject") } @Test("Class implementing interface emits conformance clause; interface method becomes an extension default") func conformanceClauseEmitted() throws { // Interface: TypePlugin.use returns Void // Class: TypeModule implements TypePlugin, has its own `use` returning Bool. // Under the extension-default model both coexist: the class's own // method is a distinct overload from the protocol extension default, // so no redeclaration/witnessing conflict is possible. let iface = Interface( name: "TypePlugin", cType: "GTypePlugin", methods: [ Method(name: "use", cIdentifier: "g_type_plugin_use", parameters: [ Parameter(name: "self", type: .pointer, cType: "GTypePlugin*", isInstanceParameter: true) ], returnValue: ReturnValue(type: .void)), ], getTypeFunction: "g_type_plugin_get_type" ) let klass = Class( name: "TypeModule", cType: "GTypeModule", parent: "Object", getTypeFunction: "g_type_module_get_type", implements: ["TypePlugin"], methods: [ Method(name: "use", cIdentifier: "g_type_module_use", parameters: [ Parameter(name: "self", type: .pointer, cType: "GTypeModule*", isInstanceParameter: true) ], returnValue: ReturnValue(type: .boolean)), ] ) let ctx = makeContext() let (ifacePlan, _) = planInterface(iface, context: ctx) let (classPlan, _) = planClass(klass, context: ctx) let types: [TypePlan] = [.class(classPlan), .interface(ifacePlan)] let module = ModulePlan(module: "GObject", types: types, skips: [], coverage: CoverageStats()) let files = renderModule(module) let classSrc = files["TypeModule.swift"] ?? "" let ifaceSrc = files["TypePlugin.swift"] ?? "" #expect(classSrc.contains("class TypeModule: Object, @MainActor TypePlugin {")) // Protocol body is bare — only the `pointer` requirement. #expect(ifaceSrc.contains("public protocol TypePlugin {")) #expect(ifaceSrc.contains("@_spi(SGTKInternal) var pointer: UnsafeMutableRawPointer { get }")) // The interface method is a protocol EXTENSION default, not a // requirement — it keeps its own (Void) signature regardless of the // implementing class's differently-signatured method. #expect(ifaceSrc.contains("extension TypePlugin {")) #expect(ifaceSrc.contains("public func use(")) // Concrete Ref wrapper is emitted so `any TypePlugin` is constructible. #expect(ifaceSrc.contains("public final class TypePluginRef: @MainActor TypePlugin {")) #expect(ifaceSrc.contains("@_spi(SGTKInternal) public let pointer: UnsafeMutableRawPointer")) #expect(ifaceSrc.contains("@_spi(SGTKInternal) public init(retaining pointer: UnsafeMutableRawPointer)")) #expect(ifaceSrc.contains("@_spi(SGTKInternal) public init(takingOwnership pointer: UnsafeMutableRawPointer)")) #expect(ifaceSrc.contains("isolated deinit")) } @Test("Class with no interfaces keeps existing header unchanged") func noInterfacesNoChange() throws { let klass = Class( name: "Object", cType: "GObject", parent: nil, getTypeFunction: "g_object_get_type" ) let (classPlan, _) = planClass(klass, context: makeContext()) let module = ModulePlan(module: "GObject", types: [.class(classPlan)], skips: [], coverage: CoverageStats()) let files = renderModule(module) let src = files["Object.swift"] ?? "" #expect(src.contains("class Object {")) #expect(!src.contains("class Object: ")) } }