Tier 2 compile-gate compliance: interfaces, cross-module refs, Gio
This commit is contained in:
parent
5abb48e889
commit
0504b34d7f
18 changed files with 9186 additions and 304 deletions
|
|
@ -1,9 +1,10 @@
|
|||
// InterfaceConformanceTests.swift
|
||||
// Covers Phase C5 conformance: classes emit `: Parent, Interface` headers and
|
||||
// protocol requirements are filtered to those every implementing class can
|
||||
// witness. Methods whose Swift signature drifts between the interface node and
|
||||
// a class node are dropped from the protocol (recorded as a skip) so the
|
||||
// conformance stays compilable.
|
||||
// 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
|
||||
|
||||
|
|
@ -29,11 +30,13 @@ struct InterfaceConformanceTests {
|
|||
return MapContext(registry: registry, currentModule: "GObject", currentNamespace: "GObject")
|
||||
}
|
||||
|
||||
@Test("Class implementing interface emits conformance clause")
|
||||
@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
|
||||
// (signature drifts → requirement dropped, but conformance still emitted)
|
||||
// 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: [
|
||||
|
|
@ -62,17 +65,12 @@ struct InterfaceConformanceTests {
|
|||
]
|
||||
)
|
||||
|
||||
// Plan both, build type plan array, then filter
|
||||
let ctx = makeContext()
|
||||
let (ifacePlan, _) = planInterface(iface, context: ctx)
|
||||
let (classPlan, _) = planClass(klass, context: ctx)
|
||||
|
||||
var skips: [SkipEntry] = []
|
||||
let types: [TypePlan] = [.class(classPlan), .interface(ifacePlan)]
|
||||
let filteredTypes = filterInterfaceRequirements(types, skips: &skips)
|
||||
|
||||
// Render the filtered module
|
||||
let module = ModulePlan(module: "GObject", types: filteredTypes, skips: skips,
|
||||
let module = ModulePlan(module: "GObject", types: types, skips: [],
|
||||
coverage: CoverageStats())
|
||||
let files = renderModule(module)
|
||||
let classSrc = files["TypeModule.swift"] ?? ""
|
||||
|
|
@ -80,67 +78,21 @@ struct InterfaceConformanceTests {
|
|||
|
||||
#expect(classSrc.contains("class TypeModule: Object, TypePlugin {"))
|
||||
|
||||
// Drifted method `use` dropped from protocol
|
||||
#expect(!ifaceSrc.contains("func use"))
|
||||
// Protocol body is bare — only the `pointer` requirement.
|
||||
#expect(ifaceSrc.contains("public protocol TypePlugin {"))
|
||||
#expect(ifaceSrc.contains("var pointer: UnsafeMutableRawPointer { get }"))
|
||||
|
||||
// Skip recorded for the drift
|
||||
let driftSkips = skips.filter { $0.reason == .interfaceMethodSignatureDrift }
|
||||
#expect(driftSkips[0].symbol == "TypePlugin.use")
|
||||
}
|
||||
// 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("))
|
||||
|
||||
@Test("Consistent interface/class signatures keep the requirement in the protocol")
|
||||
func consistentSignaturesKeepRequirement() throws {
|
||||
// Regression guard: when the interface and implementing class agree on
|
||||
// a method's signature, the requirement MUST remain in the rendered
|
||||
// protocol. Without this test the filter could over-drop every
|
||||
// requirement and the suite would still pass (the drift test only
|
||||
// asserts dropping; the no-interfaces test never exercises the keep
|
||||
// branch at all).
|
||||
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: .boolean)),
|
||||
],
|
||||
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)
|
||||
|
||||
var skips: [SkipEntry] = []
|
||||
let types: [TypePlan] = [.class(classPlan), .interface(ifacePlan)]
|
||||
let filteredTypes = filterInterfaceRequirements(types, skips: &skips)
|
||||
|
||||
let module = ModulePlan(module: "GObject", types: filteredTypes, skips: skips,
|
||||
coverage: CoverageStats())
|
||||
let files = renderModule(module)
|
||||
let ifaceSrc = files["TypePlugin.swift"] ?? ""
|
||||
|
||||
// Requirement kept: `func use` appears in the protocol with a Bool return.
|
||||
#expect(ifaceSrc.contains("func use"))
|
||||
// No drift skip was recorded.
|
||||
#expect(skips.filter { $0.reason == .interfaceMethodSignatureDrift }.isEmpty)
|
||||
// Concrete Ref wrapper is emitted so `any TypePlugin` is constructible.
|
||||
#expect(ifaceSrc.contains("public final class TypePluginRef: TypePlugin {"))
|
||||
#expect(ifaceSrc.contains("init(retaining pointer: UnsafeMutableRawPointer)"))
|
||||
#expect(ifaceSrc.contains("init(takingOwnership pointer: UnsafeMutableRawPointer)"))
|
||||
#expect(ifaceSrc.contains("isolated deinit"))
|
||||
}
|
||||
|
||||
@Test("Class with no interfaces keeps existing header unchanged")
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// InterfaceGenerationTests.swift
|
||||
// Covers Phase C5: GObject interface method planning and rendering. An
|
||||
// interface method becomes a protocol *requirement* — a `func` signature with
|
||||
// no `public` modifier and no body — because the C symbol that implements it
|
||||
// lives on the conforming class, not the interface. Unplannable methods
|
||||
// (unsupported parameter types) are recorded as skips, mirroring class
|
||||
// members. These tests lock in that surface so a regression surfaces here.
|
||||
// Covers interface method planning and rendering under the extension-default
|
||||
// model (Phase E1): an interface method's body is rendered as a `public`
|
||||
// protocol-extension DEFAULT implementation (`extension Ping { public func … }`)
|
||||
// calling the C symbol via `self.pointer`, since interface protocols have no
|
||||
// members of their own beyond `pointer`. Unplannable methods (unsupported
|
||||
// parameter types) are recorded as skips, mirroring class members. These
|
||||
// tests lock in that surface so a regression surfaces here.
|
||||
|
||||
import Testing
|
||||
|
||||
|
|
@ -32,8 +33,8 @@ struct InterfaceGenerationTests {
|
|||
return renderModule(module)["\(plan.name).swift"] ?? ""
|
||||
}
|
||||
|
||||
@Test("Interface instance methods become protocol requirements")
|
||||
func methodsBecomeRequirements() throws {
|
||||
@Test("Interface instance methods become protocol extension default implementations")
|
||||
func methodsBecomeExtensionDefaults() throws {
|
||||
let iface = Interface(
|
||||
name: "Ping", cType: "GPing",
|
||||
methods: [
|
||||
|
|
@ -51,10 +52,13 @@ struct InterfaceGenerationTests {
|
|||
#expect(plan.methods[0].name == "getId")
|
||||
|
||||
let source = render(plan)
|
||||
// A requirement — no `public`, no body.
|
||||
#expect(source.contains("func getId() -> Int"))
|
||||
#expect(!source.contains("public func getId"))
|
||||
#expect(!source.contains("g_ping_get_id")) // no body means no C call
|
||||
// Protocol body is bare — only the `pointer` requirement.
|
||||
#expect(source.contains("public protocol Ping {"))
|
||||
#expect(source.contains("var pointer: UnsafeMutableRawPointer { get }"))
|
||||
// A default implementation — `public`, with a body calling the C symbol.
|
||||
#expect(source.contains("extension Ping {"))
|
||||
#expect(source.contains("public func getId() -> Int"))
|
||||
#expect(source.contains("g_ping_get_id"))
|
||||
}
|
||||
|
||||
@Test("Unplannable interface methods are recorded as skips, not requirements")
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// PropertyGenerationTests.swift
|
||||
// Covers Phase C6: GObject property planning and rendering. A property becomes
|
||||
// a Swift computed `var` backed by the GValue machinery
|
||||
// (`g_value_init` / `g_object_get_property` / `g_object_set_property`), or — for
|
||||
// interfaces — a `{ get }` / `{ get set }` protocol requirement. Properties
|
||||
// (`g_value_init` / `g_object_get_property` / `g_object_set_property`), rendered
|
||||
// as a `public var` — for interfaces, as a protocol-extension default. Properties
|
||||
// whose type has no `GValueOps` are recorded as skips.
|
||||
//
|
||||
// These tests also lock in the numeric bridging of enum/flags getters: a
|
||||
|
|
@ -235,10 +235,10 @@ struct PropertyGenerationTests {
|
|||
#expect(!source.contains("internalThing"))
|
||||
}
|
||||
|
||||
// ── Interface property requirements ──
|
||||
// ── Interface property extension defaults ──
|
||||
|
||||
@Test("A readable-only interface property becomes a { get } requirement")
|
||||
func interfaceReadOnlyPropertyIsGetRequirement() throws {
|
||||
@Test("A readable-only interface property becomes a GValue-backed extension default getter")
|
||||
func interfaceReadOnlyPropertyIsExtensionDefault() throws {
|
||||
let iface = Interface(
|
||||
name: "Orientable", cType: "GtkOrientable",
|
||||
properties: [
|
||||
|
|
@ -253,14 +253,16 @@ struct PropertyGenerationTests {
|
|||
let module = ModulePlan(module: "GObject", types: [.interface(plan)], skips: [],
|
||||
coverage: CoverageStats())
|
||||
let source = renderModule(module)["Orientable.swift"] ?? ""
|
||||
#expect(source.contains("var orientation: Orientation { get }"))
|
||||
// A requirement — no body, no GValue machinery.
|
||||
#expect(!source.contains("g_object_get_property"))
|
||||
#expect(!source.contains("public var orientation"))
|
||||
// Protocol body is bare — only the `pointer` requirement.
|
||||
#expect(!source.contains("var orientation: Orientation { get }"))
|
||||
// A default implementation, in a protocol extension, with a real body.
|
||||
#expect(source.contains("extension Orientable {"))
|
||||
#expect(source.contains("public var orientation: Orientation"))
|
||||
#expect(source.contains("g_object_get_property"))
|
||||
}
|
||||
|
||||
@Test("A writable interface property becomes a { get set } requirement")
|
||||
func interfaceWritablePropertyIsGetSetRequirement() throws {
|
||||
@Test("A writable interface property becomes a GValue-backed extension default getter+setter")
|
||||
func interfaceWritablePropertyIsExtensionDefault() throws {
|
||||
let iface = Interface(
|
||||
name: "Editable", cType: "GtkEditable",
|
||||
properties: [
|
||||
|
|
@ -271,7 +273,10 @@ struct PropertyGenerationTests {
|
|||
let module = ModulePlan(module: "GObject", types: [.interface(plan)], skips: [],
|
||||
coverage: CoverageStats())
|
||||
let source = renderModule(module)["Editable.swift"] ?? ""
|
||||
#expect(source.contains("var text: String { get set }"))
|
||||
#expect(!source.contains("var text: String { get set }"))
|
||||
#expect(source.contains("extension Editable {"))
|
||||
#expect(source.contains("public var text: String"))
|
||||
#expect(source.contains("g_object_set_property"))
|
||||
}
|
||||
|
||||
// ── getter=/setter= method delegation ──
|
||||
|
|
|
|||
|
|
@ -185,4 +185,114 @@ struct RendererCallableTests {
|
|||
let outSkip = skips.first { $0.reason == .constructorOutParams }
|
||||
#expect(outSkip != nil)
|
||||
}
|
||||
// MARK: - E1: throwing constructor with string params doesn't nest self.init
|
||||
|
||||
@Test("Throwing constructor with a String param threads the result out of withCString instead of nesting self.init")
|
||||
func throwingConstructorWithStringParamAvoidsNestedSelfInit() throws {
|
||||
// `self.init` (a delegating initializer call) is illegal inside any
|
||||
// closure, including a non-escaping `withCString` trailing closure.
|
||||
// Regression guard for the tier-2 DBusConnection/DBusProxy bug where
|
||||
// string-parameterised throwing constructors called self.init from
|
||||
// inside the withCString closure.
|
||||
let klass = Class(
|
||||
name: "Proxy", cType: "GProxy", parent: "Object",
|
||||
getTypeFunction: "g_proxy_get_type",
|
||||
constructors: [
|
||||
Constructor(name: "new_for_bus_sync", cIdentifier: "g_proxy_new_for_bus_sync",
|
||||
parameters: [
|
||||
Parameter(name: "name", type: .string, cType: "const char*"),
|
||||
],
|
||||
returnValue: ReturnValue(transferOwnership: .full),
|
||||
throwsGError: true),
|
||||
]
|
||||
)
|
||||
let (plan, _) = planClass(klass, context: makeContext())
|
||||
let module = ModulePlan(module: "GObject", types: [.class(plan)], skips: [],
|
||||
coverage: CoverageStats())
|
||||
let src = renderModule(module)["Proxy.swift"] ?? ""
|
||||
// The C call (returning the result) lives INSIDE the withCString
|
||||
// closure as its trailing/implicit-return expression...
|
||||
#expect(src.contains("g_proxy_new_for_bus_sync(cString0, &error)"))
|
||||
// ...while `self.init` runs OUTSIDE every closure, never nested.
|
||||
#expect(!src.contains("cString0, &error)\n self.init"))
|
||||
let lines = src.components(separatedBy: "\n")
|
||||
guard let selfInitLine = lines.first(where: { $0.contains("self.init(takingOwnership:") }) else {
|
||||
Issue.record("expected a self.init(takingOwnership:) line"); return
|
||||
}
|
||||
// Indentation of 8 spaces == top-level init body scope, not nested
|
||||
// one level inside the withCString closure (12 spaces).
|
||||
#expect(selfInitLine.hasPrefix(" self.init"))
|
||||
}
|
||||
|
||||
// MARK: - E1: constructor signature deduplication
|
||||
|
||||
@Test("Two constructors with the same rendered init signature but different Swift names dedup to one, keeping the first")
|
||||
func constructorsWithIdenticalSignatureDedup() throws {
|
||||
// `init(...)` never carries the constructor's Swift name (it always
|
||||
// renders as bare `init`), so two GIR constructors mapping to
|
||||
// DIFFERENT Swift names (`newFinish` vs `newForAddressFinish`) but
|
||||
// the IDENTICAL parameter/throws/return shape still collide at
|
||||
// `init(res:)`. Regression guard for the tier-2 DBusConnection bug.
|
||||
let resParam = Parameter(name: "res", type: .typeRef("AsyncResult", namespace: "GObject"),
|
||||
cType: "GAsyncResult*")
|
||||
let klass = Class(
|
||||
name: "Connection", cType: "GConnection", parent: "Object",
|
||||
getTypeFunction: "g_connection_get_type",
|
||||
constructors: [
|
||||
Constructor(name: "new_finish", cIdentifier: "g_connection_new_finish",
|
||||
parameters: [resParam],
|
||||
returnValue: ReturnValue(transferOwnership: .full),
|
||||
throwsGError: true),
|
||||
Constructor(name: "new_for_address_finish", cIdentifier: "g_connection_new_for_address_finish",
|
||||
parameters: [resParam],
|
||||
returnValue: ReturnValue(transferOwnership: .full),
|
||||
throwsGError: true),
|
||||
]
|
||||
)
|
||||
let ctx = MapContext(
|
||||
registry: TypeRegistry(repositories: ["GObject": Repository(namespaces: [
|
||||
Namespace(name: "GObject", version: "2.0", classes: [
|
||||
Class(name: "Object", cType: "GObject", parent: nil, getTypeFunction: "g_object_get_type"),
|
||||
Class(name: "AsyncResult", cType: "GAsyncResult", parent: "Object",
|
||||
getTypeFunction: "g_async_result_get_type"),
|
||||
])
|
||||
])]),
|
||||
currentModule: "GObject", currentNamespace: "GObject"
|
||||
)
|
||||
let (plan, skips) = planClass(klass, context: ctx)
|
||||
#expect(plan.constructors.count == 1)
|
||||
#expect(plan.constructors[0].cIdentifier == "g_connection_new_finish")
|
||||
let dedupSkip = skips.first { $0.reason == .nameCollision }
|
||||
#expect(dedupSkip?.cIdentifier == "g_connection_new_for_address_finish")
|
||||
|
||||
let module = ModulePlan(module: "GObject", types: [.class(plan)], skips: [],
|
||||
coverage: CoverageStats())
|
||||
let src = renderModule(module)["Connection.swift"] ?? ""
|
||||
let occurrences = src.components(separatedBy: "convenience init(res: AsyncResult) throws").count - 1
|
||||
#expect(occurrences == 1)
|
||||
}
|
||||
|
||||
// MARK: - E1: pointer out-param zero-initializes to nil, not 0
|
||||
|
||||
@Test("A gpointer out-param local variable initializes to nil, not the integer 0")
|
||||
func pointerOutParamInitializesToNil() throws {
|
||||
// `.direct` marshalIn covers both numeric out-params (`gint*` → `0`)
|
||||
// and raw-pointer out-params (`gpointer*` → `UnsafeMutableRawPointer?`,
|
||||
// which `= 0` cannot initialize). Regression guard for the tier-2
|
||||
// FileInfo.getAttributeData bug.
|
||||
let fn = GlobalFunction(
|
||||
name: "get_attribute_data", cIdentifier: "g_file_info_get_attribute_data",
|
||||
parameters: [
|
||||
Parameter(name: "attribute", type: .string, cType: "const char*"),
|
||||
Parameter(name: "value_pp", type: .pointer, cType: "gpointer*", direction: .out),
|
||||
],
|
||||
returnValue: ReturnValue(type: .void)
|
||||
)
|
||||
guard case .success(let plan) = planFunction(fn, context: makeContext()) else {
|
||||
Issue.record("expected get_attribute_data to plan successfully"); return
|
||||
}
|
||||
let body = renderCallable(plan)
|
||||
#expect(body.contains("var out0: UnsafeMutableRawPointer? = nil"))
|
||||
#expect(!body.contains("var out0: UnsafeMutableRawPointer? = 0"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ struct TypeMapperTests {
|
|||
let ctx = makeContext()
|
||||
let mapping = try map(.typeRef("Object", namespace: "GObject"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
#expect(mapping.swiftType == "GObject.Object")
|
||||
#expect(mapping.swiftType == "Object")
|
||||
#expect(mapping.cSwiftType == "UnsafeMutableRawPointer?")
|
||||
#expect(mapping.marshalIn == .objectPointer)
|
||||
#expect(mapping.marshalOut == .objectWrap(sink: false))
|
||||
|
|
@ -301,8 +301,9 @@ struct TypeMapperTests {
|
|||
let ctx = makeContext()
|
||||
let mapping = try map(.typeRef("TypePlugin", namespace: "GObject"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
#expect(mapping.swiftType == "GObject.TypePlugin")
|
||||
#expect(mapping.marshalIn == .objectPointer)
|
||||
#expect(mapping.swiftType == "TypePlugin")
|
||||
#expect(mapping.marshalIn == .interfacePointer)
|
||||
#expect(mapping.marshalOut == .interfaceWrap(adopt: false))
|
||||
}
|
||||
|
||||
@Test("Enum typeRef maps to enum raw-value wrapping")
|
||||
|
|
@ -310,10 +311,10 @@ struct TypeMapperTests {
|
|||
let ctx = makeContext()
|
||||
let mapping = try map(.typeRef("ParamFlags", namespace: "GObject"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
#expect(mapping.swiftType == "GObject.ParamFlags")
|
||||
#expect(mapping.swiftType == "ParamFlags")
|
||||
#expect(mapping.cSwiftType == "GParamFlags")
|
||||
#expect(mapping.marshalIn == .enumRaw)
|
||||
#expect(mapping.marshalOut == .enumFromRaw(swiftType: "GObject.ParamFlags"))
|
||||
#expect(mapping.marshalOut == .enumFromRaw(swiftType: "ParamFlags"))
|
||||
#expect(mapping.gvalue?.typeMacro == "G_TYPE_ENUM")
|
||||
}
|
||||
|
||||
|
|
@ -322,10 +323,10 @@ struct TypeMapperTests {
|
|||
let ctx = makeContext()
|
||||
let mapping = try map(.typeRef("SignalFlags", namespace: "GObject"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
#expect(mapping.swiftType == "GObject.SignalFlags")
|
||||
#expect(mapping.swiftType == "SignalFlags")
|
||||
#expect(mapping.cSwiftType == "GSignalFlags")
|
||||
#expect(mapping.marshalIn == .bitfieldRaw)
|
||||
#expect(mapping.marshalOut == .bitfieldFromRaw(swiftType: "GObject.SignalFlags"))
|
||||
#expect(mapping.marshalOut == .bitfieldFromRaw(swiftType: "SignalFlags"))
|
||||
#expect(mapping.gvalue?.typeMacro == "G_TYPE_FLAGS")
|
||||
}
|
||||
|
||||
|
|
@ -334,7 +335,7 @@ struct TypeMapperTests {
|
|||
let ctx = makeContext()
|
||||
let mapping = try map(.typeRef("Value", namespace: "GObject"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
#expect(mapping.swiftType == "GObject.Value")
|
||||
#expect(mapping.swiftType == "Value")
|
||||
#expect(mapping.cSwiftType == "UnsafeMutableRawPointer?")
|
||||
#expect(mapping.marshalIn == .boxedPointer)
|
||||
#expect(mapping.marshalOut == .boxedWrap(copy: true, copyFunction: "g_value_copy")) // transfer != .full → copy
|
||||
|
|
@ -524,7 +525,7 @@ struct TypeMapperTests {
|
|||
// Should succeed without throwing.
|
||||
let mapping = try map(.typeRef("Value", namespace: "GObject"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
#expect(mapping.swiftType == "GObject.Value")
|
||||
#expect(mapping.swiftType == "Value")
|
||||
#expect(mapping.marshalOut == .boxedWrap(copy: true, copyFunction: "g_value_copy"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,12 +159,15 @@ struct TypeRegistryTests {
|
|||
#expect(!subclassed.contains("Gtk.Label"))
|
||||
}
|
||||
|
||||
@Test("Swift spelling qualifies only cross-module references")
|
||||
@Test("Swift spelling is always unqualified — per-file dependency imports resolve cross-module references")
|
||||
func spellsSwiftTypeNames() throws {
|
||||
let registry = makeRegistry()
|
||||
let object = try #require(registry.resolve(girName: "GObject.Object"))
|
||||
let widget = try #require(registry.resolve(girName: "Gtk.Widget"))
|
||||
#expect(registry.swiftTypeName(for: object, in: "Gtk") == "GObject.Object")
|
||||
// Cross-module references are unqualified (`Object`, not `GObject.Object`):
|
||||
// qualifying is impossible when the C typedef of the same name (from
|
||||
// the generated file's `import CGtk`) shadows the Swift module.
|
||||
#expect(registry.swiftTypeName(for: object, in: "Gtk") == "Object")
|
||||
#expect(registry.swiftTypeName(for: object, in: "GObject") == "Object")
|
||||
#expect(registry.swiftTypeName(for: widget, in: "Gtk") == "Widget")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue