Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require a deinit function when allocator is used by an API #143

Merged
merged 1 commit into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/reflect.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {

Expand All @@ -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());
Expand Down Expand Up @@ -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| {
Expand Down Expand Up @@ -1440,6 +1506,7 @@ const Error = error{
StructExceptionWrongErrorSet,
StructExceptionWrongInterface,
StructExceptionDoesNotExist,
StructAllocWrongDeinit,

// func errors
FuncNoSelf,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
24 changes: 24 additions & 0 deletions src/tests/proto_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/tests/types_complex_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -111,6 +117,8 @@ pub const MyException = struct {
ErrorSet.MyCustomError => errorStrings(0),
};
}

pub fn deinit(_: *MyException, _: std.mem.Allocator) void {}
};

const MyTypeWithException = struct {
Expand Down
8 changes: 8 additions & 0 deletions src/tests/types_native_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down