Skip to content
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

Union enums, @TagType, and "error: runtime cast to union '…' which has non-void fields" or "error: cast to union '…' must initialize '…' field '…'" #7532

Closed
kivikakk opened this issue Dec 23, 2020 · 1 comment · Fixed by #7538
Labels
bug Observed behavior contradicts documented or intended behavior stage1 The process of building from source via WebAssembly and the C backend.
Milestone

Comments

@kivikakk
Copy link
Contributor

This one caught me by surprise:

const std = @import("std");

const UnionEnum = union(enum) {
    A: u8,

    pub fn format(self: UnionEnum, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
        try std.fmt.format(writer, "Fancy {}", .{self.A});
    }
};

test "" {
    const x = UnionEnum{ .A = 123 };
    std.debug.print("{}\n", .{x});
    std.debug.print("{}\n", .{@as(@TagType(UnionEnum), x)});
}

zig test example.zig gives:

/Users/kameliya/Code/zig/build-native/lib/zig/std/fmt.zig:386:20: error: cast to union 'UnionEnum' must initialize 'u8' field 'A'
        return try value.format(fmt, options, writer);
                   ^
/Users/kameliya/Code/zig/build-native/lib/zig/std/fmt.zig:356:23: note: called from here
        try formatType(
                      ^
/Users/kameliya/Code/zig/build-native/lib/zig/std/io/writer.zig:33:34: note: called from here
            return std.fmt.format(self, format, args);
                                 ^
/Users/kameliya/Code/zig/build-native/lib/zig/std/debug.zig:65:27: note: called from here
    nosuspend stderr.print(fmt, args) catch return;
                          ^
./example.zig:14:20: note: called from here
    std.debug.print("{}\n", .{@as(@TagType(UnionEnum), x)});

I hit this through many more layers of comptime parameters so it was super cryptic what was happening at first. Now it's pretty clear:

  • std.fmt.formatType checks if (comptime std.meta.trait.hasFn("format")(T))
  • this is true even for the @TagType(…) variant, i.e. the enum — it shares its decls with the union (? wording?)
  • it then calls the user-supplied format function with the enum value as the first argument, which attempts and fails the enum-to-union cast

I think in my mental model I assumed the enum wouldn't also possess the decls, but it's not really clear that they should belong to one and not the other.

I don't know exactly what might've made this easier; perhaps std.fmt.format could check the signature of the format function it's calling? Really bad hack that catches this kind of error:

    if (comptime std.meta.trait.hasFn("format")(T)) {
        const formatFn = @typeInfo(@TypeOf(@field(T, "format"))).Fn;
        if (formatFn.args.len != 4 or formatFn.args[0].arg_type != T) { 
            @compileError("NOPE");
        }    
        return try value.format(fmt, options, writer);
    }    

But maybe this would reject some valid cases I'm not thinking about right now.

@Vexu
Copy link
Member

Vexu commented Dec 23, 2020

The issues is indeed with @TagType(U) also having the decls of U which seems like a bug to me.

const U = union(enum) {
    a: u8,
    const foo = 1;
};
test "" {
    @import("std").testing.expect(!@hasDecl(@TagType(U), "foo"));
}

@Vexu Vexu added bug Observed behavior contradicts documented or intended behavior stage1 The process of building from source via WebAssembly and the C backend. labels Dec 23, 2020
@Vexu Vexu added this to the 0.9.0 milestone Dec 23, 2020
LemonBoy added a commit to LemonBoy/zig that referenced this issue Dec 24, 2020
Making the enum type share the scope with the parent union means every
declaration "bleeds" into the enum scope.
Let's mint a fresh empty scope for the enum type.

Thanks to @Vexu for the test case.

Closes ziglang#7532
Vexu pushed a commit that referenced this issue Dec 24, 2020
Making the enum type share the scope with the parent union means every
declaration "bleeds" into the enum scope.
Let's mint a fresh empty scope for the enum type.

Thanks to @Vexu for the test case.

Closes #7532
@Vexu Vexu modified the milestones: 0.9.0, 0.8.0 Dec 24, 2020
aarvay pushed a commit to aarvay/zig that referenced this issue Jan 4, 2021
Making the enum type share the scope with the parent union means every
declaration "bleeds" into the enum scope.
Let's mint a fresh empty scope for the enum type.

Thanks to @Vexu for the test case.

Closes ziglang#7532
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Observed behavior contradicts documented or intended behavior stage1 The process of building from source via WebAssembly and the C backend.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants