1
0
Fork 0

Implement record, constant, alias codegen plus all override applications

This commit is contained in:
Brendan Szymanski 2026-07-01 13:47:14 -04:00
parent 56ca762fac
commit a732fcaad9
2 changed files with 580 additions and 26 deletions

View file

@ -97,3 +97,294 @@ func testGenerateEnum() throws {
#expect(output.contains("case fill"))
#expect(output.contains("case start"))
}
@Test("Generates record with fields and methods")
func testGenerateRecord() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gtk", version: "4.0", records: [
Record(name: "Rectangle", cType: "GdkRectangle",
fields: [
Field(name: "x", type: .int32),
Field(name: "y", type: .int32),
Field(name: "width", type: .int32),
Field(name: "height", type: .int32),
], methods: [
Method(name: "contains_point", cIdentifier: "gdk_rectangle_contains_point",
parameters: [
Parameter(name: "rect", type: .typeRef("Rectangle"), isInstanceParameter: true),
Parameter(name: "x", type: .int32),
Parameter(name: "y", type: .int32),
], returnType: .boolean),
])
])
])
let config = GenerationConfig(library: "Gtk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: ["Gtk.Rectangle"], manual: [], ignore: [], objects: [])
let generator = CodeGenerator(config: config)
let files = try generator.generateFiles(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
let output = files["Rectangle.swift"] ?? ""
#expect(output.contains("public struct Rectangle"))
#expect(output.contains("public let x: Int32"))
#expect(output.contains("public let y: Int32"))
#expect(output.contains("public let width: Int32"))
#expect(output.contains("public let height: Int32"))
#expect(output.contains("mutating func containsPoint"))
#expect(output.contains("gdk_rectangle_contains_point(&self, x, y)"))
}
@Test("Generates constant declaration")
func testGenerateConstant() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gtk", version: "4.0", constants: [
Constant(name: "MAJOR_VERSION", value: "4", type: .int32),
])
])
let config = GenerationConfig(library: "Gtk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: ["Gtk.MAJOR_VERSION"], manual: [], ignore: [], objects: [])
let generator = CodeGenerator(config: config)
let files = try generator.generateFiles(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
let output = files["MajorVersion.swift"] ?? ""
#expect(output.contains("public let majorVersion: Int32 = 4"))
}
@Test("Generates typealias declaration")
func testGenerateAlias() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gtk", version: "4.0", aliases: [
Alias(name: "Allocation", cType: "GtkAllocation", target: .typeRef("Rectangle")),
])
])
let config = GenerationConfig(library: "Gtk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: ["Gtk.Allocation"], manual: [], ignore: [], objects: [])
let generator = CodeGenerator(config: config)
let files = try generator.generateFiles(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
let output = files["Allocation.swift"] ?? ""
#expect(output.contains("public typealias Allocation = Rectangle"))
}
@Test("Parameterized signal generates trampoline with extraction")
func testParameterizedSignalTrampoline() {
let signal = Signal(name: "size-allocate", parameters: [
Parameter(name: "allocation", type: .typeRef("Rectangle")),
Parameter(name: "width", type: .int32),
], returnType: .void)
let code = CodeGenerator.generateSignalConnection(signal: signal)
#expect(code.contains("connectSizeAllocate"))
#expect(code.contains("handler as AnyObject"))
#expect(code.contains("Unmanaged<AnyObject>.fromOpaque(data!).takeUnretainedValue()"))
#expect(!code.contains("// Manual"))
#expect(!code.contains("nil, nil, nil"))
#expect(code.contains("g_signal_connect_data(pointer, \"size-allocate\""))
}
@Test("Class generation applies MainActor override")
func testMainActorOverride() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gtk", version: "4.0", classes: [
Class(name: "Widget", cType: "GtkWidget", parent: nil)
])
])
let config = GenerationConfig(library: "Gtk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: [], manual: [], ignore: [], objects: [
.object("Gtk.Widget", overrides: ObjectOverrides(concurrency: .mainActor))
])
let generator = CodeGenerator(config: config)
let output = try generator.generate(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
#expect(output.contains("@MainActor"))
#expect(output.contains("public final class Widget"))
}
@Test("Class generation applies Sendable conformance")
func testSendableOverride() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gdk", version: "4.0", classes: [
Class(name: "Texture", cType: "GdkTexture", parent: nil)
])
])
let config = GenerationConfig(library: "Gdk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: [], manual: [], ignore: [], objects: [
.object("Gdk.Texture", overrides: ObjectOverrides(concurrency: .sendable))
])
let generator = CodeGenerator(config: config)
let output = try generator.generate(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
#expect(output.contains("extension Texture: Sendable {}"))
}
@Test("Class generation applies cfgCondition")
func testCfgConditionOverride() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gtk", version: "4.0", classes: [
Class(name: "Widget", cType: "GtkWidget", parent: nil)
])
])
let config = GenerationConfig(library: "Gtk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: [], manual: [], ignore: [], objects: [
.object("Gtk.Widget", overrides: ObjectOverrides(cfgCondition: "os(macOS)"))
])
let generator = CodeGenerator(config: config)
let output = try generator.generate(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
#expect(output.contains("#if os(macOS)"))
#expect(output.contains("#endif"))
}
@Test("Method override applies visibility and ignore")
func testMethodOverrides() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gtk", version: "4.0", classes: [
Class(name: "Widget", cType: "GtkWidget", parent: nil, methods: [
Method(name: "show", cIdentifier: "gtk_widget_show",
parameters: [
Parameter(name: "widget", type: .typeRef("Widget"), isInstanceParameter: true)
], returnType: .void),
Method(name: "hide", cIdentifier: "gtk_widget_hide",
parameters: [
Parameter(name: "widget", type: .typeRef("Widget"), isInstanceParameter: true)
], returnType: .void),
])
])
])
let config = GenerationConfig(library: "Gtk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: [], manual: [], ignore: [], objects: [
.function("Gtk.Widget", "show", overrides: FunctionOverrides(ignore: true)),
.function("Gtk.Widget", "hide", overrides: FunctionOverrides(visibility: .internal)),
])
let generator = CodeGenerator(config: config)
let output = try generator.generate(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
#expect(!output.contains("func show"))
#expect(output.contains("internal func hide"))
#expect(!output.contains("public func hide"))
}
@Test("Signal override ignores signal")
func testSignalOverrideIgnore() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gtk", version: "4.0", classes: [
Class(name: "Widget", cType: "GtkWidget", parent: nil, signals: [
Signal(name: "map", parameters: [], returnType: .void),
Signal(name: "unmap", parameters: [], returnType: .void),
])
])
])
let config = GenerationConfig(library: "Gtk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: [], manual: [], ignore: [], objects: [
.signal("Gtk.Widget", "unmap", overrides: SignalOverrides(ignore: true)),
])
let generator = CodeGenerator(config: config)
let output = try generator.generate(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
#expect(output.contains("connectMap"))
#expect(!output.contains("connectUnmap"))
}
@Test("Inhibit signal generates Bool-returning handler")
func testInhibitSignalHandling() {
let signal = Signal(name: "delete-event", parameters: [], returnType: .boolean)
let code = CodeGenerator.generateSignalConnection(signal: signal, inhibit: true)
#expect(code.contains("() -> Bool"))
#expect(code.contains("gboolean"))
#expect(code.contains("result ? 1 : 0"))
}
@Test("Function pattern override applies glob rename")
func testFunctionPatternOverride() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gtk", version: "4.0", classes: [
Class(name: "Widget", cType: "GtkWidget", parent: nil, methods: [
Method(name: "get_parent", cIdentifier: "gtk_widget_get_parent",
parameters: [
Parameter(name: "widget", type: .typeRef("Widget"), isInstanceParameter: true)
], returnType: .typeRef("Widget")),
])
])
])
let config = GenerationConfig(library: "Gtk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: [], manual: [], ignore: [], objects: [
.functionPattern("Gtk.Widget", pattern: "get_*",
rename: RenameRule(regex: "^get_", replacement: "")),
])
let analyzer = Analyzer(config: config)
let analysis = analyzer.analyze(repository: repo)
let generator = CodeGenerator(config: config)
let output = try generator.generate(repository: repo, analysis: analysis)
#expect(output.contains("func parent"))
#expect(!output.contains("func getParent"))
}
@Test("Constructor override generates convenience init")
func testConstructorOverride() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gtk", version: "4.0", classes: [
Class(name: "Label", cType: "GtkLabel", parent: "Widget", methods: [
Method(name: "new", cIdentifier: "gtk_label_new",
parameters: [
Parameter(name: "str", type: .string)
], returnType: .typeRef("Widget")),
])
])
])
let config = GenerationConfig(library: "Gtk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: [], manual: [], ignore: [], objects: [
.function("Gtk.Label", "new", overrides: FunctionOverrides(constructor: true)),
])
let generator = CodeGenerator(config: config)
let output = try generator.generate(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
#expect(output.contains("convenience init"))
#expect(output.contains("gtk_label_new(str)"))
}
@Test("Record generation included in generateFiles output")
func testRecordInGenerateFiles() throws {
let repo = Repository(namespaces: [
Namespace(name: "Gdk", version: "4.0", records: [
Record(name: "RGBA", cType: "GdkRGBA",
fields: [
Field(name: "red", type: .double),
Field(name: "green", type: .double),
Field(name: "blue", type: .double),
Field(name: "alpha", type: .double),
])
])
])
let config = GenerationConfig(library: "Gdk", version: "4.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: ["Gdk.RGBA"], manual: [], ignore: [], objects: [])
let generator = CodeGenerator(config: config)
let files = try generator.generateFiles(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
#expect(files.keys.contains("RGBA.swift"))
let output = files["RGBA.swift"] ?? ""
#expect(output.contains("public struct RGBA"))
#expect(output.contains("public let red: Double"))
#expect(output.contains("public let green: Double"))
#expect(output.contains("public let blue: Double"))
#expect(output.contains("public let alpha: Double"))
}
@Test("Constant and alias in single-file generate")
func testConstantAndAliasInGenerate() throws {
let repo = Repository(namespaces: [
Namespace(name: "GLib", version: "2.0", constants: [
Constant(name: "VERSION", value: "\"2.0\"", type: .string),
], aliases: [
Alias(name: "Type", cType: "GType", target: .uint64),
])
])
let config = GenerationConfig(library: "GLib", version: "2.0", girsDirectories: [],
targetDirectory: "", externalLibraries: [],
generate: [], manual: [], ignore: [], objects: [])
let generator = CodeGenerator(config: config)
let output = try generator.generate(repository: repo, analysis: Analyzer(config: config).analyze(repository: repo))
#expect(output.contains("public let version: String = \"2.0\""))
#expect(output.contains("public typealias Type = UInt64"))
}