Erroneous dependency cycle

I have defined three structs that can be reduced to this:

fn Something(T: type) type {
    const info = @typeInfo(T).@"struct";
    return @Type(.{
        .@"struct" = .{
            .layout = .auto,
            .fields = info.fields,
            .decls = &.{},
            .is_tuple = false,
        },
    });
}

const A = struct {
    b: ?*B,
};

const B = struct {
    A: Something(A),
};

const C = struct {
    A: Something(A),
};

test {
    const a: A = undefined;
    const b: B = undefined;
    const c: C = undefined;
    _ = a; _ = b; _ = c;
}

Everything appears to work as intended however when I only instantiate C , I get the error struct 'main.A' depends on itself. What is going on?

Reproduction
fn Something(T: type) type {
    const info = @typeInfo(T).@"struct";
    return @Type(.{
        .@"struct" = .{
            .layout = .auto,
            .fields = info.fields,
            .decls = &.{},
            .is_tuple = false,
        },
    });
}

const A = struct {
    b: ?*B,
};

const B = struct {
    A: Something(A),
};

const C = struct {
    A: Something(A),
};

test {
    // const a: A = undefined;
    // const b: B = undefined;
    const c: C = undefined; // error: struct 'main.A' depends on itself
    // _ = a;
    // _ = b;
    _ = c;
}

The TLDR version is to make the field with the same type as the parent into a pointer of that type.

pub const Node {
    next: *Node, // good
    prev: Node   // bad
}

There is no way for the compiler to determine the size of the struct, as it would put it into a loop. If you use a pointer, then the total size of the struct can be calculated regardless.

Because A refers to B via a pointer the compiler (can) should already be able to determine the sizes of the structs. The issue is that Something(A) creates a cyclic dependency between A and B.

It gets even weirder:

test {
  @compileLog(@sizeOf(A)); // if only this line exists, the compile error occurs
  @compileLog(@sizeOf(B)); // if only this line exists, the correct size is logged
  @compileLog(@sizeOf(C)); // if only this line exists, the compile error occurs

}

Your example can be reduced to the following:

const A = struct {
    my_field: blk: {
        _ = @typeInfo(A);
        break :blk void;
    },
};

test {
    @compileLog(A);
}

If @typeInfo is called on a struct before its field types can be resolved, a dependency loop will be detected.

2 Likes

I see, that makes sense. It seems unintended however that the order of type resolution affects the outcome.

If B is referenced before C, both can be resolved because by the time C is referenced, all fields of A are already known through B.

test "this works" {
  @compileLog(B);
  @compileLog(C);
}

test "self referential struct error" {
  @compileLog(C);
  @compileLog(B);
}

Going a step further, is there a way I can avoid this issue? Is it possible to force the order types are resolved with some hack?

1 Like

I think order shouldn’t matter, when it does, I think that is a bug in the compiler.

To me this seems like a case where the compiler gives up too eagerly / early.

1 Like