Type inference through structs containing types
Sometimes it is useful to express a combination of generic types as a struct. For example, I use a struct like this in a key-value database that supports arbitrary types.
const ColumnFamily = struct {
Key: type,
Value: type,
name: []const u8,
};
I wrote some generic functions that accept a ColumnFamily as an input, and use its contained types as the types of the parameters and return of the function. A simplified example:
pub fn get(db: Database, cf: ColumnFamily, key: cf.Key) ?cf.Value {
return db.getStore(cf)).get(key);
}
This works great in zig, but zls has trouble inferring the type of any variable whose type is specified via ColumnFamily.
What Does Work?
Note: I have measured "successful type inference by zls" by checking that VS code shows a type hint for the variable.
ZLS has no trouble inferring types through layers of indirection, as long as they are supported forms of indirection.
type variable
You can assign a concrete type to a variable T of type type, and zls will successfully infer the concrete type of any variables that have their type specified as T
test "indirect type inference" {
const T: type = u8;
const start: T = 123;
const item = start; // zls infers item as u8
try std.testing.expect(u8 == @TypeOf(item));
}
generic function with type parameter
ZLS can infer the type returned from a generic function when the type is passed as a parameter to the function.
fn identity(comptime T: type, item: T) T {
return item;
}
test "double indirect type inference via type param" {
const T: type = u8;
const item = identity(T, 123); // zls infers item as u8
try std.testing.expect(u8 == @TypeOf(item));
}
generic function with anytype
The same also works when the parameter's type is inferred rather than being specified with a type parameter.
fn anyIdentity(item: anytype) @TypeOf(item) {
return item;
}
test "indirect type inferece via anytype fn" {
const start: u8 = 123;
const item = anyIdentity(start); // zls infers item as u8
try std.testing.expect(u8 == @TypeOf(item));
}
What Doesn't Work?
If all you do is wrap a type in a struct and directly specify it as the type of a variable (similar to the first example, but now with a wrapper struct), ZLS cannot infer the type:
const TypeSpec = struct { T: type };
test TypeSpec {
const spec = TypeSpec{ .T = u8 };
const start: spec.T = 123;
const item = start; // zls is unable to infer item's type here
try std.testing.expect(u8 == @TypeOf(item));
}
Likewise, it does not work when passed through a function, either.
const TypeSpec = struct { T: type };
fn specIdentity(comptime spec: TypeSpec, item: spec.T) spec.T {
return item;
}
test specIdentity {
const spec = TypeSpec{ .T = u8 };
const item = specIdentity(spec, 123); // zls is unable to infer item's type here
try std.testing.expect(u8 == @TypeOf(item));
}