diff --git a/README.CN.md b/README.CN.md index c35137f..b4c4098 100644 --- a/README.CN.md +++ b/README.CN.md @@ -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"; + } + } + } +} + ``` ## 实现接口 diff --git a/README.md b/README.md index 94cff8c..16784a0 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,22 @@ 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 @@ -181,7 +197,7 @@ pub const INameAndAge struct { // 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 diff --git a/src/test.zig b/src/test.zig index 80bd5c6..b08468d 100644 --- a/src/test.zig +++ b/src/test.zig @@ -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, @@ -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, @@ -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)); diff --git a/src/zoop.zig b/src/zoop.zig index 9844ca7..500d955 100644 --- a/src/zoop.zig +++ b/src/zoop.zig @@ -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 }; } }; @@ -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, @@ -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; @@ -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 })); } @@ -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 { @@ -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;