Skip to content

Commit

Permalink
interface support methods default implementing
Browse files Browse the repository at this point in the history
  • Loading branch information
zhuyadong committed Sep 20, 2024
1 parent e747a43 commit 57058b7
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 25 deletions.
15 changes: 15 additions & 0 deletions README.CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,21 @@ pub const INameAndAge struct {
return self.ptr == other.ptr;
}
}
// 接口还可以提供`api`的缺省实现, 这样申明实现接口的类可以不实现这些接口,
// 仍然能正确编译和工作 (接口就变成了抽象类)
pub const IName = struct {
...// 和上面一样
pub fn Default(comptime Class: type) type {
return struct {
pub fn getName(_: *Class) []const u8 {
return "default name";
}
}
}
}
```

## 实现接口
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,30 @@ pub const INameAndAge struct {
return self.ptr == other.ptr;
}
}
// Interfaces can also provide default implementations of methods,
// so that classes that declare to implement interfaces can still
// compile and work correctly without implementing these methods
// (the interface becomes an abstract class)
pub const IName = struct {
...// Same as above code
pub fn Default(comptime Class: type) type {
return struct {
pub fn getName(_: *Class) []const u8 {
return "default name";
}
}
}
}
```

## Implementing the interface
```zig
// We let `Human` implement the `IName` interface
pub const Human = struct {
pub const extends = .{IName};
...//Same as above code
...// Same as above code
};
// Let `SuperMan` implement the `IAge` interface
Expand Down
7 changes: 2 additions & 5 deletions src/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ pub const IHuman = struct {
};

pub const Human = struct {
pub const extends = .{ zoop.IFormat, IAge };
pub const extends = .{IAge};
const Self = *Human;
iface: IHuman align(zoop.alignment) = zoop.nil(IHuman),
age: u8 = 99,
Expand Down Expand Up @@ -214,7 +214,7 @@ pub const SubSub = struct {
};

pub const Custom = struct {
pub const extends = .{ zoop.IFormat, ISetName };
pub const extends = .{ISetName};

super: SubSub align(zoop.alignment),
age: u16 = 9999,
Expand Down Expand Up @@ -346,9 +346,6 @@ test "zoop" {
try t.expect(hinfo == cinfo);
try t.expect(sinfo == cinfo);
try t.expect(ssinfo == cinfo);
try t.expect(hinfo.getVtableOf(Custom, zoop.IFormat) == sinfo.getVtableOf(Human, zoop.IFormat));
try t.expect(hinfo.getVtableOf(Custom, zoop.IFormat) == sinfo.getVtableOf(Sub, zoop.IFormat));
try t.expect(hinfo.getVtableOf(Custom, zoop.IFormat) == sinfo.getVtableOf(SubSub, zoop.IFormat));
try t.expect(hinfo.getVtableOf(Custom, IAge) == sinfo.getVtableOf(Human, IAge));
try t.expect(hinfo.getVtableOf(Custom, IAge) == sinfo.getVtableOf(Sub, IAge));
try t.expect(hinfo.getVtableOf(Custom, IAge) == sinfo.getVtableOf(SubSub, IAge));
Expand Down
92 changes: 73 additions & 19 deletions src/zoop.zig
Original file line number Diff line number Diff line change
Expand Up @@ -70,27 +70,30 @@ pub const Nil = struct {
pub const IObject = struct {
ptr: *anyopaque,
vptr: *anyopaque,
};

pub const IRaw = struct {
ptr: *anyopaque,
vptr: *anyopaque,
pub fn formatAny(self: IObject, writer: std.io.AnyWriter) anyerror!void {
try icall(self, .formatAny, .{writer});
}

pub fn cast(self: IRaw, comptime I: type) I {
return I{ .ptr = self.ptr, .vptr = self.vptr };
pub fn format(self: *const IObject, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try (self.*).formatAny(if (@TypeOf(writer) == std.io.AnyWriter) writer else writer.any());
}

pub fn Default(comptime Class: type) type {
return struct {
pub fn formatAny(self: *Class, writer: std.io.AnyWriter) anyerror!void {
try writer.print("{}", .{self});
}
};
}
};

pub const IFormat = struct {
pub const IRaw = struct {
ptr: *anyopaque,
vptr: *anyopaque,

pub fn formatAny(self: IFormat, writer: std.io.AnyWriter) anyerror!void {
try icall(self, .formatAny, .{writer});
}

pub fn format(self: *const IFormat, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try (self.*).formatAny(if (@TypeOf(writer) == std.io.AnyWriter) writer else writer.any());
pub fn cast(self: IRaw, comptime I: type) I {
return I{ .ptr = self.ptr, .vptr = self.vptr };
}
};

Expand Down Expand Up @@ -267,7 +270,7 @@ pub fn ApiEnum(comptime I: type) type {
for (std.meta.declarations(I)) |decl| {
const info = @typeInfo(@TypeOf(@field(I, decl.name)));
if (info == .Fn and info.Fn.params.len > 0 and !info.Fn.is_generic) {
const first = info.Fn.params[0].type.?;
const first = info.Fn.params[0].type orelse unreachable;
const Self = switch (@typeInfo(first)) {
else => void,
.Struct => first,
Expand Down Expand Up @@ -746,6 +749,19 @@ fn RealType(comptime T: type) type {
}
}

fn DefaultMethodType(comptime T: type, comptime I: type, comptime name: []const u8) type {
comptime {
if (@hasDecl(I, "Default")) {
const def = I.Default(T);
if (@hasDecl(def, name)) {
return @TypeOf(@field(def, name));
}
}

return void;
}
}

fn MethodType(comptime T: type, comptime name: []const u8) type {
comptime {
var Cur = if (isKlassType(T)) T.Class else T;
Expand Down Expand Up @@ -1073,9 +1089,9 @@ fn makeVtable(comptime T: type, comptime I: type) *anyopaque {
if (MT != void) {
checkApi(T, I, field.name);
@field(val, field.name) = @ptrCast(&getMethod(T, field.name));
} else if (field.default_value) |def_ptr| {
const defval = @as(*align(1) const field.type, @ptrCast(def_ptr)).*;
@field(val, field.name) = defval;
} else if (DefaultMethodType(T, I, field.name) != void) {
checkInterface(I);
@field(val, field.name) = &@field(I.Default(T), field.name);
} else {
@compileError(compfmt("{s} must implement method '{s}: {}'", .{ @typeName(T), field.name, field.type }));
}
Expand All @@ -1086,6 +1102,46 @@ fn makeVtable(comptime T: type, comptime I: type) *anyopaque {
}
}

fn checkInterface(comptime I: type) void {
comptime {
if (!isInterfaceType(I)) @compileError(compfmt("{s} is not Interface.", .{@typeName(I)}));
if (@hasDecl(I, "Default")) {
const field = @field(I, "Default");
switch (@typeInfo(@TypeOf(field))) {
else => @compileError("{s}.Default must be: fn (comptime T:type) type"),
.Fn => |info| {
if (!info.is_generic or info.return_type != type)
@compileError("{s}.Default must be: fn (comptime T:type) type");
},
}
const methods = I.Default(struct {});
if (@typeInfo(methods) != .Struct) {
@compileError(compfmt("{s}.Default() must return a struct but:{}", .{ @typeName(I), methods }));
}
for (std.meta.declarations(methods)) |decl| {
if (!@hasDecl(I, decl.name))
@compileError(compfmt("'{s}' is not a '{s}' api but in '{s}.Default'.", .{ decl.name, @typeName(I), @typeName(I) }));
const definfo = @typeInfo(@TypeOf(@field(methods, decl.name)));
const info = @typeInfo(@TypeOf(@field(I, decl.name)));
var fail = false;
if (definfo == .Fn and info == .Fn) {
if (definfo.Fn.params.len == info.Fn.params.len and definfo.Fn.params.len > 0) {
for (1..definfo.Fn.params.len) |i| {
if (definfo.Fn.params[i].type != info.Fn.params[i].type) {
fail = true;
break;
}
}
} else fail = true;
} else fail = true;
if (fail) {
@compileError(compfmt("type missmatch: {s}.{s}, {s}.Default.{s}", .{ @typeName(I), decl.name, @typeName(I), decl.name }));
}
}
}
}
}

/// Check whether the type of the function named field in T and the pointer of the function with the same name in VT match
fn checkApi(comptime T: type, comptime I: type, comptime field: []const u8) void {
comptime {
Expand Down Expand Up @@ -1243,8 +1299,6 @@ fn isExclude(comptime I: type, comptime method: []const u8) bool {

fn VtableDirect(comptime I: type) type {
comptime {
if (I == IObject) return struct {};

const decls = std.meta.declarations(I);
var fields: [decls.len]StructField = undefined;
var idx = 0;
Expand Down

0 comments on commit 57058b7

Please sign in to comment.