Emit isolated deinit on root class wrappers, fix five review findings
This commit is contained in:
parent
57ff84bce5
commit
4f1016f682
12 changed files with 288 additions and 111 deletions
|
|
@ -81,6 +81,48 @@ struct CallbackGenerationTests {
|
|||
#expect(entry.reason == .callbackWithoutUserData)
|
||||
}
|
||||
|
||||
// MARK: - Parameter/return nullability (review finding #2)
|
||||
|
||||
@Test("Callback param and return nullability propagate into the mapped closure type")
|
||||
func callbackNullabilityPropagates() throws {
|
||||
let glib = Repository(namespaces: [
|
||||
Namespace(name: "GLib", version: "2.0",
|
||||
callbacks: [Callback(
|
||||
name: "NullableFunc", cType: "GNullableFunc",
|
||||
parameters: [
|
||||
Parameter(name: "value", type: .string, cType: "const char*", isNullable: true),
|
||||
],
|
||||
returnValue: ReturnValue(type: .string, isNullable: true)
|
||||
)])
|
||||
])
|
||||
let registry = TypeRegistry(repositories: ["GLib": glib])
|
||||
let ctx = MapContext(registry: registry, currentModule: "GLib", currentNamespace: "GLib")
|
||||
let mapping = try map(.typeRef("NullableFunc", namespace: "GLib"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
#expect(mapping.swiftType.contains("String?"))
|
||||
#expect(mapping.swiftType.hasSuffix("-> String?"))
|
||||
}
|
||||
|
||||
@Test("A non-nullable callback param/return stays non-optional")
|
||||
func callbackNonNullableStaysNonOptional() throws {
|
||||
let glib = Repository(namespaces: [
|
||||
Namespace(name: "GLib", version: "2.0",
|
||||
callbacks: [Callback(
|
||||
name: "PlainFunc", cType: "GPlainFunc",
|
||||
parameters: [
|
||||
Parameter(name: "value", type: .string, cType: "const char*", isNullable: false),
|
||||
],
|
||||
returnValue: ReturnValue(type: .string, isNullable: false)
|
||||
)])
|
||||
])
|
||||
let registry = TypeRegistry(repositories: ["GLib": glib])
|
||||
let ctx = MapContext(registry: registry, currentModule: "GLib", currentNamespace: "GLib")
|
||||
let mapping = try map(.typeRef("PlainFunc", namespace: "GLib"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
#expect(!mapping.swiftType.contains("String?"))
|
||||
#expect(mapping.swiftType.hasSuffix("-> String"))
|
||||
}
|
||||
|
||||
// MARK: - Renderer infrastructure (constructed directly — D4.3 render-side proof)
|
||||
|
||||
/// A `.callbackBox(scope: .call, …)` parameter that doubles as its own
|
||||
|
|
|
|||
|
|
@ -255,6 +255,11 @@ struct ParserSemanticsTests {
|
|||
<type name="gint" c:type="int"/>
|
||||
</array>
|
||||
</parameter>
|
||||
<parameter name="argv">
|
||||
<array c:type="char**">
|
||||
<type name="utf8" c:type="char*"/>
|
||||
</array>
|
||||
</parameter>
|
||||
</parameters>
|
||||
</method>
|
||||
</class>
|
||||
|
|
@ -282,6 +287,16 @@ struct ParserSemanticsTests {
|
|||
return
|
||||
}
|
||||
#expect(fixedInfo.fixedSize == 4)
|
||||
|
||||
// No zero-terminated/length/fixed-size attribute at all: GIR omits
|
||||
// zero-terminated for a plain NULL-terminated array (e.g. GStrv), so
|
||||
// absence must default to true — not false.
|
||||
guard case .cArray(_, let absentInfo) = params[4].type else {
|
||||
Issue.record("expected a C array for 'argv'")
|
||||
return
|
||||
}
|
||||
#expect(absentInfo.isZeroTerminated)
|
||||
#expect(absentInfo.hasKnownLength)
|
||||
}
|
||||
|
||||
@Test("Container element types are captured")
|
||||
|
|
|
|||
|
|
@ -129,6 +129,29 @@ struct PropertyGenerationTests {
|
|||
#expect(source.contains(" public var length: Int32"))
|
||||
}
|
||||
|
||||
@Test("A root class's isolated deinit unrefs its pointer via g_object_unref")
|
||||
func rootClassEmitsUnrefDeinit() throws {
|
||||
let prop = Property(name: "length", type: .int32, isReadable: true, isWritable: false)
|
||||
let (source, plan, _) = renderClass(named: "Buffer", properties: [prop])
|
||||
#expect(plan.unrefFunc == "g_object_unref")
|
||||
#expect(source.contains("isolated deinit {"))
|
||||
#expect(source.contains("g_object_unref(pointer)"))
|
||||
}
|
||||
|
||||
@Test("A class rooting a non-GObject fundamental unrefs through _instancePointer")
|
||||
func fundamentalRootClassUnrefsTypedPointer() throws {
|
||||
let plan = ClassPlan(
|
||||
name: "ParamSpec", girName: "GObject.ParamSpec", cType: "GParamSpec",
|
||||
getTypeFunction: "g_param_spec_get_type",
|
||||
refFunc: "g_param_spec_ref", unrefFunc: "g_param_spec_unref"
|
||||
)
|
||||
let module = ModulePlan(module: "GObject", types: [.class(plan)], skips: [],
|
||||
coverage: CoverageStats())
|
||||
let source = renderModule(module)["ParamSpec.swift"] ?? ""
|
||||
#expect(source.contains("isolated deinit {"))
|
||||
#expect(source.contains("g_param_spec_unref(_instancePointer(pointer))"))
|
||||
}
|
||||
|
||||
@Test("Writable string property generates both a getter and a setter")
|
||||
func writableStringProperty() throws {
|
||||
let prop = Property(name: "label", type: .string, isReadable: true, isWritable: true)
|
||||
|
|
|
|||
|
|
@ -376,15 +376,40 @@ struct TypeMapperTests {
|
|||
@Test("Alias follows to underlying type")
|
||||
func aliasFollowsToUnderlying() throws {
|
||||
let ctx = makeContext()
|
||||
// GLib.Strv is an alias for a zero-terminated string array.
|
||||
// Without array bridging, this throws .arrayWithoutLength — but the
|
||||
// alias is *followed*, which is what this test asserts.
|
||||
// GLib.Strv is an alias for a zero-terminated string array, which has
|
||||
// a known length (zero-termination) but array bridging itself isn't
|
||||
// implemented yet — .arrayBridgingUnimplemented, not
|
||||
// .arrayWithoutLength. The alias is *followed*, which is what this
|
||||
// test asserts.
|
||||
do {
|
||||
_ = try map(.typeRef("Strv", namespace: "GLib"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
Issue.record("Expected arrayWithoutLength error")
|
||||
Issue.record("Expected arrayBridgingUnimplemented error")
|
||||
} catch {
|
||||
#expect(error.reason == .arrayWithoutLength)
|
||||
#expect(error.reason == .arrayBridgingUnimplemented)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Cyclic type alias throws instead of overflowing the stack")
|
||||
func cyclicAliasThrows() throws {
|
||||
let cyclic = Repository(namespaces: [
|
||||
Namespace(
|
||||
name: "Cyclic", version: "1.0",
|
||||
aliases: [
|
||||
Alias(name: "A", cType: "CyclicA", target: .typeRef("B", namespace: "Cyclic")),
|
||||
Alias(name: "B", cType: "CyclicB", target: .typeRef("A", namespace: "Cyclic")),
|
||||
]
|
||||
)
|
||||
])
|
||||
let registry = TypeRegistry(repositories: ["Cyclic": cyclic])
|
||||
let ctx = MapContext(registry: registry, currentModule: "Cyclic", currentNamespace: "Cyclic")
|
||||
do {
|
||||
_ = try map(.typeRef("A", namespace: "Cyclic"),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
Issue.record("Expected unknownType error from cyclic alias detection")
|
||||
} catch {
|
||||
#expect(error.reason == .unknownType)
|
||||
#expect(error.detail.contains("cyclic"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -425,15 +450,15 @@ struct TypeMapperTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test("cArray with length throws arrayWithoutLength (not yet bridged)")
|
||||
@Test("cArray with length throws arrayBridgingUnimplemented (not yet bridged)")
|
||||
func cArrayWithLengthNotYetBridged() {
|
||||
let ctx = makeContext()
|
||||
do {
|
||||
_ = try map(.cArray(.int32, ArrayInfo(lengthParameterIndex: 0)),
|
||||
nullable: false, transfer: .none, context: ctx)
|
||||
Issue.record("Expected arrayWithoutLength error")
|
||||
Issue.record("Expected arrayBridgingUnimplemented error")
|
||||
} catch {
|
||||
#expect(error.reason == .arrayWithoutLength)
|
||||
#expect(error.reason == .arrayBridgingUnimplemented)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue