Boxed-record methods and moved-to routing
This commit is contained in:
parent
14072c5df6
commit
18e14a5b67
53 changed files with 19320 additions and 5999 deletions
|
|
@ -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("))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue