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:
parent
dd2cd7a689
commit
5abb48e889
11 changed files with 2259 additions and 987 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) */"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
167
Tests/SwiftGtkGenCoreTests/CallbackGenerationTests.swift
Normal file
167
Tests/SwiftGtkGenCoreTests/CallbackGenerationTests.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
105
Tests/SwiftGtkGenCoreTests/SignalGenerationTests.swift
Normal file
105
Tests/SwiftGtkGenCoreTests/SignalGenerationTests.swift
Normal 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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue