// FunctionGenerationTests.swift // Covers Phase C1: namespace-level function planning and rendering — the // C-type bridging that lets primitive, enum, bitfield, and const-string // callables generate compilable Swift. These lock in the boundary decisions // (numericCast for width-ambiguous integers, withCString for input strings, // and the skips for varargs / mutable buffers / string vectors) so a // regression surfaces here rather than as a mysterious drop in gate coverage. import Testing @testable import GObjectGeneratorCore @Suite("Function generation") struct FunctionGenerationTests { /// A registry-backed context in the GLib module. No named types are needed: /// these tests exercise primitives, strings, and bitfields only. func makeContext() -> MapContext { let glib = Repository(namespaces: [ Namespace( name: "GLib", version: "2.0", bitfields: [ Bitfield(name: "LogLevelFlags", cType: "GLogLevelFlags", getTypeFunction: "g_log_level_flags_get_type") ] ) ]) let registry = TypeRegistry(repositories: ["GLib": glib]) return MapContext(registry: registry, currentModule: "GLib", currentNamespace: "GLib") } /// Renders a single planned callable to Swift source via the public module /// renderer, returning the merged `Functions.swift` body it lands in. func render(_ plan: CallablePlan) -> String { let module = ModulePlan( module: "GLib", types: [.callable(plan)], skips: [], coverage: CoverageStats() ) let files = renderModule(module) return files["Functions.swift"] ?? "" } // MARK: - Plannable callables @Test("Width-ambiguous integer parameters bridge through numericCast") func integerParameterBridging() throws { let fn = GlobalFunction( name: "free_sized", cIdentifier: "g_free_sized", parameters: [ Parameter(name: "mem", type: .pointer, cType: "gpointer"), Parameter(name: "size", type: .size, cType: "gsize"), ] ) guard case .success(let plan) = planFunction(fn, context: makeContext()) else { Issue.record("expected g_free_sized to plan successfully"); return } #expect(plan.parameters.count == 2) #expect(plan.parameters[1].mapping.marshalIn == .numericCast(targetType: "UInt")) let body = render(plan) #expect(body.contains("g_free_sized(mem, numericCast(size))")) } @Test("Const string parameters are bridged with withCString") func stringParameterBridging() throws { let fn = GlobalFunction( name: "set_application_name", cIdentifier: "g_set_application_name", parameters: [ Parameter(name: "application_name", type: .string, cType: "const char*") ] ) guard case .success(let plan) = planFunction(fn, context: makeContext()) else { Issue.record("expected const-string function to plan successfully"); return } #expect(plan.parameters[0].mapping.marshalIn == .stringToC) let body = render(plan) #expect(body.contains("func setApplicationName(applicationName: String)")) #expect(body.contains("applicationName.withCString { cString0 in")) #expect(body.contains("g_set_application_name(cString0)")) } @Test("Bitfield parameters and returns round-trip through the C flags type") func bitfieldRoundTrip() throws { let fn = GlobalFunction( name: "log_set_always_fatal", cIdentifier: "g_log_set_always_fatal", parameters: [ Parameter(name: "fatal_mask", type: .typeRef("LogLevelFlags"), cType: "GLogLevelFlags") ], returnValue: ReturnValue(type: .typeRef("LogLevelFlags")) ) guard case .success(let plan) = planFunction(fn, context: makeContext()) else { Issue.record("expected bitfield function to plan successfully"); return } let body = render(plan) #expect(body.contains("GLogLevelFlags(rawValue: numericCast(fatalMask.rawValue))")) #expect(body.contains("LogLevelFlags(rawValue: numericCast(")) } // MARK: - Skips @Test("Variadic functions are skipped with the varargs reason") func variadicSkip() throws { let fn = GlobalFunction( name: "build_filename", cIdentifier: "g_build_filename", parameters: [ Parameter(name: "first_element", type: .string, cType: "const char*"), Parameter(name: "...", type: .void), ] ) guard case .skip(let entry) = planFunction(fn, context: makeContext()) else { Issue.record("expected variadic function to be skipped"); return } #expect(entry.reason == .varargs) } @Test("Mutable string buffers and string vectors are skipped") func mutableAndVectorStringSkips() throws { let mutableBuffer = GlobalFunction( name: "utf8_strncpy", cIdentifier: "g_utf8_strncpy", parameters: [Parameter(name: "dest", type: .string, cType: "char*")] ) guard case .skip = planFunction(mutableBuffer, context: makeContext()) else { Issue.record("expected mutable string buffer to be skipped"); return } let stringVector = GlobalFunction( name: "cmp_strv", cIdentifier: "g_cmp_strv", parameters: [Parameter(name: "arg1", type: .string, cType: "const char* const*")] ) guard case .skip = planFunction(stringVector, context: makeContext()) else { Issue.record("expected string vector to be skipped"); return } } @Test("A scalar return type whose c:type is a pointer with no array length is skipped") func pointerToScalarReturnSkip() throws { // Mirrors gdk_pixbuf_read_pixels: GIR names the return `guint8` but // c:type is `const guint8*` — a raw buffer pointer, not a scalar. let fn = GlobalFunction( name: "read_pixels", cIdentifier: "gdk_pixbuf_read_pixels", parameters: [], returnValue: ReturnValue(type: .uint8, cType: "const guint8*") ) guard case .skip(let entry) = planFunction(fn, context: makeContext()) else { Issue.record("expected pointer-to-scalar return to be skipped"); return } #expect(entry.reason == .unknownType) } @Test("Unexported C symbols never reach a plan") func missingSymbolSkip() throws { // g_open is a `#define g_open open` macro on non-Windows: not a real // symbol. The planner must skip it before rendering an uncompilable call. var skips: [SkipEntry] = [] var types: [TypePlan] = [] let fn = GlobalFunction(name: "open", cIdentifier: "g_open") let bound = planOrSkipFunctionForTest(&skips, &types, fn) #expect(bound == 0) #expect(types.isEmpty) #expect(skips.count == 1) } /// Test shim over the private `planOrSkipFunction`, exercised through the /// public planner so the missing-symbol gate is covered end to end. private func planOrSkipFunctionForTest( _ skips: inout [SkipEntry], _ types: inout [TypePlan], _ fn: GlobalFunction ) -> Int { let ns = Namespace(name: "GLib", version: "2.0", functions: [fn]) 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 plans = planModules(analysis: analysis, registry: registry) let module = plans["GLib"] skips = module?.skips ?? [] types = module?.types ?? [] return module?.coverage.boundCallables ?? 0 } }