Skip to content

std.meta.stringToEnum erroneous compile error with std.meta.FieldEnum #17842

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

Open
gcoakes opened this issue Nov 3, 2023 · 4 comments
Open

std.meta.stringToEnum erroneous compile error with std.meta.FieldEnum #17842

gcoakes opened this issue Nov 3, 2023 · 4 comments
Labels
bug Observed behavior contradicts documented or intended behavior frontend Tokenization, parsing, AstGen, Sema, and Liveness.
Milestone

Comments

@gcoakes
Copy link
Contributor

gcoakes commented Nov 3, 2023

Zig Version

0.12.0-dev.1369+a09ba455c

Steps to Reproduce and Observed Behavior

When std.meta.FieldEnum is used on a struct with a single field, then there is a compile error produced when attempting to convert a string to that enum. i.e.:

const std = @import("std");

test {
    const S = struct { a: usize };
    const E = std.meta.FieldEnum(S);
    try std.testing.expectEqual(@as(?E, E.a), std.meta.stringToEnum(E, "a"));
}

The error:

$ zig test foo.zig
/home/gcoakes/.local/share/zig-linux-x86_64-0.12.0-dev.1369+a09ba455c/lib/std/comptime_string_map.zig:55:63: error: expected type 'meta.FieldEnum(foo.test_0.S)', found 'u0'
                sorted_kvs[i] = .{ .key = kv.@"0", .value = kv.@"1" };
                                                            ~~^~~~~
