Open SpexGuy opened 3 years ago
By the way we already have noreturn
.
noreturn
is the type of:break
,continue
,return
,unreachable
,while (true) {}
@TypeOf(@compileError("")) == .NoReturn
I ran into a sort of interesting use-case for this. Like many projects, I have a type for a runtime-sized list with a comptime-known maximum length. However, mine is extern
(ideally packed
, but that has broken codegen at the time of writing) so that I can embed these lists in a larger structure and then use read/writeStruct
to serialize the entire thing. This simplifies memory management and has some performance benefits, notably reducing the number of reads/writes that need to be issued.
Today I saw #9134, which looks great and would ideally replace my own container type, but it returns a standard struct (that is, non-extern
, non-packed
). So it couldn't be used with read/writeStruct
. I set out to see if I could change the layout, and it turns out you can do this today:
fn makeLayout(comptime T: type, new_layout: std.builtin.TypeInfo.ContainerLayout) type {
if (@typeInfo(T) != .Struct) {
@compileError("expected struct, got " ++ @tagName(@typeInfo(T)));
}
const struct_info = @typeInfo(T).Struct;
return @Type(.{ .Struct = .{
.layout = new_layout,
.fields = struct_info.fields,
.decls = struct_info.decls,
.is_tuple = struct_info.is_tuple,
} });
}
However, I quickly noticed that a couple types I tested this on failed to compile because it triggers the compile errors on deprecated decls. So while this would work initially, if deprecated members were ever added to ArrayListFixed
, my code would suddenly fail to compile. Without a way to futureproof against this, it seems that I would be better off maintaining my own implementation.
I am interested in seeing at least the @TypeOf(expr) == noreturn
and @TypeOf(incompatible, types) == noreturn
component of this proposal realized.
In particular, being able to check whether two types can have peer type resolution performed on them would be very useful for being able to construct more complex type check error messages.
I have a particular use case, in wanting to modify std.hash_map.verifyContext
: currently, it has hard checks, such that for adapted contexts, it will issue a compile error even if the inputted key can coerce to the expected PseudoKey
(e.g. *const [n:0]u8
, []u8
, [:0]const u8
, aren't accepted in map.getOrPutAdapted(string_literal, Adapted)
, where Adapted
accepts []const u8
as PseudoKey
s).
One way to solve that problem would be to just remove the checks, but then the resulting error message in the case where the types really aren't compatible, become much less useful than they currently are.
The second way would be to implement a function in userland that checks whether the types are peer-type-resolvable, but that adds extra maintenance burden.
The third, and imo simplest way to address this would be to allow @TypeOf
given multiple values to return null
, or noreturn
, or whatever else.
I have a branch with the basic necessary modifications to Sema for the third option, and I would be happy to go through and update everything else if given the go-ahead.
Using .NoReturn
for this case doesn't make sense because it represents an expression that causes a runtime exit of the target artifact. whereas .CompileError
would mean an expression that halts compilation and cannot be allowed to exist in runtime or comptime.
one other solution to @TypeOf(incompatible, types)
is also to modify it to return ?type
instead of modifying std.builtin.Type
.
Making @TypeOf(incompatible, types) == null
is actually what I've done in my branch. The bigger question becomes what do do with @TypeOf(val)
. It would be consistent to have it also return null, but that would then mean all code that uses it and wants to just assume current behavior has to be changed to things like fn foo(a: anytype, b: @TypeOf(a).?) void
. It's not the worst, but it does seem odd to require for such a common use case.
well for that case in the meantime they would have to add .?
but long term im hopeful for https://github.com/ziglang/zig/issues/9260 which would change that to fn foo(a: infer A, b: infer B) void
or fn foo(a: infer T, b: T) void
Another idea that just came to mind would be to make it return a specific type - either a primitive (compilationerror
), or a declaration in std.builtin
(e.g. std.builtin.CompileError
) - whose only purpose is to be returned from a @TypeOf
call that's given invalid peer types.
This would allow essentially all code to remain as it is, but adds the ability to do things like @TypeOf(<expr>, ...) == std.builtin.CompileError
.
A minimal fix here might be to add a .type
field to Declarations.
There might be a good reason why this isn't already the case, but if not, this would allow introspection of container types, without taking a field access on them, and triggering a compile error. Whether a decl const oops = @compileError("do not touch!");
should have a special CompileError
type, or just NoReturn
, seems less important than providing a way to get at that type without a field access.
An important use case in Zig is the ability to load a module and use reflection to inspect the decarations it exposes. Unfortunately, it's also common to see modules that use
pub const foo = @compileError(..);
for deprecation, specialization, or other purposes like translation errors from translate-c. These two approaches are at odds with each other. This proposal suggests a way to keep the current use of@compileError
and still inspect reflection data without triggering errors.TypeInfo
with tag.CompileError
..CompileError
should be an empty tag, because allowing the metaprogram to inspect the error string could cause difficult to find dependencies on compiler versions.@Type(.CompileError)
which will be used in the decls of the type info of the containing struct.@Type(.CompileError)
is a compile error.@compileError
but also declarations that are invalid for other reasons.@TypeOf
cannot cause a compile error. Instead,@TypeOf(<code that causes compile error>)
returns.CompileError
. This could be useful for a lot of use cases that are currently difficult, likeAll of these use cases are technically solvable with just (1) through (5), but require the use of an intermediate struct whose type info can be inspected. This last point makes that use case a bit cleaner, and unifies the behaviors of "figuring out the type of a declaration for reflection" and "using
@TypeOf
".