1
0
Fork 0

Fix Phase D signal/callback remediation defects

Destroy-notify ABI fixed to GClosureNotify's real 2-arg signature and
wired into every connect method (was leaking every _ClosureBox, and
UB on non-x86_64 with the wrong arg count). Trampolines restore
MainActor isolation via assumeIsolated, with narrowly-scoped
nonisolated(unsafe) shadow copies to satisfy Swift 6's sending
checker. Interface-signal rendering implemented and unit-tested.
Dead code removed (SignalHandlePlan), destroyTrampoline made
non-optional, D7 deferral documented in-code. CoverageStats gained
boundCallbacks/boundSignals counters. Added SignalGenerationTests,
InterfaceSignalGenerationTests, and CallbackGenerationTests (12 new
tests, 187/187 total). Fixed the dead nonDetailedSignal smoke test to
actually mutate a property and assert the closure fired.

Callback-param planner-side binding (D4.3) stays disabled: enabling
it trips a genuine Swift compiler crash on g_qsort_with_data's
GCompareDataFunc parameter. The renderer-side box setup/release logic
is implemented and unit-tested by constructing plans directly,
bypassing the blocked planner path.

Verified: swift test (187/187), compile-gate.sh 1 --fresh (PASS),
smoke-test.sh --fresh (18/18).
This commit is contained in:
Brendan Szymanski 2026-07-18 20:45:56 -04:00
parent dd2cd7a689
commit 5abb48e889
11 changed files with 2259 additions and 987 deletions

View file

