diff --git a/src/reflect.zig b/src/reflect.zig index ce25065..35c89e0 100644 --- a/src/reflect.zig +++ b/src/reflect.zig @@ -433,6 +433,15 @@ pub const Func = struct { try self.return_type.lookup(structs); } + fn hasAlloc(comptime self: Func) bool { + for (self.args) |arg| { + if (arg.underT() == std.mem.Allocator) { + return true; + } + } + return false; + } + fn reflect( comptime T: type, comptime kind: FuncKind, @@ -870,6 +879,18 @@ pub const Struct = struct { return attrs; } + // Does the T has a well-formed deinit method? + fn _checkDeinit(comptime T: type, comptime self_T: type, isErr: bool) !void { + if (!isDecl( + T, + "deinit", + fn (_: *self_T, _: std.mem.Allocator) void, + isErr, + )) { + return error.StructAllocWrongDeinit; + } + } + // Is the T a well-formed exception? fn _checkException(comptime T: type, isErr: bool) Error!void { @@ -894,6 +915,13 @@ pub const Struct = struct { if (!isDecl(T, "get_message", fn (_: T) []const u8, isErr)) return err; } + // Has the API a deinit method? + pub fn hasDenit(comptime self: Struct) bool { + std.debug.assert(@inComptime()); + Struct._checkDeinit(self.T, self.Self(), false) catch false; + return true; + } + // Is the API an exception? pub fn isException(comptime self: Struct) bool { std.debug.assert(@inComptime()); @@ -1181,6 +1209,44 @@ pub const Struct = struct { } } + // check deinit + // only if at least one function has an allocator argument + var check_deinit = false; + if (has_constructor and constructor.hasAlloc()) { + check_deinit = true; + } + if (!check_deinit) { + for (getters) |getter| { + if (getter.hasAlloc()) { + check_deinit = true; + break; + } + } + } + if (!check_deinit) { + for (setters) |setter| { + if (setter.hasAlloc()) { + check_deinit = true; + break; + } + } + } + if (!check_deinit) { + for (methods) |method| { + if (method.hasAlloc()) { + check_deinit = true; + break; + } + } + } + if (check_deinit) { + if (self_T) |self| { + try Struct._checkDeinit(T, self, true); + } else { + try Struct._checkDeinit(T, T, true); + } + } + // string tag var string_tag: bool = false; for (getters) |getter| { @@ -1440,6 +1506,7 @@ const Error = error{ StructExceptionWrongErrorSet, StructExceptionWrongInterface, StructExceptionDoesNotExist, + StructAllocWrongDeinit, // func errors FuncNoSelf, @@ -1566,10 +1633,30 @@ const MyException = struct { pub fn get_message(_: MyException) []const u8 { return ""; } + pub fn deinit(_: *MyException, _: std.mem.Allocator) void {} }; const TestStructExceptionDoesNotExist = struct { pub const Exception = MyException; }; +const TestStructAllocNoDeinit = struct { + name: []const u8, + pub fn constructor(alloc: std.mem.Allocator, name: []const u8) TestStructAllocWrongDeinit { + const name_alloc = alloc.alloc(u8, name.len); + @memcpy(name_alloc, name); + return .{ .name = name_alloc }; + } +}; +const TestStructAllocWrongDeinit = struct { + name: []const u8, + pub fn constructor(alloc: std.mem.Allocator, name: []const u8) TestStructAllocWrongDeinit { + const name_alloc = alloc.alloc(u8, name.len); + @memcpy(name_alloc, name); + return .{ .name = name_alloc }; + } + pub fn deinit(_: TestStructAllocWrongDeinit, _: std.mem.Allocator) void { + // should be a pointer + } +}; // funcs tests const TestFuncNoSelf = struct { @@ -1700,6 +1787,14 @@ pub fn tests() !void { .{TestStructExceptionDoesNotExist}, error.StructExceptionDoesNotExist, ); + try ensureErr( + .{TestStructAllocNoDeinit}, + error.StructAllocWrongDeinit, + ); + try ensureErr( + .{TestStructAllocWrongDeinit}, + error.StructAllocWrongDeinit, + ); // funcs checks try ensureErr( diff --git a/src/tests/proto_test.zig b/src/tests/proto_test.zig index bc5b274..1a90a02 100644 --- a/src/tests/proto_test.zig +++ b/src/tests/proto_test.zig @@ -82,6 +82,10 @@ const Person = struct { pub fn get_symbol_toStringTag(_: Person) []const u8 { return "MyPerson"; } + + pub fn deinit(self: *Person, alloc: std.mem.Allocator) void { + alloc.free(self.last_name); + } }; const User = struct { @@ -103,6 +107,10 @@ const User = struct { pub fn get_role(self: User) u8 { return self.role; } + + pub fn deinit(self: *User, alloc: std.mem.Allocator) void { + self.proto.deinit(alloc); + } }; const PersonPtr = struct { @@ -126,6 +134,10 @@ const PersonPtr = struct { @memcpy(name_alloc, name); self.name = name_alloc; } + + pub fn deinit(self: *PersonPtr, alloc: std.mem.Allocator) void { + alloc.free(self.name); + } }; const UserForContainer = struct { @@ -174,6 +186,10 @@ const UserContainer = struct { pub fn _roleVal(self: UserForContainer) u8 { return self.role; } + + pub fn deinit(self: *UserForContainer, alloc: std.mem.Allocator) void { + self.proto.deinit(alloc); + } }; const PersonProtoCast = struct { @@ -192,6 +208,10 @@ const PersonProtoCast = struct { pub fn get_name(self: PersonProtoCast) []const u8 { return self.first_name; } + + pub fn deinit(self: *PersonProtoCast, alloc: std.mem.Allocator) void { + alloc.free(self.first_name); + } }; const UserProtoCast = struct { @@ -202,6 +222,10 @@ const UserProtoCast = struct { pub fn constructor(alloc: std.mem.Allocator, first_name: []u8) UserProtoCast { return .{ .not_proto = PersonProtoCast.constructor(alloc, first_name) }; } + + pub fn deinit(self: *UserProtoCast, alloc: std.mem.Allocator) void { + self.not_proto.deinit(alloc); + } }; // generate API, comptime diff --git a/src/tests/types_complex_test.zig b/src/tests/types_complex_test.zig index 398a38e..76f42fc 100644 --- a/src/tests/types_complex_test.zig +++ b/src/tests/types_complex_test.zig @@ -23,6 +23,10 @@ const MyList = struct { pub fn _symbol_iterator(self: MyList) MyIterable { return MyIterable.init(self.items); } + + pub fn deinit(self: *MyList, alloc: std.mem.Allocator) void { + alloc.free(self.items); + } }; const MyVariadic = struct { @@ -49,6 +53,8 @@ const MyVariadic = struct { pub fn _empty(_: MyVariadic, _: ?VariadicBool) bool { return true; } + + pub fn deinit(_: *MyVariadic, _: std.mem.Allocator) void {} }; const MyErrorUnion = struct { @@ -111,6 +117,8 @@ pub const MyException = struct { ErrorSet.MyCustomError => errorStrings(0), }; } + + pub fn deinit(_: *MyException, _: std.mem.Allocator) void {} }; const MyTypeWithException = struct { diff --git a/src/tests/types_native_test.zig b/src/tests/types_native_test.zig index a43b761..0e7bdf9 100644 --- a/src/tests/types_native_test.zig +++ b/src/tests/types_native_test.zig @@ -25,6 +25,10 @@ const Brand = struct { @memcpy(name_alloc, name); self.name = name_alloc; } + + pub fn deinit(self: *Brand, alloc: std.mem.Allocator) void { + alloc.free(self.name); + } }; const Car = struct { @@ -118,6 +122,10 @@ const Car = struct { pub fn _getBrandPtr(self: Car) *Brand { return self.get_brandPtr(); } + + pub fn deinit(self: *Car, alloc: std.mem.Allocator) void { + alloc.destroy(self.brand_ptr); + } }; // Native types with nested APIs