/home/gcoakes/.local/share/zig-linux-x86_64-0.12.0-dev.1369+a09ba455c/lib/std/meta.zig:563:12: note: enum declared here
    return @Type(.{
           ^~~~~
/home/gcoakes/.local/share/zig-linux-x86_64-0.12.0-dev.1369+a09ba455c/lib/std/comptime_string_map.zig:14:36: note: called from here
    return ComptimeStringMapWithEql(V, kvs_list, defaultEql);
           ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
/home/gcoakes/.local/share/zig-linux-x86_64-0.12.0-dev.1369+a09ba455c/lib/std/meta.zig:38:42: note: called from here
        const map = std.ComptimeStringMap(T, kvs);
                    ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
referenced by:
    test_0: foo.zig:6:68
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

I added a @compileLog in std.meta.stringToEnum logging the kvs_array which shows the value is undefined when it was expected to be E.a:

Compile Log Output:
@as([1]meta.stringToEnum__anon_1077__struct_1083, .{ .{"a", undefined} })

Oddly, this is not the case for a enum created through the normal mechanism:

const std = @import("std");

test {
    // const S = struct { a: usize };
    // const E = std.meta.FieldEnum(S);
    const E = enum { a };
    try std.testing.expectEqual(@as(?E, E.a), std.meta.stringToEnum(E, "a"));
}

Expected Behavior

That first test should compile and pass.

@gcoakes gcoakes added the bug Observed behavior contradicts documented or intended behavior label Nov 3, 2023
@Vexu Vexu added the standard library This issue involves writing Zig code for the standard library. label Nov 3, 2023
@Vexu Vexu added this to the 0.13.0 milestone Nov 3, 2023
@squeek502
Copy link
Collaborator

Seems like it would be due to this line:

.tag_type = std.math.IntFittingRange(0, field_infos.len - 1),

which will set tag_type to u0 in this case.

But this actually passes:

test {
    const E = enum(u0) { a };
    try std.testing.expectEqual(@as(?E, E.a), std.meta.stringToEnum(E, "a"));
    try std.testing.expectEqual(@as(?E, null), std.meta.stringToEnum(E, "b"));
}

and a @compileLog in stringToEnum gives the same output as the failing case:

@as([1]meta.stringToEnum__anon_2392__struct_2785, .{ .{"a", undefined} })

@ianprime0509
Copy link
Contributor

@squeek502's alternate version above with using an enum directly does work, however, constructing the same enum type using @Type reproduces the original issue (all of this output is for 0.12.0-dev.1454+f24ceec35):

const std = @import("std");

test {
    //const E = enum(u0) { a };
    const E = @Type(.{ .Enum = .{
        .tag_type = u0,
        .fields = &.{.{ .name = "a", .value = 0 }},
        .decls = &.{},
        .is_exhaustive = true,
    } });
    try std.testing.expectEqual(@as(?E, E.a), std.meta.stringToEnum(E, "a"));
    try std.testing.expectEqual(@as(?E, null), std.meta.stringToEnum(E, "b"));
}

Output:

/var/home/ian/src/zig/lib/std/comptime_string_map.zig:55:63: error: expected type 'test2.test_0.E', found 'u0'
                sorted_kvs[i] = .{ .key = kv.@"0", .value = kv.@"1" };
                                                            ~~^~~~~
test2.zig:5:15: note: enum declared here
    const E = @Type(.{ .Enum = .{
              ^~~~~
/var/home/ian/src/zig/lib/std/comptime_string_map.zig:14:36: note: called from here
    return ComptimeStringMapWithEql(V, kvs_list, defaultEql);
           ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
/var/home/ian/src/zig/lib/std/meta.zig:38:42: note: called from here
        const map = std.ComptimeStringMap(T, kvs);
                    ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
referenced by:
    test_0: test2.zig:12:69
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

The following example gives more insight into what's going on here by removing std.meta from the equation:

test {
    //const E = enum(u0) { a };
    const E = @Type(.{ .Enum = .{
        .tag_type = u0,
        .fields = &.{.{ .name = "a", .value = 0 }},
        .decls = &.{},
        .is_exhaustive = true,
    } });
    const e: E = @enumFromInt(0);
    @compileLog(@TypeOf(e));
    const T = struct { E };
    const t: T = .{@enumFromInt(0)};
    @compileLog(@typeInfo(T).Struct.fields);
    @compileLog(@TypeOf(t[0]));
}

Compile log output:

@as(type, test2.test_0.E)
@as([]const builtin.Type.StructField, .{ .{.name = "0", .type = test2.test_0.E, .default_value = null, .is_comptime = false, .alignment = 1} })
@as(type, u0)

Compare with the compile log output when the non-@Type enum is used instead:

@as(type, test2.test_0.E)
@as([]const builtin.Type.StructField, .{ .{.name = "0", .type = test2.test_0.E, .default_value = null, .is_comptime = false, .alignment = 1} })
@as(type, test2.test_0.E)

So, somehow, when E is embedded in a tuple, reading that tuple field results in a value of type u0 rather than E, even though the compiler seems to know that the type of the field is E.

By the way, changing T in the example above to a normal struct rather than a tuple also fixes the issue, so this seems like it might be specific to tuples.

@Vexu Vexu added frontend Tokenization, parsing, AstGen, Sema, and Liveness. and removed standard library This issue involves writing Zig code for the standard library. labels Nov 5, 2023
@gcoakes
Copy link
Contributor Author

gcoakes commented Aug 11, 2024

Just retested this, and it still reproduces on the latest master branch. However, the signature changed:

$ ./build/stage3/bin/zig test repro.zig 
build/stage3/lib/zig/std/meta.zig:29:51: error: value stored in comptime field does not match the default value of the field
                kvs_array[i] = .{ enumField.name, @field(T, enumField.name) };
                                                  ^~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    test_0: repro.zig:6:68
$ cat repro.zig
const std = @import("std");

test {
    const S = struct { a: usize };
    const E = std.meta.FieldEnum(S);
    try std.testing.expectEqual(@as(?E, E.a), std.meta.stringToEnum(E, "a"));
}

@ianprime0509
Copy link
Contributor

This looks to be fixed as of 0.14.0-dev.2267+5f3a70ed5 (maybe a result of #21817?). The original test passes, and my @compileLog example now produces the expected output regardless of whether a reified type is used:

test {
    //const E = enum(u0) { a };
    const E = @Type(.{ .@"enum" = .{
        .tag_type = u0,
        .fields = &.{.{ .name = "a", .value = 0 }},
        .decls = &.{},
        .is_exhaustive = true,
    } });
    const e: E = @enumFromInt(0);
    @compileLog(@TypeOf(e));
    const T = struct { E };
    const t: T = .{@enumFromInt(0)};
    @compileLog(@typeInfo(T).@"struct".fields);
    @compileLog(@TypeOf(t[0]));
}

Output (same regardless of which version of E is used):

@as(type, t.test_0.E)
@as([]const builtin.Type.StructField, &.{ .{ .name = "0"[0..1], .type = t.test_0.E, .default_value = @as(*const anyopaque, @ptrCast(@as(t.test_0.E, .a))), .is_comptime = true, .alignment = 1 } }[0..1])
@as(type, t.test_0.E)

@andrewrk andrewrk modified the milestones: 0.14.0, 0.15.0 Feb 10, 2025
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 frontend Tokenization, parsing, AstGen, Sema, and Liveness.
Projects
None yet
Development

No branches or pull requests

5 participants