1
0
Fork 0

Boxed-record methods and moved-to routing

This commit is contained in:
Brendan Szymanski 2026-08-11 17:34:26 -04:00
parent 14072c5df6
commit 18e14a5b67
53 changed files with 19320 additions and 5999 deletions

View file

@ -149,4 +149,65 @@ struct RecordGenerationTests {
#expect(resolvedFreeFunction(record) == "g_priority_unref")
#expect(resolvedCopyFunction(record) == "g_priority_ref")
}
@Test("Boxed record renders callables but hides lifetime methods")
func rendersRecordCallablesWithoutLifetimeMethods() {
let record = Record(
name: "BreakpointCondition", cType: "AdwBreakpointCondition",
getTypeFunction: "adw_breakpoint_condition_get_type",
copyFunction: "adw_breakpoint_condition_copy",
freeFunction: "adw_breakpoint_condition_free",
methods: [
releaseMethod("free", "adw_breakpoint_condition_free"),
releaseMethod("unref", "adw_breakpoint_condition_unref"),
Method(name: "to_string", cIdentifier: "adw_breakpoint_condition_to_string",
parameters: [Parameter(name: "self", type: .pointer, isInstanceParameter: true)],
returnValue: ReturnValue(type: .string)),
],
constructors: [
Constructor(name: "new_length", cIdentifier: "adw_breakpoint_condition_new_length",
parameters: [Parameter(name: "value", type: .double)]),
Constructor(name: "new_and", cIdentifier: "adw_breakpoint_condition_new_and",
parameters: [Parameter(name: "condition1", type: .double),
Parameter(name: "condition2", type: .double)]),
Constructor(name: "new_or", cIdentifier: "adw_breakpoint_condition_new_or",
parameters: [Parameter(name: "condition1", type: .double),
Parameter(name: "condition2", type: .double)]),
],
functions: [GlobalFunction(name: "parse", cIdentifier: "adw_breakpoint_condition_parse",
parameters: [Parameter(name: "value", type: .string, cType: "const char*")])]
)
let result = planRecord(record, context: makeContext())
let source = render(result.plan)
#expect(result.memberSkips.isEmpty)
#expect(source.contains("public convenience init("))
#expect(source.contains("_rawPointer(adw_breakpoint_condition_new_length("))
#expect(source.contains("condition1: Double, condition2: Double"))
#expect(source.contains("orCondition1: Double, orCondition2: Double"))
#expect(source.contains("public func toString("))
#expect(source.contains("public static func parse("))
#expect(!source.contains("public func free("))
#expect(!source.contains("public func unref("))
}
@Test("moved-to namespace functions render on their record")
func routesMovedToFunction() {
let record = Record(name: "MyRecord", cType: "MyRecord",
getTypeFunction: "my_record_get_type")
let moved = GlobalFunction(
name: "my_record_do_thing", cIdentifier: "my_record_do_thing",
returnValue: ReturnValue(type: .int32),
symbolInfo: SymbolInfo(movedTo: "MyRecord.do_thing"))
let namespace = Namespace(name: "Test", version: "1.0",
records: [record], functions: [moved])
let repository = Repository(namespaces: [namespace])
let analysis = MultiPackageAnalysis(
repositories: ["Test": repository],
directDependencies: ["Test": []], transitiveDependencies: ["Test": []],
implicitImports: ["Test": []], packageConfigs: [:])
let registry = TypeRegistry(repositories: ["Test": repository])
let module = planModules(analysis: analysis, registry: registry)["Test"]!
let source = renderModule(module).values.joined(separator: "\n")
#expect(source.contains("public static func doThing("))
#expect(!source.contains("public func myRecordDoThing("))
}
}

View file

