We have the @reify proposal here: #383. One of the reasons it's not accepted yet is that it's big and scary and has some pretty serious implications for how complicated zig code is allowed to be.
This is a proposal which is a subset of #383 with a smaller scope, which is a builtin function (and I'm going to suggest a different name here) @Type which turns builtin.TypeInfo into a type. In this proposal, if you try to make a struct, you get a compile error "unable to create struct type". The list of supported types would be:
@IntType)@OpaqueType)@Vector)This issue is a prerequisite to #383.
In this proposal, if you try to make a struct, you get a compile error "unable to create struct type"
Could this be conveyed by another name that @Type, like @BuiltinType but more well-thought ?
_Bikeshedding Disclaimer_
@InfoType could work as it's the reverse of @typeInfo and it's the functional inverse of it. Of course @Type is nice and short so I would only suggest a longer name if there were potential name conflicts with @Type.
We can categorize types by whether their type info contains a bounded amount of information, and whether they refer to another type. An error set's type info contains a slice of possible errors, so that's an unbounded amount of information. An optional type contains another type, so that's a recursive type.
The list of types is taken from the docs on @typeInfo
| Type | Unbounded | Recursive |
| --- | --- | --- |
| Type | | |
| Void | | |
| Bool | | |
| NoReturn | | |
| Int | | |
| Float | | |
| Pointer | | :heavy_check_mark: |
| Array | | :heavy_check_mark: |
| Struct | :heavy_check_mark: | :heavy_check_mark: |
| ComptimeFloat | | |
| ComptimeInt | | |
| Undefined | | |
| Null | | |
| Optional | | :heavy_check_mark: |
| ErrorUnion | | :heavy_check_mark: |
| ErrorSet | :heavy_check_mark: | |
| Enum[1] | :heavy_check_mark: | |
| Union[2] | :heavy_check_mark: | :heavy_check_mark: |
| Fn | :heavy_check_mark: | :heavy_check_mark: |
| BoundFn | :heavy_check_mark: | :heavy_check_mark: |
| ArgTuple[3] | :heavy_check_mark: | :heavy_check_mark: |
| Opaque | | |
| Promise | | :heavy_check_mark: |
| Vector | | :heavy_check_mark: |
| EnumLiteral[4] | | |
[1] I'm not counting the decls here. I'm not even sure why enum's are allowed to have decls. And maybe all decls should be moved out of TypeInfo, since they're not relevant to any instance of the type. The rest of this discussion will assume that TypeInfo does not include decls.
[2] The in-memory space used by a union is bounded, but the type info is effectively the same as a struct's type info.
[3] I'm pretty sure ArgTuple should have []type instead of void type info.
[4] EnumLiteral might store the identifier characters, which is kinda unbounded,
but that doesn't seem significant to this discussion.
This proposal seems to be attempting to exclude the :heavy_check_mark: :heavy_check_mark: cases, which means we're missing Type, Enum, and EnumLiteral.
The term "primitive type" to me means types that are bounded and non-recursive. This includes the types you can express with a single keyword in zig (void, u32, comptime_float, noreturn, etc.). There are also some singleton types whose value is a keyword and whose type can be expressed with @typeOf() (undefined, null).
My definition of primitive types also includes Opaque types, which are quite the special case. The OP comment "(this would replace @OpaqueType)" suggests that @Type(.Opaque) is how you would create an opaque type, making opaque types an exception to @Type(@typeInfo(T)) == T. This seems a little strange.
I think there's a good argument to be able to construct pointer types with some builtin function, because pointer types have so much metadata (const, align(N), etc.). While there is not a good reason to support creating optional types (since you could just ?@Type(T),), it makes more sense if we already want to do pointer types. Then we should do all the bounded recursive types, like Array, Vector, Promise. But I think we should call it something other than "primitive types". I don't have a good name for it right now other than "bounded, possibly-recursive types" :man_shrugging: .
Should the @Type builtin only accept TypeInfo or should it also accept any instance of a TypeInfo sub-type? i.e. should this work?
const MyPointerType = @Type(builtin.TypeInfo.Pointer {
.size = builtin.TypeInfo.Pointer.Size.One,
.is_const = false,
.is_volatile = false,
.alignment = 1,
.child = u8,
.is_allowzero = false,
});
Or should we require this?
const MyPointerType = @Type(builtin.TypeInfo {
.Pointer = builtin.TypeInfo.Pointer {
.size = builtin.TypeInfo.Pointer.Size.One,
.is_const = false,
.is_volatile = false,
.alignment = 1,
.child = u8,
.is_allowzero = false,
}
);
Note that it shouldn't be much more code to implement.
Implementation here: https://github.com/ziglang/zig/pull/3111
This is mostly done thanks to @marler8997. The types left to implement are:
-
I'm not certain that we should allow @Type to construct enums. Constructing anything that declares an identifier from a comptime string would make "go to definition" and autocomplete features of an editor extremely difficult to implement (would have to embed an entire comptime VM that works on half-written code), and would mean it's impossible to know you'll be able to grep for the definition of an enum literal (or, soon after, struct field). If you need to write an api that dynamically creates comptime-known values with names that are comptime-known strings, it can be done by exposing a function fn getValue(comptime name: []const u8) GenType, or with code generation. So this rule doesn't limit comptime functionality.
Constructing anything that declares an identifier from a comptime string would make "go to definition" and autocomplete features of an editor extremely difficult to implement (would have to embed an entire comptime VM that works on half-written code)
I think this is already true, you can do something like
fn SomeType() type {
return if (comptime function())
struct { const A = 1; const B = 2; }
else
struct { const A = 2; };
}
I agree, you basically need to be a zig compiler (or at least comptime engine) to correctly do completions etc, adding aggregate type reification does make it slightly more diffciult but not by a huge degreee.
Personally I would be okay with generating any aggregate type as long as you cannot generate functions in its namespace but I do get the arguments against it.
Having enum reification alone will aready let metaprogramming code do a lot more without introducing really surprising behavior.
This is fully implemented now.
Most helpful comment
This is fully implemented now.