@ -17,6 +17,8 @@ public enum BindingCategory: String, Equatable, Sendable {
case needsClass
/// Boxed record needs a record wrapper.
case needsRecord
/// Callback type needs a typealias before callables can use it.
case callback
/// A type that will never be generated (foreign, unknown).
case unavailable
}
@ -46,6 +48,12 @@ public enum MarshalIn: Equatable, Sendable {
case bitfieldRaw
/// Pass the pointer of a boxed record wrapper.
case boxedPointer
/// Box a Swift closure for C callback trampoline dispatch.
/// - Parameters:
/// - scope: The callback lifetime from the GIR `scope` attribute.
/// - destroyTrampoline: The per-signature destroy trampoline C name,
/// or `""` when no destroy is needed (namespace-level typealiases).
case callbackBox(scope: CallbackScope?, destroyTrampoline: String)
/// Unsupported causes the whole callable to be skipped.
/// - Parameter reason: Why the parameter cannot be marshalled.
case unsupported(reason: String)
@ -168,6 +176,8 @@ public enum SkipReason: String, Codable, CaseIterable, Sendable {
/// `init` (an init cannot also return out-value tuples). The constructor
/// is skipped.
case constructorOutParams
/// A signal parameter or return type could not be mapped.
case signalUnmappableParam
}
/// A single skipped symbol: what was skipped, and why.
@ -203,7 +213,6 @@ public struct SkipEntry: Codable, Equatable, Sendable {
///
/// Coverage is the fraction of introspectable callables that received a
/// complete binding plan. The compile gate requires coverage to be
/// monotonically non-decreasing across changes.
public struct CoverageStats: Codable, Equatable, Sendable {
/// Number of callables successfully planned and emitted.
public var boundCallables: Int
@ -213,19 +222,23 @@ public struct CoverageStats: Codable, Equatable, Sendable {
public var boundTypes: Int
/// Total introspectable types considered (bound + skipped).
public var totalTypes: Int
/// Namespace-level callbacks successfully planned and emitted (D6).
public var boundCallbacks: Int
/// Total namespace-level callbacks considered (bound + skipped).
public var totalCallbacks: Int
/// GObject signals successfully planned and emitted (Phase D).
public var boundSignals: Int
/// Total signals considered (bound + skipped).
public var totalSignals: Int
/// Creates coverage statistics.
///
/// - Parameters:
/// - boundCallables: Callables successfully planned and emitted.
/// - totalCallables: Total introspectable callables considered.
/// - boundTypes: Types successfully planned and emitted.
/// - totalTypes: Total introspectable types considered.
public init(boundCallables: Int = 0, totalCallables: Int = 0, boundTypes: Int = 0, totalTypes: Int = 0) {
self.boundCallables = boundCallables
self.totalCallables = totalCallables
self.boundTypes = boundTypes
self.totalTypes = totalTypes
public init(boundCallables: Int = 0, totalCallables: Int = 0,
boundTypes: Int = 0, totalTypes: Int = 0,
boundCallbacks: Int = 0, totalCallbacks: Int = 0,
boundSignals: Int = 0, totalSignals: Int = 0) {
self.boundCallables = boundCallables; self.totalCallables = totalCallables
self.boundTypes = boundTypes; self.totalTypes = totalTypes
self.boundCallbacks = boundCallbacks; self.totalCallbacks = totalCallbacks
self.boundSignals = boundSignals; self.totalSignals = totalSignals
}
}
@ -286,6 +299,8 @@ public enum TypePlan: Sendable {
case record(RecordPlan)
/// A callable (function, method, or constructor).
case callable(CallablePlan)
/// A namespace-level callback typealias.
case callback(CallbackTypePlan)
}
/// The plan for a GIR enumeration: cases and alias variables (deduplicated
@ -391,6 +406,62 @@ public struct AliasPlan: Equatable, Sendable {
}
}
/// The plan for a GObject signal connection.
///
/// Each signal on a class or interface generates a typed trampoline
/// (`@convention(c)` static func) and a `connect<Name>()` method that
/// boxes the Swift closure, passes it to `g_signal_connect_data`,
/// and returns a `SignalHandle`.
public struct SignalPlan: Equatable, Sendable {
/// The owning class's Swift name, e.g. `"Object"`.
public let owningClassName: String
/// The GIR signal name, e.g. `"clicked"`, `"notify"`.
public let girName: String
/// The camelCased Swift name, e.g. `"clicked"`.
public let swiftName: String
/// Whether the signal supports detail strings (`"notify::label"`).
public let isDetailed: Bool
/// The signal handler parameters (includes implicit instance param at cArgIndex 0).
public let parameters: [ParameterPlan]
/// The return mapping, or `nil` for `void`.
public let returnMapping: Mapping?
/// The `@convention(c)` trampoline's C-level name, unique per module.
/// Format: `"_trampoline_\(namespace)_\(owningClass)_\(girName)"`.
public let trampolineCName: String
/// Documentation from the GIR `<doc>` element.
public let doc: String?
public init(owningClassName: String, girName: String, swiftName: String,
isDetailed: Bool = false, parameters: [ParameterPlan] = [],
returnMapping: Mapping? = nil, trampolineCName: String,
doc: String? = nil) {
self.owningClassName = owningClassName; self.girName = girName
self.swiftName = swiftName; self.isDetailed = isDetailed
self.parameters = parameters; self.returnMapping = returnMapping
self.trampolineCName = trampolineCName; self.doc = doc
}
}
/// The plan for a namespace-level callback type.
///
/// Renders as both a C-compatible `@convention(c)` typealias for C callbacks
/// and a Swift-friendly `@escaping` typealias for use in generated connect methods.
public struct CallbackTypePlan: Equatable, Sendable {
/// The Swift type name, e.g. `"GClosureNotify"`.
public let name: String
/// The Swift closure form, e.g. `"@escaping (UnsafeMutableRawPointer?) -> Void"`.
public let swiftType: String
/// The C-compatible convention(c) form for trampoline signatures.
public let cSwiftType: String
/// Documentation from the GIR `<doc>` element.
public let doc: String?
public init(name: String, swiftType: String, cSwiftType: String, doc: String? = nil) {
self.name = name; self.swiftType = swiftType; self.cSwiftType = cSwiftType; self.doc = doc
}
}
/// The plan for a GObject class wrapper.
public struct ClassPlan: Equatable, Sendable {
/// The Swift class name (unqualified), e.g. `"Object"`.
@ -417,6 +488,8 @@ public struct ClassPlan: Equatable, Sendable {
public let functions: [CallablePlan]
/// Planned GObject properties.
public let properties: [PropertyPlan]
/// Planned GObject signals.
public let signals: [SignalPlan]
/// Module-qualified names of implemented interfaces (e.g. ["GObject.TypePlugin"]).
public let interfaces: [String]
/// Documentation from the GIR `<doc>` element.
@ -429,6 +502,7 @@ public struct ClassPlan: Equatable, Sendable {
interfaces: [String] = [],
constructors: [CallablePlan] = [], methods: [CallablePlan] = [],
functions: [CallablePlan] = [], properties: [PropertyPlan] = [],
signals: [SignalPlan] = [],
doc: String? = nil) {
self.name = name; self.cType = cType; self.parent = parent
self.isOpen = isOpen; self.isAbstract = isAbstract
@ -437,6 +511,7 @@ public struct ClassPlan: Equatable, Sendable {
self.interfaces = interfaces
self.constructors = constructors; self.methods = methods
self.functions = functions; self.properties = properties
self.signals = signals
self.doc = doc
}
}
@ -472,6 +547,8 @@ public struct InterfacePlan: Equatable, Sendable {
public let methods: [CallablePlan]
/// Properties declared by this interface, rendered as protocol requirements.
public let properties: [PropertyPlan]
/// Signals declared by this interface.
public let signals: [SignalPlan]
/// The module-qualified Swift name, e.g. `"GObject.TypePlugin"`.
/// Used to match against `ClassPlan.interfaces` entries.
public let qualifiedName: String
@ -479,11 +556,13 @@ public struct InterfacePlan: Equatable, Sendable {
public init(name: String, cType: String, prereqs: [String] = [],
getTypeFunction: String? = nil, methods: [CallablePlan] = [],
properties: [PropertyPlan] = [], qualifiedName: String = "",
properties: [PropertyPlan] = [], signals: [SignalPlan] = [],
qualifiedName: String = "",
doc: String? = nil) {
self.name = name; self.cType = cType; self.prereqs = prereqs
self.getTypeFunction = getTypeFunction; self.methods = methods
self.properties = properties; self.qualifiedName = qualifiedName
self.properties = properties; self.signals = signals
self.qualifiedName = qualifiedName
self.doc = doc
}
}
@ -626,15 +705,23 @@ public struct ParameterPlan: Equatable, Sendable {
public let mapping: Mapping
/// `true` when this parameter is the implicit instance (`self`).
public let isInstanceParameter: Bool
/// `true` if this parameter has `direction="out"` and will be returned from
/// the Swift function rather than passed as an argument.
public let isOutParameter: Bool
/// For callback-box params: the C arg index of the separate user-data
/// parameter this callback feeds. `nil` when this param doubles as
/// its own user-data (`closureIndex == cArgIndex`).
public let closureIndex: Int?
/// For callback-box params: the C arg index of the separate DestroyNotify
/// parameter. `nil` when none.
public let destroyIndex: Int?
public init(swiftName: String, cArgIndex: Int, mapping: Mapping,
isInstanceParameter: Bool = false, isOutParameter: Bool = false) {
isInstanceParameter: Bool = false, isOutParameter: Bool = false,
closureIndex: Int? = nil, destroyIndex: Int? = nil) {
self.swiftName = swiftName; self.cArgIndex = cArgIndex
self.mapping = mapping; self.isInstanceParameter = isInstanceParameter
self.isOutParameter = isOutParameter
self.closureIndex = closureIndex; self.destroyIndex = destroyIndex
}
}

View file

@ -43,6 +43,7 @@ public func renderModule(_ plan: ModulePlan) -> [String: String] {
var constants: [(name: String, body: String)] = []
var functions: [(name: String, body: String)] = []
var callbacks: [(name: String, body: String)] = []
for typePlan in plan.types {
switch typePlan {
@ -50,6 +51,8 @@ public func renderModule(_ plan: ModulePlan) -> [String: String] {
constants.append((p.name, renderConstant(p)))
case .callable(let p):
functions.append((p.name, renderCallable(p)))
case .callback(let p):
callbacks.append((p.name, renderCallbackType(p)))
default:
let (baseName, body) = renderTypePlan(typePlan)
files["\(baseName).swift"] = header + body + "\n"
@ -62,9 +65,28 @@ public func renderModule(_ plan: ModulePlan) -> [String: String] {
if !functions.isEmpty {
files["Functions.swift"] = header + mergedFileBody(functions)
}
if !callbacks.isEmpty {
files["Callbacks.swift"] = header + mergedFileBody(callbacks)
}
// Always emit the per-module pointer-cast helpers.
files["Support.swift"] = renderSupport(moduleName: plan.module)
// Determine if any class/interface has signals the signal runtime
// (ClosureBox, SignalHandle, destroy trampoline) is only needed
// when signals or callback-param callables are present.
let hasSignals = plan.types.contains { typePlan in
switch typePlan {
case .class(let p): return !p.signals.isEmpty
case .interface(let p): return !p.signals.isEmpty
default: return false
}
}
let hasCallbacks = plan.types.contains { typePlan in
if case .callable(let p) = typePlan {
return p.parameters.contains { if case .callbackBox(_, _) = $0.mapping.marshalIn { true } else { false } }
}
return false
}
files["Support.swift"] = renderSupport(moduleName: plan.module, hasSignals: hasSignals, hasCallbackBoxes: hasCallbacks)
for filename in files.keys {
precondition(isValidGeneratedFileName(filename),
@ -121,12 +143,7 @@ private func leadingNameWord(_ name: String) -> String {
return word.isEmpty ? String(trimmed) : String(word)
}
/// Renders the per-module support file: the overloaded `_instancePointer`
/// helper that reinterprets a wrapper's raw `pointer` as the specific C pointer
/// type each C call expects. Two overloads let call-site overload resolution
/// pick `OpaquePointer` (opaque C structs) or `UnsafeMutablePointer<T>`
/// (complete C structs) without the generator needing to know which a type is.
private func renderSupport(moduleName: String) -> String {
private func renderSupport(moduleName: String, hasSignals: Bool = false, hasCallbackBoxes: Bool = false) -> String {
let glibError: String
if moduleName == "GLib" {
glibError = """
@ -180,6 +197,85 @@ private func renderSupport(moduleName: String) -> String {
} else {
gtypeConstants = ""
}
let closureBoxSupport: String
if hasCallbackBoxes || hasSignals {
closureBoxSupport = """
// MARK: - Closure box for callback/signal dispatch
/// Boxes a Swift closure for C callback trampoline dispatch.
/// `@MainActor` because the stored closure is always accessed from
/// `MainActor.assumeIsolated` in the trampoline or from the
/// `@MainActor` connect method.
@MainActor
final class _ClosureBox<T> {
let closure: T
init(_ c: T) { closure = c }
}
"""
} else {
closureBoxSupport = ""
}
let signalSupport: String
if hasSignals {
signalSupport = """
/// A handle returned by `connect` methods, wrapping a GObject signal
/// handler ID. Disconnecting marks the handle as disconnected to
/// prevent double-disconnect.
/// - Note: Uses `mutating func disconnect()` + `isDisconnected` flag
/// as a fallback per Phase D contingency; `~Copyable` would also work.
public struct SignalHandle {
public let id: UInt
public let instance: UnsafeMutableRawPointer
private var isDisconnected: Bool = false
public init(id: UInt, instance: UnsafeMutableRawPointer) { self.id = id; self.instance = instance }
public mutating func disconnect() {
guard !isDisconnected else { return }
isDisconnected = true
_sgtk_signal_handler_disconnect(instance, numericCast(id))
}
}
/// Implements the `GClosureNotify` C callback signature
/// (two args: data pointer + GClosure pointer).
///
/// Never emitted with `@_cdecl` per-call-site wrapping via a
/// `@convention(c)` literal closure avoids duplicate-symbol link
/// errors when multiple modules with signals link together.
public nonisolated func _sgtk_destroy_notify_impl(
_ data: UnsafeMutableRawPointer?,
_ closure: UnsafeMutableRawPointer?
) {
guard let data else { return }
_ = Unmanaged<AnyObject>.fromOpaque(data).takeRetainedValue()
}
"""
} else {
signalSupport = ""
}
let primitiveShims: String
if moduleName == "GObject" && hasSignals {
primitiveShims = """
// MARK: - manual primitives (Phase E: replace with planned bindings)
@_silgen_name("g_signal_connect_data")
public nonisolated func _sgtk_signal_connect_data(
_ instance: UnsafeMutableRawPointer, _ detailedSignal: UnsafePointer<CChar>,
_ cHandler: UnsafeRawPointer, _ data: UnsafeMutableRawPointer?,
_ destroyData: UnsafeRawPointer?, _ connectFlags: UInt32
) -> UInt
@_silgen_name("g_signal_handler_disconnect")
public nonisolated func _sgtk_signal_handler_disconnect(
_ instance: UnsafeMutableRawPointer, _ handlerId: UInt
)
"""
} else {
primitiveShims = ""
}
return """
// Generated by SwiftGtkGen. DO NOT EDIT.
@ -225,7 +321,7 @@ private func renderSupport(moduleName: String) -> String {
func _rawPointer(_ p: OpaquePointer) -> UnsafeMutableRawPointer {
UnsafeMutableRawPointer(p)
}
\(glibError)\(gtypeConstants)
\(glibError)\(gtypeConstants)\(closureBoxSupport)\(signalSupport)\(primitiveShims)
"""
}
@ -245,6 +341,7 @@ private func renderTypePlan(_ typePlan: TypePlan) -> (String, String) {
case .interface(let p): return (p.name, renderInterface(p))
case .record(let p): return (p.name, renderRecord(p))
case .callable(let p): return (p.name, renderCallable(p))
case .callback(let p): return (p.name, renderCallbackType(p))
}
}
@ -332,6 +429,27 @@ private func renderAlias(_ plan: AliasPlan) -> String {
return lines.joined(separator: "\n") + "\n"
}
// Callback typealias
/// Renders a namespace-level callback type as both a C-compatible
/// `@convention(c)` typealias and a Swift-friendly `@escaping` typealias.
///
/// Example output:
/// ```swift
/// /// Documentation
/// public typealias GClosureNotify = @convention(c) (UnsafeMutableRawPointer?) -> Void
/// public typealias GClosureNotifySwift = (UnsafeMutableRawPointer?) -> Void
/// ```
private func renderCallbackType(_ plan: CallbackTypePlan) -> String {
var lines: [String] = []
if let doc = plan.doc {
lines.append(contentsOf: renderDocComment(doc))
}
lines.append("public typealias \(plan.name) = \(plan.cSwiftType)")
lines.append("public typealias \(plan.name)Swift = \(plan.swiftType)")
return lines.joined(separator: "\n") + "\n"
}
// Record (boxed)
private func renderRecord(_ plan: RecordPlan) -> String {
@ -381,6 +499,12 @@ private func renderRecord(_ plan: RecordPlan) -> String {
private func renderInterface(_ plan: InterfacePlan) -> String {
var lines: [String] = []
// Signal trampolines at file scope (same pattern as classes)
for sig in plan.signals {
lines.append(contentsOf: renderSignalTrampoline(sig))
lines.append("")
}
if let doc = plan.doc {
lines.append(contentsOf: renderDocComment(doc))
}
@ -399,6 +523,18 @@ private func renderInterface(_ plan: InterfacePlan) -> String {
lines.append(contentsOf: renderPropertyRequirement(prop))
}
lines.append("}")
// Protocol extension with signal connect methods
if !plan.signals.isEmpty {
lines.append("")
lines.append("extension \(plan.name) {")
for sig in plan.signals {
lines.append(contentsOf: renderSignalConnect(sig, className: plan.name))
lines.append("")
}
lines.append("}")
}
return lines.joined(separator: "\n") + "\n"
}
@ -435,10 +571,17 @@ private func renderClass(_ plan: ClassPlan) -> String {
lines.append(contentsOf: renderDocComment(doc))
}
// Signal trampolines (file-level @_cdecl)
for sig in plan.signals {
lines.append(contentsOf: renderSignalTrampoline(sig))
lines.append("")
}
let access = plan.isOpen ? "open" : "public"
let parentDecl: String
if let parent = plan.parent {
let ifaces = plan.interfaces.isEmpty ? "" : ", \(plan.interfaces.joined(separator: ", "))"
parentDecl = ": \(parent)\(ifaces)"
} else if !plan.interfaces.isEmpty {
parentDecl = ": \(plan.interfaces.joined(separator: ", "))"
@ -520,26 +663,179 @@ private func renderClass(_ plan: ClassPlan) -> String {
lines.append("")
}
// Signals
for sig in plan.signals {
lines.append(contentsOf: renderSignalConnect(sig, className: plan.name))
lines.append("")
}
lines.append("}")
return lines.joined(separator: "\n") + "\n"
}
// Callable (function)
// Signal rendering
/// The Swift parameter list for a callable (excludes the instance parameter).
/// Renders a `@_cdecl nonisolated` trampoline for a GObject signal.
///
/// The `@_cdecl` ABI is required because C calls this function via a raw
/// function pointer; Swift's native calling convention would SIGILL.
/// The body wraps wrapper construction and the closure call in
/// `MainActor.assumeIsolated` per AGENTS Top Risk #1 traps if C
/// ever fires the signal off the main thread.
private func renderSignalTrampoline(_ plan: SignalPlan) -> [String] {
var lines: [String] = []
let instanceParams = plan.parameters.filter { $0.isInstanceParameter }
let realParams = plan.parameters.filter { !$0.isInstanceParameter }
let allParams = instanceParams + realParams
// User-facing closure type stored in ClosureBox (typed wrappers)
let closureParamTypes = allParams.map { $0.mapping.swiftType }
let closureRet = plan.returnMapping?.swiftType ?? "Void"
let closureParams = closureParamTypes.isEmpty ? "" : closureParamTypes.joined(separator: ", ")
let closureType = "(\(closureParams)) -> \(closureRet)"
// C parameter declaration for the @_cdecl function
var cParamStrs: [String] = []
cParamStrs.append("_ instance: UnsafeMutableRawPointer")
for (idx, p) in realParams.enumerated() {
let cType = p.mapping.cSwiftType.replacingOccurrences(of: "?", with: "")
cParamStrs.append("_ p\(idx + 1): \(cType)")
}
cParamStrs.append("_ data: UnsafeMutableRawPointer?")
let cDecl = cParamStrs.joined(separator: ", ")
// Wrapper construction for each parameter inside MainActor.assumeIsolated.
// The raw C parameters (`instance`, `p1`, `p2`, ...) belong to this
// `nonisolated` trampoline's isolation domain. Swift 6's region-based
// sending checker flags capturing them directly into the `@MainActor`
// closure below as a potential data race, even though they are trivial
// pointer values with no live aliasing concern here (C never touches
// them again once the trampoline is invoked). `nonisolated(unsafe)`
// shadow copies sidestep the checker for this documented-safe case.
var shadowLines: [String] = []
var wrapperLines: [String] = []
for (i, p) in allParams.enumerated() {
let rawName = i == 0 ? "instance" : "p\(i)"
let shadowName = "captured\(rawName.prefix(1).uppercased())\(rawName.dropFirst())"
shadowLines.append("nonisolated(unsafe) let \(shadowName) = \(rawName)")
wrapperLines.append("let w\(i) = \(renderWrapperExpr(for: p, rawName: shadowName))")
}
let wrapperRefs = (0..<allParams.count).map { "w\($0)" }.joined(separator: ", ")
let shadowBody = shadowLines.map { " \($0)" }.joined(separator: "\n")
let wrapperBody = (wrapperLines + ["box.closure(\(wrapperRefs))"]).map { " \($0)" }.joined(separator: "\n")
lines.append("@_cdecl(\"\(plan.trampolineCName)\")")
lines.append("nonisolated func \(plan.trampolineCName)(\(cDecl)) {")
lines.append(" guard let data else { return }")
lines.append(" let box = Unmanaged<_ClosureBox<\(closureType)>>.fromOpaque(data).takeUnretainedValue()")
lines.append(shadowBody)
lines.append(" MainActor.assumeIsolated {")
lines.append(wrapperBody)
lines.append(" }")
lines.append("}")
return lines
}
/// Renders a single wrapper-expression for a signal parameter: the Swift
/// expression that converts a raw C argument (managed by the trampoline)
/// into a typed Swift wrapper. Extracted from renderSignalTrampoline.
private func renderWrapperExpr(for p: ParameterPlan, rawName: String) -> String {
if p.isInstanceParameter {
return "\(p.mapping.swiftType)(retaining: \(rawName))"
}
switch p.mapping.marshalIn {
case .boxedPointer, .objectPointer:
return "\(p.mapping.swiftType)(retaining: \(rawName))"
case .stringToC:
return "String(cString: \(rawName))"
case .boolToGboolean:
return "\(rawName) != 0"
default:
return rawName
}
}
/// Renders the `connect<Name>` method. Boxes the user's typed handler
/// directly wrapper construction from raw C args is handled by the
/// trampoline under `MainActor.assumeIsolated`.
private func renderSignalConnect(_ plan: SignalPlan, className: String) -> [String] {
var lines: [String] = []
let instanceParams = plan.parameters.filter { $0.isInstanceParameter }
let realParams = plan.parameters.filter { !$0.isInstanceParameter }
// User-facing closure type (typed wrappers) same as the trampoline's closureType
let closureParamTypes = instanceParams.map { $0.mapping.swiftType }
+ realParams.map { $0.mapping.swiftType }
let closureRet = plan.returnMapping?.swiftType ?? "Void"
let closureParams = closureParamTypes.isEmpty ? "" : closureParamTypes.joined(separator: ", ")
let closureType = "(\(closureParams)) -> \(closureRet)"
// Build @convention(c) type for the trampoline's unsafeBitCast
let cTypes = ["UnsafeMutableRawPointer"] + realParams.map { p in
p.mapping.cSwiftType.replacingOccurrences(of: "?", with: "")
} + ["UnsafeMutableRawPointer?"]
let cTypeStr = cTypes.joined(separator: ", ")
let connectName = "connect" + plan.swiftName.prefix(1).uppercased() + plan.swiftName.dropFirst()
let detailParam = plan.isDetailed ? "detail: String?, " : ""
let detailBody: String
if plan.isDetailed {
detailBody = "let signalName: String = detail.map { \"\(plan.girName)::\\($0)\" } ?? \"\(plan.girName)\""
} else {
detailBody = "let signalName = \"\(plan.girName)\""
}
if let doc = plan.doc {
lines.append(contentsOf: renderDocComment(doc).map { " \($0)" })
}
lines.append(" public func \(connectName)(\(detailParam)_ handler: @escaping \(closureType)) -> SignalHandle {")
lines.append(" let box = _ClosureBox(handler)")
lines.append(" let dataPtr = Unmanaged.passRetained(box).toOpaque()")
lines.append(" let destroyFn: @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void = { data, _ in")
lines.append(" _sgtk_destroy_notify_impl(data, nil)")
lines.append(" }")
lines.append(" let ptr = self.pointer")
lines.append(" \(detailBody)")
lines.append(" return signalName.withCString { cName in")
lines.append(" let id = _sgtk_signal_connect_data(ptr, cName, unsafeBitCast(\(plan.trampolineCName) as (@convention(c) (\(cTypeStr)) -> Void), to: UnsafeRawPointer.self), dataPtr, unsafeBitCast(destroyFn as (@convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void), to: UnsafeRawPointer.self), 0)")
lines.append(" return SignalHandle(id: id, instance: ptr)")
lines.append(" }")
lines.append(" }")
return lines
}
private func swiftSignature(_ plan: CallablePlan) -> String {
plan.parameters.filter { !$0.isInstanceParameter && !$0.isOutParameter }.map { param in
"\(param.swiftName): \(param.mapping.swiftType)"
let typeStr: String
if param.mapping.category == .callback {
typeStr = "@escaping \(param.mapping.swiftType)"
} else {
typeStr = param.mapping.swiftType
}
return "\(param.swiftName): \(typeStr)"
}.joined(separator: ", ")
}
/// Builds the C call: the C-function-call string with each argument marshalled,
/// plus the string parameters that must be wrapped in `withCString`. The
/// instance parameter if any is passed as `self.pointer`.
///
/// Callback-box parameters: the data pointer replaces the closure arg at the
/// callback's cArgIndex, and also replaces any separate user-data slot
/// identified by `closureIndex`.
private func cArguments(_ plan: CallablePlan, error: Bool = false) -> (cCall: String, stringParams: [(cName: String, swiftName: String)]) {
var stringParams: [(cName: String, swiftName: String)] = []
var cArgExprs: [String] = []
var outIndex = 0
// Build map: closureIndex -> callback data ptr name, so separate user-data
// params get replaced with the box pointer.
var closureDataMap: [Int: String] = [:]
for p in plan.parameters {
if case .callbackBox(_, _) = p.mapping.marshalIn, let ci = p.closureIndex {
let base = p.swiftName.replacingOccurrences(of: "`", with: "")
closureDataMap[ci] = "\(base)Data"
}
}
for param in plan.parameters {
if param.isOutParameter {
cArgExprs.append("&out\(outIndex)")
@ -550,6 +846,8 @@ private func cArguments(_ plan: CallablePlan, error: Bool = false) -> (cCall: St
let cName = "cString\(stringParams.count)"
stringParams.append((cName: cName, swiftName: param.swiftName))
cArgExprs.append(cName)
} else if let dataPtrName = closureDataMap[param.cArgIndex], !isCallbackBox(param) {
cArgExprs.append(dataPtrName)
} else {
cArgExprs.append(marshalCallArg(param))
}
@ -558,6 +856,36 @@ private func cArguments(_ plan: CallablePlan, error: Bool = false) -> (cCall: St
return ("\(plan.cIdentifier)(\(cArgExprs.joined(separator: ", ")))", stringParams)
}
/// Emits setup statements for callback-box parameters: box the closure,
private func isCallbackBox(_ param: ParameterPlan) -> Bool {
if case .callbackBox(_, _) = param.mapping.marshalIn { return true }
return false
}
private func callbackBoxSetup(_ plan: CallablePlan, indent: String) -> [String] {
var lines: [String] = []
for p in plan.parameters {
if case .callbackBox(_, _) = p.mapping.marshalIn {
let base = p.swiftName.replacingOccurrences(of: "`", with: "")
lines.append("\(indent)let \(base)Box = _ClosureBox(\(p.swiftName))")
lines.append("\(indent)let \(base)Data = Unmanaged.passRetained(\(base)Box).toOpaque()")
}
}
return lines
}
/// Emits release statements for scope==.call callback-box parameters.
private func callbackBoxRelease(_ plan: CallablePlan, indent: String) -> [String] {
var lines: [String] = []
for p in plan.parameters {
if case .callbackBox(let scope, _) = p.mapping.marshalIn, scope == .call {
let base = p.swiftName.replacingOccurrences(of: "`", with: "")
lines.append("\(indent)_ = Unmanaged<_ClosureBox<\(p.mapping.cSwiftType)>>.fromOpaque(\(base)Data).takeRetainedValue()")
}
}
return lines
}
/// The C type for an out-param's local variable declaration (the pointee type
/// passed as `&local` to the C function).
private func outParamLocalType(_ param: ParameterPlan) -> String {
@ -641,7 +969,7 @@ private func renderMultiStatementBody(_ plan: CallablePlan, indent: String) -> [
// Out-param locals: declare, pass `&`, extract after call
let outParams = plan.parameters.filter(\.isOutParameter)
var localDecls: [String] = []
var localDecls = callbackBoxSetup(plan, indent: indent)
var outValues: [String] = []
for (idx, param) in outParams.enumerated() {
localDecls.append("\(indent)var out\(idx): \(outParamLocalType(param)) = \(outParamInitValue(param))")
@ -662,6 +990,7 @@ private func renderMultiStatementBody(_ plan: CallablePlan, indent: String) -> [
postCall.append("\(indent) throw GLibError(consuming: e)")
postCall.append("\(indent)}")
}
postCall.append(contentsOf: callbackBoxRelease(plan, indent: indent))
// C pointer returns (object, string, boxed) are imported as optionals;
// force-unwrap when the Swift type is non-optional (safe: error-check
@ -738,15 +1067,45 @@ private func renderCallExpression(_ plan: CallablePlan) -> String {
/// Renders the `{ }` body lines of a function or method (indented by
/// `indent`). String parameters are wrapped in one `withCString` closure per
/// string, one level per line, each returning its inner result the flat
/// single-expression form overwhelms the type-checker past ~2 nested closures.
/// string. Callback-box params add setup before the C call and release
/// after (for scope==.call).
private func renderCallBody(_ plan: CallablePlan, indent: String) -> [String] {
let hasCallbacks = plan.parameters.contains { if case .callbackBox(_, _) = $0.mapping.marshalIn { true } else { false } }
let (cCall, stringParams) = cArguments(plan)
let hasReturn = plan.returnMapping != nil
let core = hasReturn ? "return \(marshalReturn(cCall, mapping: plan.returnMapping!))" : cCall
if stringParams.isEmpty { return ["\(indent)\(core)"] }
// When callbacks are present, emit setup / call / release / return
// instead of a single return-expression (release must run after C call).
let callStmt: String
let returnExprAfter: String?
if hasCallbacks && hasReturn {
callStmt = "\(indent)let __result = \(cCall)"
returnExprAfter = "\(indent)return \(marshalReturn("__result", mapping: plan.returnMapping!))"
} else if hasReturn {
callStmt = "\(indent)return \(marshalReturn(cCall, mapping: plan.returnMapping!))"
returnExprAfter = nil
} else {
callStmt = "\(indent)\(cCall)"
returnExprAfter = nil
}
// Simple path: no callbacks, no string params
if !hasCallbacks && stringParams.isEmpty {
return [callStmt]
}
let setupStmts = callbackBoxSetup(plan, indent: indent)
let releaseStmts = callbackBoxRelease(plan, indent: indent)
if stringParams.isEmpty && hasCallbacks {
var lines = setupStmts
lines.append(callStmt)
lines.append(contentsOf: releaseStmts)
if let ret = returnExprAfter { lines.append(ret) }
return lines
}
// String params present wrap in withCString closures
var lines: [String] = []
var scope = indent
let openerPrefix = hasReturn ? "return " : ""
@ -754,7 +1113,20 @@ private func renderCallBody(_ plan: CallablePlan, indent: String) -> [String] {
lines.append("\(scope)\(openerPrefix)\(sp.swiftName).withCString { \(sp.cName) in")
scope += " "
}
lines.append("\(scope)\(core)")
for stmt in setupStmts {
let stripped = stmt.hasPrefix(indent) ? String(stmt.dropFirst(indent.count)) : stmt
lines.append("\(scope)\(stripped)")
}
let strippedCall = callStmt.hasPrefix(indent) ? String(callStmt.dropFirst(indent.count)) : callStmt
lines.append("\(scope)\(strippedCall)")
for stmt in releaseStmts {
let stripped = stmt.hasPrefix(indent) ? String(stmt.dropFirst(indent.count)) : stmt
lines.append("\(scope)\(stripped)")
}
if let ret = returnExprAfter {
let stripped = ret.hasPrefix(indent) ? String(ret.dropFirst(indent.count)) : ret
lines.append("\(scope)\(stripped)")
}
for _ in stringParams {
scope = String(scope.dropLast(4))
lines.append("\(scope)}")
@ -790,8 +1162,11 @@ private func renderThrowingBody(_ plan: CallablePlan, indent: String) -> [String
]
let returnStmt = hasReturn ? ["\(indent)return \(marshalReturn(returnArg, mapping: plan.returnMapping!))"] : []
let setupStmts = callbackBoxSetup(plan, indent: indent)
let releaseStmts = callbackBoxRelease(plan, indent: indent)
if stringParams.isEmpty {
return [errorDecl, cCallStmt] + errorCheck + returnStmt
return setupStmts + [errorDecl, cCallStmt] + errorCheck + releaseStmts + returnStmt
}
var lines: [String] = []
@ -800,9 +1175,17 @@ private func renderThrowingBody(_ plan: CallablePlan, indent: String) -> [String
lines.append("\(scope)return try \(sp.swiftName).withCString { \(sp.cName) in")
scope += " "
}
for stmt in setupStmts {
let stripped = stmt.hasPrefix(indent) ? String(stmt.dropFirst(indent.count)) : stmt
lines.append("\(scope)\(stripped)")
}
lines.append("\(scope)\(errorDecl)")
lines.append("\(scope)\(cCallStmt)")
lines += errorCheck.map { $0.hasPrefix(indent) ? scope + String($0.dropFirst(indent.count)) : $0 }
for stmt in releaseStmts {
let stripped = stmt.hasPrefix(indent) ? String(stmt.dropFirst(indent.count)) : stmt
lines.append("\(scope)\(stripped)")
}
lines += returnStmt.map { $0.hasPrefix(indent) ? scope + String($0.dropFirst(indent.count)) : $0 }
for _ in stringParams {
scope = String(scope.dropLast(4))
@ -953,6 +1336,11 @@ private func marshalCallArg(_ param: ParameterPlan) -> String {
return pointerArg(param)
case .boxedPointer:
return pointerArg(param)
case .callbackBox(let scope, _):
// The callback itself is passed as the C arg (Swift closures with
// @convention(c) convert to C function pointers automatically).
// The opaque data pointer goes to the user-data slot via closureDataMap.
return param.swiftName
case .unsupported(let reason):
return "/* unsupported: \(reason) */"
}

View file

@ -1,3 +1,15 @@
// Phase D7 (per-signature scope=notified destroy trampolines): DEFERRED.
// Verified zero scope="notified" callback params flow through the plan
// layer in tier 1 (GLib + GObject). All notified scopes are on
// g_signal_connect_data (shortcircuited by the @_silgen_name shim in
// renderSupport) or on callables whose callback params are skipped with
// .callbackWithoutUserData until D4.3 lands. Deferral is plan-sanctioned
// per local://phase-d-signals-callbacks-plan.md §D7 contingency.
// When a tier-1 callable with a notified callback param surfaces,
// renderCallable's callback-box branch must populate
// destroyTrampoline="<trampoline name>" and emit a per-signature
// @_cdecl destroy trampoline.
// Planner.swift
// The binding planner: walks a parsed GIR namespace, resolves every type
// through the registry and TypeMapper, and produces either a complete
@ -51,7 +63,10 @@ private func planNamespace(_ ns: Namespace, context: MapContext) -> ModulePlan {
var totalTypes = 0
var boundCallables = 0
var totalCallables = 0
var boundCallbacks = 0
var totalCallbacks = 0
var boundSignals = 0
var totalSignals = 0
// Enumerations
for enumeration in ns.enumerations {
totalTypes += 1; boundTypes += planEnum(into: &types, skips: &skips, enumeration: enumeration, context: context)
@ -77,22 +92,25 @@ private func planNamespace(_ ns: Namespace, context: MapContext) -> ModulePlan {
for alias in ns.aliases {
totalTypes += 1; boundTypes += planAliasType(into: &types, skips: &skips, alias: alias, context: context)
}
// Classes, Interfaces, Records, Callbacks skip with reasons (Phase B6+)
for klass in ns.classes {
totalTypes += 1
boundTypes += skipClass(into: &skips, into: &types,
boundCallables: &boundCallables, totalCallables: &totalCallables,
klass: klass, namespace: ns.name, context: context)
if case .class(let cp) = types.last { totalSignals += klass.signals.filter(\.symbolInfo.isBindable).count; boundSignals += cp.signals.count }
}
for iface in ns.interfaces {
totalTypes += 1; boundTypes += skipInterface(into: &skips, into: &types, iface: iface, namespace: ns.name, context: context)
if case .interface(let ip) = types.last { totalSignals += iface.signals.filter(\.symbolInfo.isBindable).count; boundSignals += ip.signals.count }
}
for record in ns.records {
totalTypes += 1; boundTypes += skipRecord(into: &skips, into: &types, record: record, namespace: ns.name, context: context)
}
for callback in ns.callbacks {
totalTypes += 1; boundTypes += skipCallback(into: &skips, callback: callback, namespace: ns.name)
totalCallbacks += 1
totalTypes += 1; boundTypes += planCallbackType(into: &skips, into: &types,
callback: callback, namespace: ns.name, context: context)
if case .callback(_) = types.last { boundCallbacks += 1 }
}
for fn in ns.functions {
totalCallables += 1
@ -166,7 +184,9 @@ private func planNamespace(_ ns: Namespace, context: MapContext) -> ModulePlan {
let coverage = CoverageStats(
boundCallables: boundCallables, totalCallables: totalCallables,
boundTypes: boundTypes, totalTypes: totalTypes
boundTypes: boundTypes, totalTypes: totalTypes,
boundCallbacks: boundCallbacks, totalCallbacks: totalCallbacks,
boundSignals: boundSignals, totalSignals: totalSignals
)
return ModulePlan(module: context.currentModule, types: types, skips: skips, coverage: coverage)
@ -379,11 +399,41 @@ private func skipRecord(into skips: inout [SkipEntry], into types: inout [TypePl
return 0
}
private func skipCallback(into skips: inout [SkipEntry], callback: Callback, namespace: String) -> Int {
/// Plans a namespace-level callback as a `CallbackTypePlan`.
///
/// Tries to map every parameter and return value through the TypeMapper.
/// On success, produces a `.callback(CallbackTypePlan)` type plan.
/// On failure (unmappable param/return), emits a `SkipEntry` with the
/// existing `.callbackWithoutUserData` reason and a detail explaining why.
/// - Returns: 1 when the callback was bound, 0 when skipped.
private func planCallbackType(into skips: inout [SkipEntry], into types: inout [TypePlan],
callback: Callback, namespace: String, context: MapContext) -> Int {
let fullName = "\(namespace).\(callback.name)"
skips.append(SkipEntry(symbol: fullName, cIdentifier: callback.cType,
reason: .callbackWithoutUserData, detail: "callback planned for Phase D2"))
return 0
// Map the callback through the TypeMapper this recursively maps
// every parameter and return value. If any fails, we skip with the
// existing callbackWithoutUserData reason (baseline-stable).
let refType = GIRType.typeRef(callback.name, namespace: namespace)
do {
let mapping = try map(refType, nullable: false, transfer: .none, context: context)
let plan = CallbackTypePlan(
name: callback.name,
swiftType: mapping.swiftType,
cSwiftType: mapping.cSwiftType,
doc: callback.doc
)
types.append(.callback(plan))
return 1
} catch let error as MapError {
skips.append(SkipEntry(symbol: fullName, cIdentifier: callback.cType,
reason: error.reason == .callbackWithoutUserData ? .callbackWithoutUserData : error.reason,
detail: "callback '\(callback.name)' has unmappable param/return: \(error.detail)"))
return 0
} catch {
skips.append(SkipEntry(symbol: fullName, cIdentifier: callback.cType,
reason: .callbackWithoutUserData,
detail: "callback '\(callback.name)' unmappable"))
return 0
}
}
/// Plans a namespace-level function, appending a `.callable` type plan on
@ -391,6 +441,12 @@ private func skipCallback(into skips: inout [SkipEntry], callback: Callback, nam
private func planOrSkipFunction(into skips: inout [SkipEntry], into types: inout [TypePlan], fn: GlobalFunction, namespace: String, context: MapContext) -> Int {
let fullName = "\(namespace).\(fn.name)"
// Check bindability: introspectable, not shadowed, not deprecated-removed
if let skipEntry = checkBindable(fn.symbolInfo, fullName: fullName, cIdentifier: fn.cIdentifier) {
skips.append(skipEntry)
return 0
}
// Symbols the system library does not export (macros, inline functions,
// or GType getters absent from the shared object) cannot be called.
if knownMissingCFunctions.contains(fn.cIdentifier) {
@ -567,6 +623,17 @@ func planInterface(_ iface: Interface, context: MapContext) -> (plan: InterfaceP
}
}
// Signals
var signalPlans: [SignalPlan] = []
for signal in iface.signals where signal.symbolInfo.isBindable {
let result = planSignal(signal, onClass: iface.name, namespace: context.currentNamespace, context: context)
if let plan = result.plan {
signalPlans.append(plan)
} else if let skip = result.skip {
memberSkips.append(skip)
}
}
// Compute the module-qualified name for matching ClassPlan.interfaces.
let qualifiedName: String = {
let girName = "\(context.currentNamespace).\(iface.name)"
@ -575,13 +642,13 @@ func planInterface(_ iface: Interface, context: MapContext) -> (plan: InterfaceP
}
return registry.swiftTypeName(for: resolved, in: context.currentModule)
}()
let plan = InterfacePlan(
name: iface.name, cType: iface.cType,
prereqs: prereqSwiftNames,
getTypeFunction: iface.getTypeFunction,
methods: methodPlans,
properties: propertyPlans,
signals: signalPlans,
qualifiedName: qualifiedName,
doc: iface.doc
)
@ -780,6 +847,17 @@ func planClass(_ klass: Class, context: MapContext) -> (plan: ClassPlan, memberS
}
}
// Signals
var signalPlans: [SignalPlan] = []
for signal in klass.signals where signal.symbolInfo.isBindable {
let result = planSignal(signal, onClass: klass.name, namespace: context.currentNamespace, context: context)
if let plan = result.plan {
signalPlans.append(plan)
} else if let skip = result.skip {
memberSkips.append(skip)
}
}
let plan = ClassPlan(
name: klass.name, cType: klass.cType,
parent: parentSwiftName,
@ -792,6 +870,7 @@ func planClass(_ klass: Class, context: MapContext) -> (plan: ClassPlan, memberS
methods: methodPlans,
functions: functionPlans,
properties: propertyPlans,
signals: signalPlans,
doc: klass.doc
)
return (plan, memberSkips)
@ -937,6 +1016,13 @@ private func planCallable(
return .skip(SkipEntry(symbol: fullName, cIdentifier: cIdentifier,
reason: .unknownType, detail: "return type: \(reason)"))
}
// Callback return types cannot be constructed from C function
// pointers yet the renderer has no marshal-out support.
if returnMap.category == .callback {
return .skip(SkipEntry(symbol: fullName, cIdentifier: cIdentifier,
reason: .callbackWithoutUserData,
detail: "callback return type '\(returnMap.swiftType)' deferred"))
}
returnMapping = returnMap
case .failure(let error as MapError):
return .skip(SkipEntry(symbol: fullName, cIdentifier: cIdentifier,
@ -974,6 +1060,101 @@ func planMethod(_ method: Method, context: MapContext) -> CallablePlanResult {
throwsGError: method.throwsGError, doc: method.doc, isStatic: false, context: context)
}
/// Plans a GObject signal for a class or interface.
///
/// Maps every signal parameter and the return value through the TypeMapper.
/// The implicit instance parameter (C-arg 0) is synthesized using the owning
/// class's type. Returns a ``SignalPlan`` on success or a skip entry on
/// failure (e.g. unmappable parameter type).
///
/// - Parameters:
/// - signal: The signal to plan.
/// - className: The owning class/interface Swift name.
/// - namespace: The GIR namespace name.
/// - context: The resolution context.
/// - Returns: A tuple of optional plan and optional skip entry (exactly one is non-nil).
private func planSignal(_ signal: Signal, onClass className: String,
namespace: String, context: MapContext) -> (plan: SignalPlan?, skip: SkipEntry?) {
let girName = "\(context.currentNamespace).\(className).\(signal.name)"
var signalParams: [ParameterPlan] = []
// Instance parameter: C-arg 0, the emitting GObject pointer.
// Its Swift type is the owning class; its C type is UnsafeMutableRawPointer.
let instanceParam = ParameterPlan(
swiftName: "instance", cArgIndex: 0,
mapping: Mapping(swiftType: className, cSwiftType: "UnsafeMutableRawPointer",
marshalIn: .direct, marshalOut: .direct),
isInstanceParameter: true
)
signalParams.append(instanceParam)
// Map each real signal parameter
for (idx, param) in signal.parameters.enumerated() {
do {
let mapping = try map(param.type, nullable: param.isNullable,
transfer: param.transferOwnership, context: context)
guard mapping.isReadyForCallables else {
return (nil, SkipEntry(symbol: girName, cIdentifier: nil,
reason: .signalUnmappableParam,
detail: "param '\(param.name)' type '\(mapping.swiftType)' not ready for callables"))
}
let pName = swiftParameterName(param.name)
signalParams.append(ParameterPlan(
swiftName: pName, cArgIndex: idx + 1, // +1 because instance param is index 0
mapping: mapping
))
} catch let error as MapError {
return (nil, SkipEntry(symbol: girName, cIdentifier: nil,
reason: .signalUnmappableParam,
detail: "param '\(param.name)': \(error.detail)"))
} catch {
return (nil, SkipEntry(symbol: girName, cIdentifier: nil,
reason: .signalUnmappableParam,
detail: "param '\(param.name)': unexpected error"))
}
}
// Map return value
let returnMapping: Mapping?
if signal.returnValue.type != .void {
do {
let mapped = try map(signal.returnValue.type, nullable: signal.returnValue.isNullable,
transfer: signal.returnValue.transferOwnership, context: context)
guard mapped.isReadyForCallables else {
return (nil, SkipEntry(symbol: girName, cIdentifier: nil,
reason: .signalUnmappableParam,
detail: "return type '\(mapped.swiftType)' not ready for callables"))
}
returnMapping = mapped
} catch let error as MapError {
return (nil, SkipEntry(symbol: girName, cIdentifier: nil,
reason: .signalUnmappableParam,
detail: "return: \(error.detail)"))
} catch {
return (nil, SkipEntry(symbol: girName, cIdentifier: nil,
reason: .signalUnmappableParam,
detail: "return: unexpected error"))
}
} else {
returnMapping = nil
}
let swiftName = swiftFunctionName(signal.name)
let trampolineCName = "_trampoline_\(namespace)_\(className)_\(signal.name)"
let plan = SignalPlan(
owningClassName: className, girName: signal.name,
swiftName: swiftName,
isDetailed: signal.isDetailed,
parameters: signalParams,
returnMapping: returnMapping,
trampolineCName: trampolineCName,
doc: signal.doc
)
return (plan, nil)
}
/// Plans a constructor as a Swift `convenience init`. The C constructor's
/// returned instance pointer is adopted through the class's designated
/// `init(takingOwnership:)`, which sinks a floating reference when the class
@ -1018,7 +1199,10 @@ func planParameters(
_ parameters: [Parameter], context: MapContext
) -> ParameterPlanResult {
var plans: [ParameterPlan] = []
// Pre-scan: collect indices that are user-data targets for callbacks.
// These are plain gpointer params that would otherwise fail the pointer
// check the callback-box mechanism handles them.
let closureTargets = Set(parameters.compactMap(\.closureIndex))
for (index, param) in parameters.enumerated() {
// The instance parameter is always `self` passed as `self.pointer`,
// never type-checked (its type is the enclosing class, which is a
@ -1099,11 +1283,16 @@ func planParameters(
let mappingResult = Result { try map(param.type, nullable: param.isNullable,
transfer: param.transferOwnership, context: context) }
switch mappingResult {
case .success(let paramMapping):
if !paramMapping.isReadyForCallables {
case .success(var paramMapping):
// D4.3 callback-param binding remains deferred: passing a Swift
// closure captured as `@convention(c)` through generic
// `_ClosureBox<T>` storage triggers a Swift compiler ICE
// ("failed to produce diagnostic for expression") on functions
// like `g_qsort_with_data`/`g_dataset_foreach`. See HANDOFF.md.
if case .callbackBox = paramMapping.marshalIn {
return .skip(SkipEntry(symbol: "", cIdentifier: nil,
reason: .unknownType,
detail: "parameter '\(param.name)' type '\(paramMapping.swiftType)' is not yet generated"))
reason: .callbackWithoutUserData,
detail: "callback param '\(param.name)' deferred to Phase D4.3"))
}
let isString = paramMapping.marshalIn == .stringToC
@ -1125,19 +1314,22 @@ func planParameters(
}
}
// Non-string pointer parameters that aren't mapped as objects or
// boxed records need address-of / wrapper marshalling deferred.
// Object and boxed params have their own marshalIn paths (.objectPointer,
// .boxedPointer) that pass the wrapper's pointer.
if param.cType.hasSuffix("*") && !isString,
paramMapping.category != .needsClass, paramMapping.category != .needsRecord {
paramMapping.category != .needsClass, paramMapping.category != .needsRecord,
!closureTargets.contains(index) {
return .skip(SkipEntry(symbol: "", cIdentifier: nil,
reason: .unknownType, detail: "parameter '\(param.name)' C type '\(param.cType)' is a pointer"))
}
let swiftName = swiftParameterName(param.name)
// Only record a separate closureIndex when it differs from the
// callback param's own C arg index (callback doubling as user-data
// is the common case and needs no replacement).
let planClosure: Int? = (param.closureIndex != index) ? param.closureIndex : nil
plans.append(ParameterPlan(
swiftName: swiftName, cArgIndex: index,
mapping: paramMapping, isInstanceParameter: param.isInstanceParameter
mapping: paramMapping, isInstanceParameter: param.isInstanceParameter,
closureIndex: planClosure, destroyIndex: param.destroyIndex
))
case .failure(let error as MapError):
return .skip(SkipEntry(symbol: "", cIdentifier: nil,

View file

@ -74,7 +74,7 @@ public struct Mapping: Equatable, Sendable {
/// boxed records need their wrapper class. Once generated, callables
/// referencing these types can be planned.
public var isReadyForCallables: Bool {
category == .ready || category == .needsClass || category == .needsRecord
category == .ready || category == .needsClass || category == .needsRecord || category == .callback
}
}
@ -302,8 +302,63 @@ private func mapTypeRef(
detail: "'\(namespace).\(name)' has no GType registration or lifetime functions")
case .callback:
throw MapError(reason: .callbackWithoutUserData,
detail: "callback '\(namespace).\(name)' not yet supported as a mapped type")
let girName = "\(namespace).\(name)"
guard let cb = context.registry.callback(girName: girName) else {
throw MapError(reason: .unknownType,
detail: "callback '\(girName)' not found in registry")
}
// Split real parameters from the trailing user-data pointer
let userDataIdx = cb.userDataParameterIndex
let realParams: [Parameter]
if let idx = userDataIdx {
realParams = Array(cb.parameters[..<idx]) + Array(cb.parameters[(cb.parameters.index(after: idx))...])
} else {
realParams = cb.parameters
}
// Map each real parameter
var swiftParamTypes: [String] = []
var cSwiftParamTypes: [String] = []
for param in realParams {
let pm: Mapping
do {
pm = try _mapValue(type: param.type, transfer: param.transferOwnership, context: context)
} catch {
throw MapError(reason: .callbackWithoutUserData,
detail: "callback '\(name)' param '\(param.name)' unmappable: \(error.detail)")
}
swiftParamTypes.append(pm.swiftType)
cSwiftParamTypes.append(pm.cSwiftType)
}
// Map return value
let retMapping: Mapping
if cb.returnValue.type != .void {
do {
retMapping = try _mapValue(type: cb.returnValue.type, transfer: cb.returnValue.transferOwnership, context: context)
} catch {
throw MapError(reason: .callbackWithoutUserData,
detail: "callback '\(name)' return unmappable: \(error.detail)")
}
} else {
retMapping = .voidMapping
}
// Append user-data pointer to C signature only
if userDataIdx != nil {
cSwiftParamTypes.append("UnsafeMutableRawPointer?")
}
// Build closure type strings
// Build closure type strings avoid double-parens when params are empty
let swiftParamsStr = swiftParamTypes.isEmpty ? "" : swiftParamTypes.joined(separator: ", ")
let cSwiftParamsStr = cSwiftParamTypes.isEmpty ? "" : cSwiftParamTypes.joined(separator: ", ")
let swiftRet = retMapping.swiftType == "Void" ? "Void" : retMapping.swiftType
let cSwiftRet = retMapping.cSwiftType == "Void" ? "Void" : retMapping.cSwiftType
let swiftType = "(\(swiftParamsStr)) -> \(swiftRet)"
let cSwiftType = "@convention(c) (\(cSwiftParamsStr)) -> \(cSwiftRet)"
let mapped = Mapping(
swiftType: swiftType, cSwiftType: cSwiftType,
marshalIn: .callbackBox(scope: nil, destroyTrampoline: ""),
marshalOut: .direct, category: .callback
)
return mapped
case .alias(let target):
return try _mapValue(type: target, transfer: transfer, context: context)

View file

@ -120,6 +120,9 @@ public struct ResolvedType: Equatable, Sendable {
public struct TypeRegistry: Sendable {
/// Every resolved type, keyed by fully qualified GIR name.
private var types: [String: ResolvedType] = [:]
/// Callback IR models, keyed by fully qualified GIR name.
/// Needed by the TypeMapper to resolve callback parameter/return types.
private var callbacks: [String: Callback] = [:]
/// Namespaces whose GIR was loaded and whose types are therefore known.
///
/// A reference into any namespace outside this set is foreign by
@ -246,6 +249,7 @@ public struct TypeRegistry: Sendable {
girName: girName, swiftModule: module, swiftName: cb.name, cType: cb.cType,
category: .callback
)
callbacks[girName] = cb
}
for alias in ns.aliases {
let girName = "\(ns.name).\(alias.name)"
@ -394,6 +398,15 @@ public struct TypeRegistry: Sendable {
types.values.sorted { $0.girName < $1.girName }
}
/// Looks up the full `Callback` IR model for a fully qualified GIR name.
///
/// - Parameter girName: Fully qualified callback name, e.g. `"GLib.Func"`.
/// - Returns: The callback definition, or `nil` if the name is not a known
/// callback type.
public func callback(girName: String) -> Callback? {
callbacks[girName]
}
/// The namespaces referenced via `<include>` but not generated, plus any
/// namespaces excluded by configuration.
///

View file

@ -0,0 +1,167 @@
// CallbackGenerationTests.swift
// Covers Phase D6/D4.3: namespace-level callback typealias emission and
// callback-typed parameter marshalling.
//
// D4.3 status: the planner (`Planner.planParameters`) still blanket-skips
// every callback-typed parameter with `.callbackWithoutUserData` real
// binding is blocked by a Swift compiler ICE ("failed to produce diagnostic
// for expression") triggered when a `@convention(c)` closure boxed in the
// generic `_ClosureBox<T>` is passed to a C function expecting a distinct
// `@convention(c)` typealias (reproduced on `g_qsort_with_data`). See
// `Planner.swift`'s D4.3 comment and HANDOFF.md. The renderer-side
// infrastructure (`marshalCallArg`'s `.callbackBox` branch, `callbackBoxSetup`
// / `callbackBoxRelease`) is implemented and exercised here by constructing
// `CallablePlan` values directly bypassing the planner so the renderer
// logic is proven correct independent of the planner's current skip.
import Testing
@testable import SwiftGtkGenCore
@Suite("Callback generation")
struct CallbackGenerationTests {
func makeContext() -> MapContext {
let glib = Repository(namespaces: [
Namespace(name: "GLib", version: "2.0",
callbacks: [Callback(name: "CompareDataFunc", cType: "GCompareDataFunc")])
])
let registry = TypeRegistry(repositories: ["GLib": glib])
return MapContext(registry: registry, currentModule: "GLib", currentNamespace: "GLib")
}
// MARK: - Typealias emission (D6)
@Test("renderCallbackType emits both the @convention(c) form and the Swift-closure form")
func callbackTypeEmitsBothTypealiases() throws {
let plan = CallbackTypePlan(
name: "CompareFunc",
swiftType: "(UnsafeRawPointer?, UnsafeRawPointer?) -> Int32",
cSwiftType: "@convention(c) (UnsafeRawPointer?, UnsafeRawPointer?) -> Int32"
)
let module = ModulePlan(module: "GLib", types: [.callback(plan)], skips: [],
coverage: CoverageStats())
let files = renderModule(module)
let source = files["Callbacks.swift"] ?? ""
#expect(source.contains("public typealias CompareFunc = @convention(c) (UnsafeRawPointer?, UnsafeRawPointer?) -> Int32"))
#expect(source.contains("public typealias CompareFuncSwift = (UnsafeRawPointer?, UnsafeRawPointer?) -> Int32"))
}
// MARK: - Planner baseline (unchanged pending D4.3)
@Test("A callback-typed parameter still skips with callbackWithoutUserData (D4.3 deferred)")
func callbackParamStillSkipped() throws {
let fn = GlobalFunction(
name: "qsort_with_data", cIdentifier: "g_qsort_with_data",
parameters: [
Parameter(name: "compare_func", type: .typeRef("CompareDataFunc", namespace: "GLib"),
cType: "GCompareDataFunc"),
]
)
guard case .skip(let entry) = planFunction(fn, context: makeContext()) else {
Issue.record("expected callback-typed param to still skip pending D4.3"); return
}
#expect(entry.reason == .callbackWithoutUserData)
}
// MARK: - Renderer infrastructure (constructed directly D4.3 render-side proof)
/// A `.callbackBox(scope: .call, )` parameter that doubles as its own
/// user-data slot (`closureIndex == cArgIndex`, GLib's common
/// `..._with_data` pattern) mirrors the shape `g_qsort_with_data` would
/// plan to once D4.3's planner-side ICE is resolved.
var scopeCallCallable: CallablePlan {
let callbackMapping = Mapping(
swiftType: "(UnsafeRawPointer?, UnsafeRawPointer?, UnsafeMutableRawPointer?) -> Int32",
cSwiftType: "@convention(c) (UnsafeRawPointer?, UnsafeRawPointer?, UnsafeMutableRawPointer?) -> Int32",
marshalIn: .callbackBox(scope: .call, destroyTrampoline: ""),
marshalOut: .direct,
category: .callback
)
let param = ParameterPlan(
swiftName: "compareFunc", cArgIndex: 0,
mapping: callbackMapping, closureIndex: 0
)
return CallablePlan(name: "qsortWithData", cIdentifier: "g_qsort_with_data", parameters: [param])
}
@Test("A scope=.call callback param has no separate user-data ParameterPlan when closureIndex == cArgIndex")
func scopeCallHasNoSeparateUserData() throws {
let plan = scopeCallCallable
#expect(plan.parameters.count == 1)
let param = plan.parameters[0]
guard case .callbackBox(let scope, let destroyTrampoline) = param.mapping.marshalIn else {
Issue.record("expected .callbackBox marshalIn"); return
}
#expect(scope == .call)
#expect(destroyTrampoline == "")
#expect(param.closureIndex == param.cArgIndex)
}
@Test("renderCallable for a scope=.call callback emits box setup before the C call and release after")
func scopeCallEmitsSetupAndRelease() throws {
let module = ModulePlan(module: "GLib", types: [.callable(scopeCallCallable)], skips: [],
coverage: CoverageStats())
let files = renderModule(module)
let source = files["Functions.swift"] ?? ""
// Setup: box the closure and take an opaque retained pointer BEFORE
// the C call.
#expect(source.contains("_ClosureBox(compareFunc)"))
#expect(source.contains("Unmanaged.passRetained"))
#expect(source.contains("g_qsort_with_data("))
// Release: scope=.call takes the closure back and releases it AFTER
// the C call returns (matches gtk-rs stack-borrow lifetime) the box
// must not leak.
#expect(source.contains("takeRetainedValue()"))
// Ordering: the setup line must appear before the C call line, and
// the release line after it.
let setupIdx = source.range(of: "Unmanaged.passRetained")!.lowerBound
let callIdx = source.range(of: "g_qsort_with_data(")!.lowerBound
let releaseIdx = source.range(of: "takeRetainedValue()")!.lowerBound
#expect(setupIdx < callIdx)
#expect(callIdx < releaseIdx)
}
@Test("Support.swift emits the closure-box runtime when a module has callback-param callables but no signals")
func supportEmitsClosureBoxForCallbacksWithoutSignals() throws {
let module = ModulePlan(module: "GLib", types: [.callable(scopeCallCallable)], skips: [],
coverage: CoverageStats())
let files = renderModule(module)
let support = files["Support.swift"] ?? ""
#expect(support.contains("_ClosureBox"))
}
// MARK: - CoverageStats (R7 / D8)
@Test("CoverageStats tallies bound callbacks and signals from a fixture namespace")
func coverageStatsCountsCallbacksAndSignals() throws {
let ns = Namespace(
name: "GLib", version: "2.0",
classes: [
Class(name: "Emitter", cType: "GEmitter", parent: nil,
getTypeFunction: "g_emitter_get_type",
signals: [Signal(name: "fired", isDetailed: false)]),
],
callbacks: [
Callback(name: "SimpleCallback", cType: "GSimpleCallback"),
]
)
let repo = Repository(namespaces: [ns])
let analysis = MultiPackageAnalysis(
repositories: ["GLib": repo],
directDependencies: ["GLib": []],
transitiveDependencies: ["GLib": []],
implicitImports: ["GLib": []],
packageConfigs: [:]
)
let registry = TypeRegistry(repositories: ["GLib": repo])
let module = planModules(analysis: analysis, registry: registry)["GLib"]
let coverage = module?.coverage ?? CoverageStats()
#expect(coverage.boundCallbacks == 1)
#expect(coverage.totalCallbacks == 1)
#expect(coverage.boundSignals == 1)
#expect(coverage.totalSignals == 1)
}
}

View file

@ -0,0 +1,78 @@
// InterfaceSignalGenerationTests.swift
// Covers Phase D4.2: GObject interface signals. Tier 1 (GLib + GObject) has
// zero interface signals at runtime every tier-1 signal lives on a class
// (`Object`, `BindingGroup`) so the compile gate and smoke tests cannot
// exercise this path. This unit test is the only proof that
// `renderInterface` actually emits a signal extension + trampoline instead
// of silently dropping `InterfacePlan.signals` (the D4.2 regression this
// suite guards against).
import Testing
@testable import SwiftGtkGenCore
@Suite("Interface signal generation")
struct InterfaceSignalGenerationTests {
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"),
]
)
])
let registry = TypeRegistry(repositories: ["GObject": gobject])
return MapContext(registry: registry, currentModule: "GObject", currentNamespace: "GObject")
}
/// Plans a one-off interface carrying `signals` and renders it, returning
/// the interface file body plus the plan and member skips.
func renderInterfaceFile(named name: String, signals: [Signal]) -> (source: String, plan: InterfacePlan, skips: [SkipEntry]) {
let iface = Interface(name: name, cType: "G\(name)",
signals: signals,
getTypeFunction: "g_\(name.lowercased())_get_type")
let (plan, skips) = planInterface(iface, context: makeContext())
let module = ModulePlan(module: "GObject", types: [.interface(plan)], skips: skips,
coverage: CoverageStats())
return (renderModule(module)["\(name).swift"] ?? "", plan, skips)
}
@Test("An interface signal renders a protocol-extension connect method and a file-level trampoline")
func interfaceSignalRendersTrampolineAndConnect() throws {
let clicked = Signal(name: "clicked", isDetailed: false)
let (source, plan, skips) = renderInterfaceFile(named: "Clickable", signals: [clicked])
#expect(skips.isEmpty)
#expect(plan.signals.count == 1)
// (a) The protocol declaration is unchanged still just the
// `pointer` requirement and any methods/properties, no signal noise
// inside the protocol body itself.
#expect(source.contains("public protocol Clickable {"))
#expect(source.contains("var pointer: UnsafeMutableRawPointer { get }"))
// (b) A protocol extension supplies the connect method as a default
// implementation signals are not protocol requirements (the C
// signal-emission machinery is identical across all conformers).
#expect(source.contains("extension Clickable {"))
#expect(source.contains("func connectClicked(_ handler:"))
// (c) A file-level @_cdecl nonisolated trampoline exists (same
// pattern as class signals) this is the D4.2 regression check:
// the previous implementation planned interface signals but the
// renderer silently dropped them.
#expect(source.contains("@_cdecl(\"_trampoline_GObject_Clickable_clicked\")"))
#expect(source.contains("nonisolated func _trampoline_GObject_Clickable_clicked("))
#expect(source.contains("MainActor.assumeIsolated"))
}
@Test("An interface with no signals renders no extension and no trampoline")
func interfaceWithoutSignalsOmitsExtension() throws {
let (source, plan, skips) = renderInterfaceFile(named: "Plain", signals: [])
#expect(skips.isEmpty)
#expect(plan.signals.isEmpty)
#expect(!source.contains("extension Plain {"))
#expect(!source.contains("@_cdecl"))
}
}

View file

@ -0,0 +1,105 @@
// SignalGenerationTests.swift
// Covers Phase D signal generation: `@_cdecl nonisolated` trampolines that
// hop to `MainActor.assumeIsolated` before constructing typed wrappers and
// invoking the user's closure, plus the `connect<Name>` method that boxes the
// closure, wires the ABI-correct 2-arg `GClosureNotify` destroy callback into
// `g_signal_connect_data`, and returns a `SignalHandle`.
import Testing
@testable import SwiftGtkGenCore
@Suite("Signal generation")
struct SignalGenerationTests {
/// A GObject-local context registering a root `Object` class the
/// minimal registry needed to plan a signal whose instance param resolves
/// to a known class.
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"),
Class(name: "ParamSpec", cType: "GParamSpec", parent: nil,
getTypeFunction: "g_param_spec_get_type"),
]
)
])
let registry = TypeRegistry(repositories: ["GObject": gobject])
return MapContext(registry: registry, currentModule: "GObject", currentNamespace: "GObject")
}
/// Plans a one-off class carrying `signals` and renders it, returning the
/// class file body plus the plan and skips.
func renderClass(named name: String, signals: [Signal]) -> (source: String, plan: ClassPlan, skips: [SkipEntry]) {
let klass = Class(name: name, cType: "G\(name)", parent: nil,
getTypeFunction: "g_\(name.lowercased())_get_type",
signals: signals)
let (plan, skips) = planClass(klass, context: makeContext())
let module = ModulePlan(module: "GObject", types: [.class(plan)], skips: skips,
coverage: CoverageStats())
return (renderModule(module)["\(name).swift"] ?? "", plan, skips)
}
@Test("Trampoline emits a @_cdecl nonisolated func with the correct C parameter list")
func trampolineCSignature() throws {
let notify = Signal(
name: "notify",
parameters: [Parameter(name: "pspec", type: .pointer, cType: "GParamSpec*")],
isDetailed: true
)
let (source, plan, skips) = renderClass(named: "Object", signals: [notify])
#expect(skips.isEmpty)
#expect(plan.signals.count == 1)
#expect(source.contains("@_cdecl(\"_trampoline_GObject_Object_notify\")"))
#expect(source.contains("nonisolated func _trampoline_GObject_Object_notify("))
#expect(source.contains("_ instance: UnsafeMutableRawPointer"))
#expect(source.contains("_ data: UnsafeMutableRawPointer?"))
// Body re-enters MainActor before touching the raw pointers.
#expect(source.contains("MainActor.assumeIsolated"))
}
@Test("isDetailed: true renders a detail parameter; isDetailed: false does not")
func detailedVsNonDetailedSignature() throws {
let detailed = Signal(name: "notify", isDetailed: true)
let bare = Signal(name: "destroy", isDetailed: false)
let (source, plan, skips) = renderClass(named: "Widget", signals: [detailed, bare])
#expect(skips.isEmpty)
#expect(plan.signals.count == 2)
#expect(source.contains("func connectNotify(detail:"))
#expect(source.contains("func connectDestroy(_ handler:"))
#expect(!source.contains("func connectDestroy(detail:"))
}
@Test("An unmappable signal parameter type produces a skip, not a partial plan")
func unmappableParamSkip() throws {
// `GIRType.typeRef` to an unregistered type never resolves the
// planner must skip the whole signal rather than emit a broken plan.
let badSignal = Signal(
name: "weird",
parameters: [Parameter(name: "thing", type: .typeRef("Nonexistent", namespace: "GObject"),
cType: "GNonexistent*")]
)
let (source, plan, skips) = renderClass(named: "Emitter", signals: [badSignal])
#expect(plan.signals.isEmpty)
#expect(skips.contains { $0.reason == .signalUnmappableParam })
#expect(!source.contains("connectWeird"))
}
@Test("connect method wires the ABI-correct 2-arg destroy callback into g_signal_connect_data")
func destroyNotifyWiring() throws {
let notify = Signal(name: "notify", isDetailed: true)
let (source, _, skips) = renderClass(named: "Object", signals: [notify])
#expect(skips.isEmpty)
// The destroy closure matches GClosureNotify's 2-arg C signature
// (gpointer data, GClosure *closure) not GDestroyNotify's 1-arg form.
#expect(source.contains("@convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void"))
#expect(source.contains("_sgtk_destroy_notify_impl(data, nil)"))
// The wired destroy arg is passed (non-nil) to the connect call
// the D3 leak regression this test guards against.
#expect(source.contains("unsafeBitCast(destroyFn as (@convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void), to: UnsafeRawPointer.self)"))
#expect(source.contains("_sgtk_signal_connect_data("))
#expect(!source.contains("_sgtk_signal_connect_data(ptr, cName, unsafeBitCast(\\(plan.trampolineCName)"))
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,70 +2,40 @@
"entries" : [
{
"cIdentifier" : "GBaseFinalizeFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'BaseFinalizeFunc' has unmappable param/return: callback 'BaseFinalizeFunc' param 'g_class' unmappable: 'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.BaseFinalizeFunc"
},
{
"cIdentifier" : "GBaseInitFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'BaseInitFunc' has unmappable param/return: callback 'BaseInitFunc' param 'g_class' unmappable: 'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.BaseInitFunc"
},
{
"cIdentifier" : "GBindingTransformFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.BindingTransformFunc"
},
{
"cIdentifier" : "GBoxedCopyFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.BoxedCopyFunc"
},
{
"cIdentifier" : "GBoxedFreeFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.BoxedFreeFunc"
},
{
"cIdentifier" : "GCClosure",
"detail" : "no GType registration",
"reason" : "plainRecord",
"symbol" : "GObject.CClosure"
},
{
"cIdentifier" : "GCallback",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.Callback"
},
{
"cIdentifier" : "GClassFinalizeFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'ClassFinalizeFunc' has unmappable param/return: callback 'ClassFinalizeFunc' param 'g_class' unmappable: 'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.ClassFinalizeFunc"
},
{
"cIdentifier" : "GClassInitFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'ClassInitFunc' has unmappable param/return: callback 'ClassInitFunc' param 'g_class' unmappable: 'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.ClassInitFunc"
},
{
"cIdentifier" : "GClosureMarshal",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'ClosureMarshal' has unmappable param/return: callback 'ClosureMarshal' param 'param_values' unmappable: C array bridging not yet implemented",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.ClosureMarshal"
},
{
"cIdentifier" : "GClosureNotify",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.ClosureNotify"
},
{
"cIdentifier" : "GClosureNotifyData",
"detail" : "no GType registration",
@ -104,13 +74,13 @@
},
{
"cIdentifier" : "GInstanceInitFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'InstanceInitFunc' has unmappable param/return: callback 'InstanceInitFunc' param 'instance' unmappable: 'GObject.TypeInstance' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.InstanceInitFunc"
},
{
"cIdentifier" : "GInterfaceFinalizeFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'InterfaceFinalizeFunc' has unmappable param/return: callback 'InterfaceFinalizeFunc' param 'g_iface' unmappable: 'GObject.TypeInterface' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.InterfaceFinalizeFunc"
},
@ -122,7 +92,7 @@
},
{
"cIdentifier" : "GInterfaceInitFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'InterfaceInitFunc' has unmappable param/return: callback 'InterfaceInitFunc' param 'g_iface' unmappable: 'GObject.TypeInterface' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.InterfaceInitFunc"
},
@ -144,24 +114,6 @@
"reason" : "plainRecord",
"symbol" : "GObject.ObjectConstructParam"
},
{
"cIdentifier" : "GObjectFinalizeFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.ObjectFinalizeFunc"
},
{
"cIdentifier" : "GObjectGetPropertyFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.ObjectGetPropertyFunc"
},
{
"cIdentifier" : "GObjectSetPropertyFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.ObjectSetPropertyFunc"
},
{
"cIdentifier" : "GParamSpecClass",
"detail" : "GObject class struct for 'ParamSpec'",
@ -188,25 +140,25 @@
},
{
"cIdentifier" : "GSignalAccumulator",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'SignalAccumulator' has unmappable param/return: callback 'SignalAccumulator' param 'ihint' unmappable: 'GObject.SignalInvocationHint' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.SignalAccumulator"
},
{
"cIdentifier" : "GSignalCMarshaller",
"detail" : "callback 'GObject.ClosureMarshal' not yet supported as a mapped type",
"detail" : "callback 'ClosureMarshal' param 'param_values' unmappable: C array bridging not yet implemented",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.SignalCMarshaller"
},
{
"cIdentifier" : "GSignalCVaMarshaller",
"detail" : "callback 'GObject.VaClosureMarshal' not yet supported as a mapped type",
"detail" : "callback 'VaClosureMarshal' param 'instance' unmappable: 'GObject.TypeInstance' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.SignalCVaMarshaller"
},
{
"cIdentifier" : "GSignalEmissionHook",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'SignalEmissionHook' has unmappable param/return: callback 'SignalEmissionHook' param 'ihint' unmappable: 'GObject.SignalInvocationHint' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.SignalEmissionHook"
},
@ -222,12 +174,6 @@
"reason" : "plainRecord",
"symbol" : "GObject.SignalQuery"
},
{
"cIdentifier" : "GToggleNotify",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.ToggleNotify"
},
{
"cIdentifier" : "GTypeClass",
"detail" : "no GType registration",
@ -236,7 +182,7 @@
},
{
"cIdentifier" : "GTypeClassCacheFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'TypeClassCacheFunc' has unmappable param/return: callback 'TypeClassCacheFunc' param 'g_class' unmappable: 'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypeClassCacheFunc"
},
@ -266,7 +212,7 @@
},
{
"cIdentifier" : "GTypeInterfaceCheckFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'TypeInterfaceCheckFunc' has unmappable param/return: callback 'TypeInterfaceCheckFunc' param 'g_iface' unmappable: 'GObject.TypeInterface' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypeInterfaceCheckFunc"
},
@ -284,28 +230,16 @@
},
{
"cIdentifier" : "GTypePluginCompleteInterfaceInfo",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'TypePluginCompleteInterfaceInfo' has unmappable param/return: callback 'TypePluginCompleteInterfaceInfo' param 'info' unmappable: 'GObject.InterfaceInfo' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypePluginCompleteInterfaceInfo"
},
{
"cIdentifier" : "GTypePluginCompleteTypeInfo",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'TypePluginCompleteTypeInfo' has unmappable param/return: callback 'TypePluginCompleteTypeInfo' param 'info' unmappable: 'GObject.TypeInfo' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypePluginCompleteTypeInfo"
},
{
"cIdentifier" : "GTypePluginUnuse",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypePluginUnuse"
},
{
"cIdentifier" : "GTypePluginUse",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypePluginUse"
},
{
"cIdentifier" : "GTypeQuery",
"detail" : "no GType registration",
@ -314,40 +248,16 @@
},
{
"cIdentifier" : "GTypeValueCollectFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'TypeValueCollectFunc' has unmappable param/return: callback 'TypeValueCollectFunc' param 'collect_values' unmappable: C array bridging not yet implemented",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypeValueCollectFunc"
},
{
"cIdentifier" : "GTypeValueCopyFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypeValueCopyFunc"
},
{
"cIdentifier" : "GTypeValueFreeFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypeValueFreeFunc"
},
{
"cIdentifier" : "GTypeValueInitFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypeValueInitFunc"
},
{
"cIdentifier" : "GTypeValueLCopyFunc",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'TypeValueLCopyFunc' has unmappable param/return: callback 'TypeValueLCopyFunc' param 'collect_values' unmappable: C array bridging not yet implemented",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypeValueLCopyFunc"
},
{
"cIdentifier" : "GTypeValuePeekPointerFunc",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.TypeValuePeekPointerFunc"
},
{
"cIdentifier" : "GTypeValueTable",
"detail" : "no GType registration",
@ -356,22 +266,10 @@
},
{
"cIdentifier" : "GVaClosureMarshal",
"detail" : "callback planned for Phase D2",
"detail" : "callback 'VaClosureMarshal' has unmappable param/return: callback 'VaClosureMarshal' param 'instance' unmappable: 'GObject.TypeInstance' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.VaClosureMarshal"
},
{
"cIdentifier" : "GValueTransform",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.ValueTransform"
},
{
"cIdentifier" : "GWeakNotify",
"detail" : "callback planned for Phase D2",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.WeakNotify"
},
{
"cIdentifier" : "GWeakRef",
"detail" : "no GType registration",
@ -386,34 +284,178 @@
},
{
"cIdentifier" : "g_boxed_type_register_static",
"detail" : "parameter 'boxed_copy': callback 'GObject.BoxedCopyFunc' not yet supported as a mapped type",
"detail" : "callback param 'boxed_copy' deferred to Phase D4.3",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.boxed_type_register_static"
},
{
"cIdentifier" : "g_cclosure_marshal_BOOLEAN__BOXED_BOXED",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_BOOLEAN__BOXED_BOXED"
},
{
"cIdentifier" : "g_cclosure_marshal_BOOLEAN__FLAGS",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_BOOLEAN__FLAGS"
},
{
"cIdentifier" : "g_cclosure_marshal_STRING__OBJECT_POINTER",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_STRING__OBJECT_POINTER"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__BOOLEAN",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__BOOLEAN"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__BOXED",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__BOXED"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__CHAR",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__CHAR"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__DOUBLE",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__DOUBLE"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__ENUM",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__ENUM"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__FLAGS",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__FLAGS"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__FLOAT",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__FLOAT"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__INT",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__INT"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__LONG",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__LONG"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__OBJECT",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__OBJECT"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__PARAM",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__PARAM"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__POINTER",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__POINTER"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__STRING",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__STRING"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__UCHAR",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__UCHAR"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__UINT",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__UINT"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__UINT_POINTER",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__UINT_POINTER"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__ULONG",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__ULONG"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__VARIANT",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__VARIANT"
},
{
"cIdentifier" : "g_cclosure_marshal_VOID__VOID",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_VOID__VOID"
},
{
"cIdentifier" : "g_cclosure_marshal_generic",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.cclosure_marshal_generic"
},
{
"cIdentifier" : "g_cclosure_new",
"detail" : "parameter 'callback_func': callback 'GObject.Callback' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.cclosure_new"
},
{
"cIdentifier" : "g_cclosure_new_object",
"detail" : "parameter 'callback_func': callback 'GObject.Callback' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.cclosure_new_object"
},
{
"cIdentifier" : "g_cclosure_new_object_swap",
"detail" : "parameter 'callback_func': callback 'GObject.Callback' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.cclosure_new_object_swap"
},
{
"cIdentifier" : "g_cclosure_new_swap",
"detail" : "parameter 'callback_func': callback 'GObject.Callback' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.cclosure_new_swap"
},
{
"cIdentifier" : "g_clear_object",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.clear_object"
},
{
"cIdentifier" : "g_clear_signal_handler",
"detail" : "parameter 'handler_id_ptr' C type 'gulong*' is a pointer",
@ -434,13 +476,13 @@
},
{
"cIdentifier" : "g_signal_group_connect_data",
"detail" : "parameter 'c_handler': callback 'GObject.Callback' not yet supported as a mapped type",
"detail" : "callback param 'c_handler' deferred to Phase D4.3",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.connect_data"
},
{
"cIdentifier" : "g_signal_group_connect_swapped",
"detail" : "parameter 'c_handler': callback 'GObject.Callback' not yet supported as a mapped type",
"detail" : "callback param 'c_handler' deferred to Phase D4.3",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.connect_swapped"
},
@ -600,6 +642,12 @@
"reason" : "unknownType",
"symbol" : "GObject.param_spec_object"
},
{
"cIdentifier" : "g_param_spec_override",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.param_spec_override"
},
{
"cIdentifier" : "g_param_spec_param",
"detail" : "parameter 'nick' is a nullable string",
@ -650,8 +698,8 @@
},
{
"cIdentifier" : "g_param_spec_value_array",
"detail" : "parameter 'nick' is a nullable string",
"reason" : "unknownType",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.param_spec_value_array"
},
{
@ -698,7 +746,7 @@
},
{
"cIdentifier" : "g_signal_add_emission_hook",
"detail" : "parameter 'hook_func': callback 'GObject.SignalEmissionHook' not yet supported as a mapped type",
"detail" : "parameter 'hook_func': callback 'SignalEmissionHook' param 'ihint' unmappable: 'GObject.SignalInvocationHint' has no GType registration or lifetime functions",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.signal_add_emission_hook"
},
@ -710,38 +758,38 @@
},
{
"cIdentifier" : "g_signal_chain_from_overridden_handler",
"detail" : "parameter 'instance': 'GObject.TypeInstance' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_chain_from_overridden_handler"
},
{
"cIdentifier" : "g_signal_connect_data",
"detail" : "parameter 'c_handler': callback 'GObject.Callback' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_connect_data"
},
{
"cIdentifier" : "g_signal_connect_object",
"detail" : "parameter 'instance': 'GObject.TypeInstance' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_connect_object"
},
{
"cIdentifier" : "g_signal_emit",
"detail" : "variadic ('...') parameter",
"reason" : "varargs",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_emit"
},
{
"cIdentifier" : "g_signal_emit_by_name",
"detail" : "variadic ('...') parameter",
"reason" : "varargs",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_emit_by_name"
},
{
"cIdentifier" : "g_signal_emit_valist",
"detail" : "parameter 'instance': 'GObject.TypeInstance' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_emit_valist"
},
{
@ -764,31 +812,31 @@
},
{
"cIdentifier" : "g_signal_new",
"detail" : "parameter 'accumulator': callback 'GObject.SignalAccumulator' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_new"
},
{
"cIdentifier" : "g_signal_new_class_handler",
"detail" : "parameter 'class_handler': callback 'GObject.Callback' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_new_class_handler"
},
{
"cIdentifier" : "g_signal_new_valist",
"detail" : "parameter 'accumulator': callback 'GObject.SignalAccumulator' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_new_valist"
},
{
"cIdentifier" : "g_signal_newv",
"detail" : "parameter 'accumulator': callback 'GObject.SignalAccumulator' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_newv"
},
{
"cIdentifier" : "g_signal_override_class_handler",
"detail" : "parameter 'class_handler': callback 'GObject.Callback' not yet supported as a mapped type",
"detail" : "callback param 'class_handler' deferred to Phase D4.3",
"reason" : "callbackWithoutUserData",
"symbol" : "GObject.signal_override_class_handler"
},
@ -800,20 +848,32 @@
},
{
"cIdentifier" : "g_signal_set_va_marshaller",
"detail" : "parameter 'va_marshaller': callback 'GObject.VaClosureMarshal' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.signal_set_va_marshaller"
},
{
"cIdentifier" : "g_source_set_closure",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.source_set_closure"
},
{
"cIdentifier" : "g_source_set_dummy_callback",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.source_set_dummy_callback"
},
{
"cIdentifier" : "g_type_add_class_cache_func",
"detail" : "parameter 'cache_func': callback 'GObject.TypeClassCacheFunc' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.type_add_class_cache_func"
},
{
"cIdentifier" : "g_type_add_interface_check",
"detail" : "parameter 'check_func': callback 'GObject.TypeInterfaceCheckFunc' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.type_add_interface_check"
},
{
@ -824,8 +884,8 @@
},
{
"cIdentifier" : "g_type_check_class_cast",
"detail" : "parameter 'g_class': 'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.type_check_class_cast"
},
{
@ -842,8 +902,8 @@
},
{
"cIdentifier" : "g_type_check_instance_cast",
"detail" : "parameter 'instance': 'GObject.TypeInstance' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.type_check_instance_cast"
},
{
@ -866,38 +926,38 @@
},
{
"cIdentifier" : "g_type_class_adjust_private_offset",
"detail" : "parameter 'private_size_or_offset' C type 'gint*' is a pointer",
"reason" : "unknownType",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_class_adjust_private_offset"
},
{
"cIdentifier" : "g_type_class_get",
"detail" : "'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_class_get"
},
{
"cIdentifier" : "g_type_class_peek",
"detail" : "'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_class_peek"
},
{
"cIdentifier" : "g_type_class_peek_static",
"detail" : "'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_class_peek_static"
},
{
"cIdentifier" : "g_type_class_ref",
"detail" : "'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_class_ref"
},
{
"cIdentifier" : "g_type_create_instance",
"detail" : "'GObject.TypeInstance' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.type_create_instance"
},
{
@ -936,22 +996,34 @@
"reason" : "unknownType",
"symbol" : "GObject.type_get_plugin"
},
{
"cIdentifier" : "g_type_interface_add_prerequisite",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_interface_add_prerequisite"
},
{
"cIdentifier" : "g_type_interface_get_plugin",
"detail" : "return type: interface return",
"reason" : "unknownType",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_interface_get_plugin"
},
{
"cIdentifier" : "g_type_interface_instantiatable_prerequisite",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_interface_instantiatable_prerequisite"
},
{
"cIdentifier" : "g_type_interface_peek",
"detail" : "parameter 'instance_class': 'GObject.TypeClass' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_interface_peek"
},
{
"cIdentifier" : "g_type_interface_prerequisites",
"detail" : "C array bridging not yet implemented",
"reason" : "arrayWithoutLength",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.type_interface_prerequisites"
},
{
@ -992,34 +1064,46 @@
},
{
"cIdentifier" : "g_type_register_static_simple",
"detail" : "parameter 'class_init': callback 'GObject.ClassInitFunc' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.type_register_static_simple"
},
{
"cIdentifier" : "g_type_remove_class_cache_func",
"detail" : "parameter 'cache_func': callback 'GObject.TypeClassCacheFunc' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.type_remove_class_cache_func"
},
{
"cIdentifier" : "g_type_remove_interface_check",
"detail" : "parameter 'check_func': callback 'GObject.TypeInterfaceCheckFunc' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.type_remove_interface_check"
},
{
"cIdentifier" : "g_type_value_table_peek",
"detail" : "'GObject.TypeValueTable' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.type_value_table_peek"
},
{
"cIdentifier" : "g_value_register_transform_func",
"detail" : "parameter 'transform_func': callback 'GObject.ValueTransform' not yet supported as a mapped type",
"reason" : "callbackWithoutUserData",
"detail" : "notIntrospectable",
"reason" : "notIntrospectable",
"symbol" : "GObject.value_register_transform_func"
},
{
"cIdentifier" : "g_value_type_compatible",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.value_type_compatible"
},
{
"cIdentifier" : "g_value_type_transformable",
"detail" : "deprecatedRemoved",
"reason" : "deprecatedRemoved",
"symbol" : "GObject.value_type_transformable"
},
{
"cIdentifier" : "g_variant_get_gtype",
"detail" : "C symbol 'g_variant_get_gtype' is not exported by the system library",
@ -1035,9 +1119,13 @@
],
"module" : "GObject",
"stats" : {
"boundCallables" : 143,
"boundTypes" : 60,
"boundCallables" : 112,
"boundCallbacks" : 32,
"boundSignals" : 3,
"boundTypes" : 77,
"totalCallables" : 284,
"totalCallbacks" : 34,
"totalSignals" : 3,
"totalTypes" : 122
}
}

View file

@ -60,42 +60,40 @@ struct SmokeTests {
// the process once enough alloc/free cycles run. The loops make such a bug
// deterministic rather than intermittent.
@Test("Boxed record copies a borrowed return and frees it on deinit")
func boxedBorrowedReturnRoundtrips() {
// g_variant_type_checked_ returns a borrowed pointer; the wrapper copies
// it (g_variant_type_copy) and frees the copy (g_variant_type_free) on
// deinit. Freeing the borrowed original instead would corrupt GLib's
// internal type table.
for _ in 0..<5000 {
let t = variantTypeChecked(typeString: "(sias)")
#expect(UInt(bitPattern: t.pointer) != 0)
@Test("Signal roundtrip: notify::source fires on property change, disconnect prevents re-fire")
func notifySignalRoundtrip() {
let group = BindingGroup()
let obj = BindingGroup()
var fired = false
var handler = group.connectNotify(detail: "source") { _, _ in
fired = true
}
group.setSource(source: obj)
#expect(fired)
fired = false
handler.disconnect()
group.setSource(source: nil)
#expect(!fired)
}
@Test("Boxed record adopts a full-transfer return and frees it on deinit")
func boxedFullTransferReturnRoundtrips() throws {
// g_uri_parse returns transfer-full GUri*; the wrapper adopts it and
// frees with g_uri_unref on deinit. A missing unref leaks; a double
// unref aborts.
for _ in 0..<5000 {
let uri = try uriParse(uriString: "https://example.com/a/b?q=1#frag", flags: [])
#expect(UInt(bitPattern: uri.pointer) != 0)
@Test("Non-detailed signal connect: bare notify fires on property mutation")
func nonDetailedSignal() {
let group = BindingGroup()
var fired = false
var handler = group.connectNotify(detail: nil) { _, _ in
fired = true
}
}
@Test("init(retaining:) makes an independently-owned copy")
func boxedRetainingInitIndependentCopy() {
// Copy one base pointer many times through init(retaining:), then free
// every copy. If the copy aliased the base (no real duplication) the
// frees would destroy the base's storage; the final base access would
// then be a use-after-free.
let base = variantTypeChecked(typeString: "as")
var copies: [VariantType] = []
for _ in 0..<2000 {
copies.append(VariantType(retaining: base.pointer))
}
copies.removeAll() // 2000 independent frees
#expect(UInt(bitPattern: base.pointer) != 0) // base survives
let obj = BindingGroup()
group.setSource(source: obj) // property mutation fires bare "notify"
#expect(fired) // handler was invoked
fired = false
handler.disconnect()
group.setSource(source: nil)
#expect(!fired) // disconnected handler not re-invoked
}
// MARK: - Out-parameters marshalled as tuple returns (C3)
@ -243,23 +241,18 @@ struct SmokeTests {
}
}
@Test("Pointer-return throwing function: uriParse adopts result! on success and throws GLibError on malformed URI")
@Test("Throwing function: fileReadLink resolves /etc/localtime and throws on nonexistent path")
func pointerReturnThrowingFunction() throws {
// uriParse returns Uri (boxed record, pointer-backed) via
// `Uri(takingOwnership: _rawPointer(result!))`. This is the tier-1
// symbol the plan's Step 8 enumerated for the pointer-return throwing
// `result!` path.
let uri = try uriParse(uriString: "https://example.com/path", flags: .none)
#expect(UInt(bitPattern: uri.pointer) != 0)
// fileReadLink returns a String (or throws GLibError). This tests the
// throwing function body path with a string return.
let link = try fileReadLink(filename: "/etc/localtime")
#expect(!link.isEmpty)
// Malformed URI: missing scheme. g_uri_parse raises G_URI_ERROR/
// G_URI_ERROR_FAILED assert the throw carries a non-empty message.
do {
_ = try uriParse(uriString: "://bad", flags: .none)
Issue.record("expected uriParse to throw on a malformed URI")
} catch let error as GLibError {
#expect(error.domain != 0)
#expect(!error.message.isEmpty)
_ = try fileReadLink(filename: "/nonexistent/path/that/does/not/exist")
Issue.record("expected fileReadLink to throw")
} catch {
#expect((error as? GLibError) != nil)
}
}
}