@ -163,6 +163,61 @@ struct RendererCallableTests {
"g_take_expr(_instancePointer(_rawPointer(g_expr_ref(_instancePointer(expr.pointer)))))"))
#expect(!body.contains("g_object_ref"))
}
@Test("transfer-full boxed params copy the value before the call")
func transferFullBoxedParamCopiesValue() throws {
let boxed = Record(
name: "Box", cType: "GBox", getTypeFunction: "g_box_get_type",
methods: [
Method(name: "copy", cIdentifier: "g_box_copy",
parameters: [Parameter(name: "self", type: .pointer, isInstanceParameter: true)],
returnValue: ReturnValue(type: .typeRef("Box", namespace: "GObject"))),
releaseMethodForTest("free", "g_box_free"),
])
let voidBoxed = Record(
name: "VoidBox", cType: "GVoidBox", getTypeFunction: "g_void_box_get_type",
methods: [
releaseMethodForTest("ref", "g_void_box_ref"),
releaseMethodForTest("unref", "g_void_box_unref"),
])
let repository = Repository(namespaces: [
Namespace(name: "GObject", version: "2.0", records: [boxed, voidBoxed])
])
let registry = TypeRegistry(repositories: ["GObject": repository])
let context = MapContext(registry: registry, currentModule: "GObject", currentNamespace: "GObject")
let copied = GlobalFunction(
name: "take_box", cIdentifier: "g_take_box",
parameters: [Parameter(name: "box", type: .typeRef("Box", namespace: "GObject"),
transferOwnership: .full)],
returnValue: ReturnValue(type: .void))
guard case .success(let copiedPlan) = planFunction(copied, context: context) else {
Issue.record("expected transfer-full boxed parameter to plan")
return
}
let copiedSource = renderCallable(copiedPlan)
#expect(copiedSource.contains("g_box_copy(_instancePointer(box.pointer))"))
#expect(copiedSource.contains("_rawPointer(g_box_copy("))
let consumed = GlobalFunction(
name: "take_void_box", cIdentifier: "g_take_void_box",
parameters: [Parameter(name: "box", type: .typeRef("VoidBox", namespace: "GObject"),
transferOwnership: .full)],
returnValue: ReturnValue(type: .void))
guard case .success(let consumedPlan) = planFunction(consumed, context: context) else {
Issue.record("expected void-returning boxed ref parameter to plan")
return
}
let consumedSource = renderCallable(consumedPlan)
#expect(consumedSource.contains("g_void_box_ref(pointer)"))
#expect(!consumedSource.contains("_rawPointer(g_void_box_ref("))
}
/// Creates a parameterless record lifetime method for renderer fixtures.
private func releaseMethodForTest(_ name: String, _ cIdentifier: String) -> Method {
Method(name: name, cIdentifier: cIdentifier,
parameters: [Parameter(name: "self", type: .pointer, isInstanceParameter: true)])
}
// MARK: - C3: Out-param tuples
@ -363,7 +418,7 @@ struct RendererCallableTests {
// MARK: - E1: constructor signature deduplication
@Test("Two constructors with the same rendered init signature but different Swift names dedup to one, keeping the first")
@Test("Colliding constructor signatures are disambiguated by constructor name")
func constructorsWithIdenticalSignatureDedup() throws {
// `init(...)` never carries the constructor's Swift name (it always
// renders as bare `init`), so two GIR constructors mapping to
@ -397,16 +452,16 @@ struct RendererCallableTests {
currentModule: "GObject", currentNamespace: "GObject"
)
let (plan, skips) = planClass(klass, context: ctx)
#expect(plan.constructors.count == 1)
#expect(plan.constructors.count == 2)
#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")
#expect(plan.constructors[1].cIdentifier == "g_connection_new_for_address_finish")
#expect(plan.constructors[1].parameters[0].swiftName == "forAddressFinishRes")
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)
#expect(src.contains("convenience init(res: AsyncResult) throws"))
#expect(src.contains("convenience init(forAddressFinishRes: AsyncResult) throws"))
}
// MARK: - E1: pointer out-param zero-initializes to nil, not 0

View file

@ -238,6 +238,23 @@ struct TypeMapperTests {
#expect(mapping.marshalOut == .direct)
#expect(mapping.gvalue?.typeMacro == "G_TYPE_POINTER")
}
@Test("Native C99 bool maps directly to Swift Bool")
func nativeBoolMapping() throws {
let mapping = try map(.boolean, nullable: false, transfer: .none,
context: makeContext(), cType: "_Bool")
#expect(mapping.cSwiftType == "Bool")
#expect(mapping.marshalIn == .direct)
#expect(mapping.marshalOut == .direct)
}
@Test("Const pointer maps to UnsafeRawPointer?")
func constPointerMapping() throws {
let mapping = try map(.pointer, nullable: false, transfer: .none,
context: makeContext(), cType: "gconstpointer")
#expect(mapping.swiftType == "UnsafeRawPointer?")
#expect(mapping.cSwiftType == "UnsafeRawPointer?")
}
// MARK: - String with transfer ownership
@ -367,7 +384,7 @@ struct TypeMapperTests {
nullable: false, transfer: .none, context: ctx)
#expect(mapping.swiftType == "Value")
#expect(mapping.cSwiftType == "UnsafeMutableRawPointer?")
#expect(mapping.marshalIn == .boxedPointer)
#expect(mapping.marshalIn == .boxedPointer(consumingCopyFunction: nil, copyReturnsVoid: false))
#expect(mapping.marshalOut == .boxedWrap(copy: true, copyFunction: "g_value_copy")) // transfer != .full copy
#expect(mapping.gvalue?.typeMacro == "G_TYPE_BOXED")
}