113 lines
5.4 KiB
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: "))
|
|
}
|
|
}
|