1
0
Fork 0
gobject-generator/Tests/GObjectGeneratorCoreTests/InterfaceConformanceTests.swift

113 lines
5.4 KiB
Swift

// 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 `<Name>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: "))
}
}