From 5866ed57e9ea49138f5f8f50808d3d79079a0b91 Mon Sep 17 00:00:00 2001 From: Brendan Szymanski Date: Wed, 12 Aug 2026 02:17:23 -0400 Subject: [PATCH] Fix gint64/guint64 marshalling for macOS glib builds --- .../GObjectGeneratorCore/PlanRenderer.swift | 23 +++++++++++++++++++ Sources/GObjectGeneratorCore/TypeMapper.swift | 17 ++++++++++++-- .../TypeMapperTests.swift | 4 ++-- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/Sources/GObjectGeneratorCore/PlanRenderer.swift b/Sources/GObjectGeneratorCore/PlanRenderer.swift index 4b25101..9984faf 100644 --- a/Sources/GObjectGeneratorCore/PlanRenderer.swift +++ b/Sources/GObjectGeneratorCore/PlanRenderer.swift @@ -1177,6 +1177,17 @@ private func renderWrapperExpr(for p: ParameterPlan, rawName: String, ownerIsInt return "String(cString: \(rawName))" case .boolToGboolean: return "\(rawName) != 0" + case .numericCast: + // The raw C parameter is `cSwiftType` (e.g. the literal `gint64` + // typedef), which is not always the same nominal type as the + // public `swiftType` (`Int`) exposed on the closure. `numericCast` + // alone can't infer its target here (this becomes a standalone + // `let` binding, with no call-site context to infer from), so + // construct the public type directly instead — equivalent to + // `numericCast` for the always-non-optional scalar types this + // marshalling case covers. + let baseType = p.mapping.swiftType.hasSuffix("?") ? String(p.mapping.swiftType.dropLast()) : p.mapping.swiftType + return "\(baseType)(\(rawName))" default: return rawName } @@ -2088,6 +2099,13 @@ private func gvalueGetterBody(swiftType: String, girName: String, resultExpr = hasCopyFunction ? "\(swiftType)(retaining: g_value_get_boxed(&gvalue))" : "\(swiftType)(takingOwnership: g_value_get_boxed(&gvalue))" + } else if suffix == "int64" || suffix == "uint64" { + // `g_value_get_int64`/`_uint64` return the literal `gint64`/`guint64` + // typedef, which glib defines as `long long` on some platforms and + // plain `long` on others (see `TypeMapper.int64Mapping`) — bridge + // through `numericCast` so this compiles regardless of which one + // the Clang importer resolved it to. + resultExpr = "numericCast(g_value_get_\(suffix)(&gvalue))" } else { resultExpr = "g_value_get_\(suffix)(&gvalue)" } @@ -2127,6 +2145,11 @@ private func gvalueSetterBody(swiftType: String, girName: String, : "g_value_set_object(&gvalue, newValue.pointer)" } else if suffix == "boxed" { setCall = "g_value_set_boxed(&gvalue, newValue.pointer)" + } else if suffix == "int64" || suffix == "uint64" { + // See the getter's matching branch: `g_value_set_int64`/`_uint64` + // take the literal `gint64`/`guint64` typedef, which is not always + // the same nominal Swift type as the public `Int`/`UInt` property. + setCall = "g_value_set_\(suffix)(&gvalue, numericCast(newValue))" } else { setCall = "g_value_set_\(suffix)(&gvalue, newValue)" } diff --git a/Sources/GObjectGeneratorCore/TypeMapper.swift b/Sources/GObjectGeneratorCore/TypeMapper.swift index e951796..c023498 100644 --- a/Sources/GObjectGeneratorCore/TypeMapper.swift +++ b/Sources/GObjectGeneratorCore/TypeMapper.swift @@ -594,7 +594,16 @@ extension Mapping { gvalue: GValueOps(typeMacro: "G_TYPE_INT", getterSuffix: "int", setterSuffix: "int")) static let int64Mapping = Mapping( - swiftType: "Int", cSwiftType: "Int", // gint64 → Int on LP64, Int64 on LLP64 + // `cSwiftType` is the literal glib typedef name, not a guessed Swift + // equivalent: glib defines `gint64` unconditionally as `long long` + // on some builds (e.g. Homebrew's glib on macOS) and as plain `long` + // on others (e.g. glibc-targeting Linux builds), so it imports into + // Swift as `Int64` on one and `Int` on the other. Using the raw + // typedef name lets the Clang importer resolve the exact pointee + // type for out-param locals (`var out0: gint64 = 0; &out0`) on + // every platform; `numericCast` still bridges it to the public + // `Int` at the Swift boundary. + swiftType: "Int", cSwiftType: "gint64", marshalIn: .numericCast(targetType: "Int"), marshalOut: .numericCast(fromType: "Int"), gvalue: GValueOps(typeMacro: "G_TYPE_INT64", getterSuffix: "int64", setterSuffix: "int64")) @@ -610,7 +619,11 @@ extension Mapping { gvalue: GValueOps(typeMacro: "G_TYPE_UINT", getterSuffix: "uint", setterSuffix: "uint")) static let uint64Mapping = Mapping( - swiftType: "UInt", cSwiftType: "UInt", // guint64 → UInt on LP64, UInt64 on LLP64 + // See `int64Mapping`: `cSwiftType` is the literal `guint64` typedef + // name so out-param locals get the exact platform pointee type + // regardless of whether glib defines it as `unsigned long long` or + // `unsigned long`. + swiftType: "UInt", cSwiftType: "guint64", marshalIn: .numericCast(targetType: "UInt"), marshalOut: .numericCast(fromType: "UInt"), gvalue: GValueOps(typeMacro: "G_TYPE_UINT64", getterSuffix: "uint64", setterSuffix: "uint64")) diff --git a/Tests/GObjectGeneratorCoreTests/TypeMapperTests.swift b/Tests/GObjectGeneratorCoreTests/TypeMapperTests.swift index 9ed6092..56ea289 100644 --- a/Tests/GObjectGeneratorCoreTests/TypeMapperTests.swift +++ b/Tests/GObjectGeneratorCoreTests/TypeMapperTests.swift @@ -146,7 +146,7 @@ struct TypeMapperTests { PrimitiveCase(type: .int8, swiftType: "Int8", cSwiftType: "Int8", gvalueTypeMacro: nil, gvalueGetter: nil), PrimitiveCase(type: .int16, swiftType: "Int16", cSwiftType: "Int16", gvalueTypeMacro: nil, gvalueGetter: nil), PrimitiveCase(type: .int32, swiftType: "Int32", cSwiftType: "Int32", gvalueTypeMacro: "G_TYPE_INT", gvalueGetter: "int"), - PrimitiveCase(type: .int64, swiftType: "Int", cSwiftType: "Int", gvalueTypeMacro: "G_TYPE_INT64", gvalueGetter: "int64", + PrimitiveCase(type: .int64, swiftType: "Int", cSwiftType: "gint64", gvalueTypeMacro: "G_TYPE_INT64", gvalueGetter: "int64", marshalIn: .numericCast(targetType: "Int"), marshalOut: .numericCast(fromType: "Int")), ]) func signedIntegers(_ tc: PrimitiveCase) throws { @@ -167,7 +167,7 @@ struct TypeMapperTests { PrimitiveCase(type: .uint8, swiftType: "UInt8", cSwiftType: "UInt8", gvalueTypeMacro: nil, gvalueGetter: nil), PrimitiveCase(type: .uint16, swiftType: "UInt16", cSwiftType: "UInt16", gvalueTypeMacro: nil, gvalueGetter: nil), PrimitiveCase(type: .uint32, swiftType: "UInt32", cSwiftType: "UInt32", gvalueTypeMacro: "G_TYPE_UINT", gvalueGetter: "uint"), - PrimitiveCase(type: .uint64, swiftType: "UInt", cSwiftType: "UInt", gvalueTypeMacro: "G_TYPE_UINT64", gvalueGetter: "uint64", + PrimitiveCase(type: .uint64, swiftType: "UInt", cSwiftType: "guint64", gvalueTypeMacro: "G_TYPE_UINT64", gvalueGetter: "uint64", marshalIn: .numericCast(targetType: "UInt"), marshalOut: .numericCast(fromType: "UInt")), ]) func unsignedIntegers(_ tc: PrimitiveCase) throws {