-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tagged union switch prong capture doesn't coerce noreturn
/ uninstantiable field type
#20187
Comments
imo having a |
@nektro That would break the use case of generic code I mentioned in "Expected Behavior". |
Here's my status-quo workaround until it's fixed if someone else needs it:
/// aka `noreturn`-like, aka `never` type
pub fn isTypeUninstantiable(comptime T: type) bool {
return switch (@typeInfo(T)) {
else => @compileError("TODO"),
.Type, .Void, .Bool, .Int, .Float, .ComptimeFloat, .ComptimeInt, .Optional, .Fn, .Opaque, .EnumLiteral => false,
.NoReturn => true,
inline .Pointer, .Array, .Vector => |x| comptime isTypeUninstantiable(x.child), //note: We COULD categorize all 0-length arrays and vectors as instantiable... I'm not sure whether that's the sensible default though.
.Struct => |s| inline for (s.fields) |field| {
if (comptime isTypeUninstantiable(field.type)) break true;
} else false,
.Union => |u| inline for (u.fields) |field| {
if (comptime !isTypeUninstantiable(field.type)) break false;
} else true,
.Undefined => @compileError("TODO (seems unlikely we want it, reconsider use case if encountered)"),
.Null => @compileError("TODO (seems unlikely, but trivial to add if encountered)"),
.ErrorUnion => |u| comptime (isTypeUninstantiable(u.error_set) and isTypeUninstantiable(u.payload)),
.ErrorSet => |u| if (u) |e| (e.len == 0) else false,
.Enum => |e| comptime isTypeUninstantiable(e.tag_type) or (e.is_exhaustive and (e.fields.len == 0)),
};
}
pub fn InstantiableUnionStatesPayload(comptime Union: type, comptime states: []const @typeInfo(Union).Union.tag_type.?) ?type {
const union_fields_info = @typeInfo(Union).Union.fields;
comptime var found_type: ?type = null;
comptime var next_union_field_index = 0;
inline for(states) |state| {
const state_name = @tagName(state);
while (next_union_field_index < union_fields_info.len) {
const next_union_field_info = union_fields_info[next_union_field_index];
next_union_field_index += 1;
//@compileLog(state_name, next_union_field_info.name, std.mem.eql(u8, next_union_field_info.name, state_name));
if (!@import("std").mem.eql(u8, next_union_field_info.name, state_name)) continue;
const field_type = next_union_field_info.type;
if (comptime isTypeUninstantiable(field_type)) { // ignore this one, continue with the next state
break;
}
if (found_type) |f| {
if (f != field_type) @compileError("found differing field types: " ++ @typeName(f) ++ " != " ++ @typeName(field_type)); //alternative, if ever wanted: found_type = @TypeOf(f, field_type);
} else found_type = field_type;
break;
} else @compileError("unreachable: argument 'states' is incorrectly ordered or contains duplicates"); //TODO: Check whether union_fields_info and states are both canonically sorted to provide a better error message.
}
return found_type;
}
pub fn instantiableUnionStatesPayload(union_instance: anytype, comptime states: []const @typeInfo(@TypeOf(union_instance)).Union.tag_type.?) (InstantiableUnionStatesPayload(@TypeOf(union_instance), states) orelse @compileError("all given states have uninstantiable payload types")) {
const UnionTag = @typeInfo(@TypeOf(union_instance)).Union.tag_type.?;
const instance_state_tag = @as(UnionTag, union_instance);
inline for(states) |state| {
if (instance_state_tag == state) return @field(union_instance, @tagName(state));
} else unreachable; //the union instance is in none of the given states
}
test instantiableUnionStatesPayload {
var u: union(enum) { //tagged union type with an uninstantiable state payload type
a: void,
b: noreturn,
} = .a;
_ = &u;
_ = switch (u) {
//.a, .b => |p| p, //shared prong with capture triggers "expected type 'void', found 'noreturn'"
.a, .b => {
const p = instantiableUnionStatesPayload(u, &.{.a, .b});
_ = p;
},
};
} |
Zig Version
0.13.0-dev.365+332fbb4b0
Steps to Reproduce and Observed Behavior
In a tagged
union
type, uninstantiable field/payload types (such asnoreturn
, but also f.e.enum{}
) signal that values of the union type will never be in these states (see also #15909 ).As such, accesses to them are
unreachable
, including theirswitch
prongs.The issue is that when sharing a prong with capture between multiple cases, the compiler currently doesn't seem to perform the usual coercion logic that
noreturn
(and arguably all uninstantiable types) are never reachable, and so should coerce with values of all other types.Test case:
Output of
zig test
on the file:Expected Behavior
The shared prong with capture should be allowed by the compiler by generating the cases with uninstantiable types as implicit unreachable, just as is done without capture.
This becomes useful when writing generic code, where a single
switch
in source code can handle values of various union types. A field can be instantiable for some types and not for others.The text was updated successfully, but these errors were encountered: