zig icon indicating copy to clipboard operation
zig copied to clipboard

inconsistent dependency loop error when attemping to reify indirectly self-referential type

Open LordMZTE opened this issue 2 years ago • 6 comments

Zig Version

0.12.0-dev.1396+f6de3ec96

Steps to Reproduce and Observed Behavior

Create a file bug.zig:

const std = @import("std");

fn Union(comptime T: type) type {
    const enum_fields = &[1]std.builtin.Type.EnumField{.{
        .name = "x",
        .value = 0,
    }};

    const Enum = @Type(.{ .Enum = .{
        .tag_type = u1,
        .fields = enum_fields,
        .decls = &.{},
        .is_exhaustive = true,
    } });

    const union_fields = &[1]std.builtin.Type.UnionField{.{
        .name = "x",
        .type = T,
        .alignment = @alignOf(T), // This causes the error.
    }};

    return @Type(.{ .Union = .{
        .layout = .Auto,
        .tag_type = Enum,
        .fields = union_fields,
        .decls = &.{},
    } });
}

test {
    _ = struct {
        const A = struct { b: *B };
        const B = Union(A);
    }.B;
}
  1. zig test bug.zig
bug.zig:33:9: error: dependency loop detected
        const B = Union(A);
        ^~~~~~~~~~~~~~~~~~

This might seem logical at first, as A's alignment seems to be dependent on B. However, if the implementation of Union is replaced with this:

return union(enum) {
    x: T,
};

the compiler can correctly infer the alignment of the x field to be 8.

Considering this works, it seems logical to assume that the @Type-based implementation also does.

Expected Behavior

The code compiles.

LordMZTE avatar Nov 05 '23 13:11 LordMZTE

In my use-case, this seems to be fixable by setting .alignment = 0, making the compiler infer it. We should probably document that this is a valid value and consider setting the default value of the field to 0.

LordMZTE avatar Nov 06 '23 17:11 LordMZTE

Possibly related to #17255 - that issue does not use comptime, but does use @alignOf.

dweiller avatar Nov 07 '23 03:11 dweiller

Nope, unrelated. This issue isn't really a bug - resolving the alignment of T requires evaluating Union(A), triggering the dependency loop. The behaviour that makes this not work is that reified struct types do not keep laztly alignment values lazy for future resolution, instead resolving the entire reified type immediately. This could be changed, but the merits of doing so are unclear.

However, we should definitely document the fields of std.builtin.Type better.

mlugg avatar Nov 07 '23 04:11 mlugg

The following variation of the originally reported code however does compile:

const std = @import("std");

fn Union(comptime T: type) type {
    const enum_fields = &[1]std.builtin.Type.EnumField{.{
        .name = "x",
        .value = 0,
    }};

    const Enum = @Type(.{ .@"enum" = .{
        .tag_type = u1,
        .fields = enum_fields,
        .decls = &.{},
        .is_exhaustive = true,
    } });

    const union_fields = &[1]std.builtin.Type.UnionField{.{
        .name = "x",
        .type = T,
        .alignment = @alignOf(T), // This causes the error.
    }};

    return @Type(.{ .@"union" = .{
        .layout = .auto,
        .tag_type = Enum,
        .fields = union_fields,
        .decls = &.{},
    } });
}

pub fn main() void {
    const decls = struct {
        const A = struct { b: *B };
        const B = Union(A);
    };
    var t: decls.A = undefined;
    const B0 = decls.B; // if this line is shifted up above the preceding one, the error is back
    _ = B0;
    _ = &t;
}

It however yields a dependency loop error, if the declaration of B0 is put before declaration of t. It looks as if somehow the dependency loop is or is not an issue, depending on at which point it's entered first.

vadim-za avatar Jan 28 '25 14:01 vadim-za

Reduction of the above:

const A = struct { b: *B };
const B = @Type(.{ .@"struct" = .{
    .layout = .auto,
    .is_tuple = false,
    .fields = &.{.{
        .name = "x",
        .type = A,
        .is_comptime = false,
        .default_value_ptr = null,
        .alignment = @alignOf(A),
    }},
    .decls = &.{},
} });
comptime {
    _ = @as(A, undefined); // try commenting this line
    _ = B;
}

build-obj on this file succeeds. If you comment out the marked line, a dependency loop compile error is emitted.

mlugg avatar Aug 10 '25 19:08 mlugg

Please notice that in 0.15.2 the .alignment = 0 workaround no longer works, since 0 is not accepted as the alignment value any longer.

vadim-za avatar Nov 22 '25 13:11 vadim-za