Thread overview
Mutually recursive template expansion
Oct 01, 2021
Stephen
Oct 01, 2021
jfondren
Oct 02, 2021
Stephen
Oct 02, 2021
Basile B.
Oct 04, 2021
bauss
Oct 04, 2021
Paul Backus
Oct 02, 2021
Imperatorn
October 01, 2021

I've been trying out templates in more depth and one thing I was wondering was whether template expansions with circular dependencies might work.
Here is an example that doesn't work:

mixin(A!());
mixin(B!());

void main() {}

template A() {
    const char[] A = q{
        struct Ar {
            Br b;
        }
    };
}

template B() {
    const char[] B = q{
        struct Br {
            Ar a;
        }
    };
}

This code should work should mutual recursion be supported. How might I get it to work properly (without sacrificing recursion or templates)?

October 01, 2021

On Friday, 1 October 2021 at 14:03:06 UTC, Stephen wrote:

>

This code should work should mutual recursion be supported.

It still wouldn't work, because structs are value types and it's impossible to say how large either struct is:

Error: struct mutualrec.Ar no size because of forward reference

With s/struct/class/ it still wouldn't work because this is a mixin problem rather than a problem of template mutual recursion:

mixin(q{ class Ar { Br b; } });
mixin(q{ class Br { Ar b; } });

mutualrec2.d-mixin-1(1): Error: undefined identifier Br, did you mean class Ar?

This seems like a surprising limitation of mixin, though, which isn't highlighted by the spec.

October 02, 2021

On Friday, 1 October 2021 at 14:26:39 UTC, jfondren wrote:

>

On Friday, 1 October 2021 at 14:03:06 UTC, Stephen wrote:

>

This code should work should mutual recursion be supported.

It still wouldn't work, because structs are value types and it's impossible to say how large either struct is:

Error: struct mutualrec.Ar no size because of forward reference

With s/struct/class/ it still wouldn't work because this is a mixin problem rather than a problem of template mutual recursion:

mixin(q{ class Ar { Br b; } });
mixin(q{ class Br { Ar b; } });

mutualrec2.d-mixin-1(1): Error: undefined identifier Br, did you mean class Ar?

This seems like a surprising limitation of mixin, though, which isn't highlighted by the spec.

All right I'll try to design without mutual recursion but mixins don't seem to work if they're put out of order.
(i.e. this works:

struct Ar { Br b; ubyte a; }
struct Br { ubyte b; }

but not this:

mixin(q{struct Ar { Br b; ubyte a; }});
mixin(q{struct Br { ubyte b; }});

).
Is this by design?

October 02, 2021

On Saturday, 2 October 2021 at 08:48:24 UTC, Stephen wrote:

>

Is this by design?

No but it's easily explainable.

without mixins

struct Ar { Br b; ubyte a; }
struct Br { ubyte b; }

Ar semantic starts, members are analyzed, that begins with the variable declaration Br b. Br type needs to be resolved. Br can be found in the AST as it is declared manually. Now Br is run and finally Ar sema continues.

with mixins

mixin(q{struct Ar { Br b; ubyte a; }});
mixin(q{struct Br { ubyte b; }});

The first mixin is compiled (compileIt() in dmd code base).

The AST is now the same as obtained by parsing

struct Ar { Br b; ubyte a; }
mixin(q{struct Br { ubyte b; }});

Ar semantic starts, members are analyzed, that begins with the variable declarationBr b. But Br cannot be resolved because it is not yet in the AST and the symbol tables.

October 02, 2021

On Friday, 1 October 2021 at 14:03:06 UTC, Stephen wrote:

>

I've been trying out templates in more depth and one thing I was wondering was whether template expansions with circular dependencies might work.
Here is an example that doesn't work:

mixin(A!());
mixin(B!());

void main() {}

template A() {
    const char[] A = q{
        struct Ar {
            Br b;
        }
    };
}

template B() {
    const char[] B = q{
        struct Br {
            Ar a;
        }
    };
}

This code should work should mutual recursion be supported. How might I get it to work properly (without sacrificing recursion or templates)?

Just curious since I haven't found a use case for this myself, what's the benefit rather than having a separate struct?

October 04, 2021

On Friday, 1 October 2021 at 14:26:39 UTC, jfondren wrote:

>

On Friday, 1 October 2021 at 14:03:06 UTC, Stephen wrote:

>

This code should work should mutual recursion be supported.

It still wouldn't work, because structs are value types and it's impossible to say how large either struct is:

Error: struct mutualrec.Ar no size because of forward reference

With s/struct/class/ it still wouldn't work because this is a mixin problem rather than a problem of template mutual recursion:

mixin(q{ class Ar { Br b; } });
mixin(q{ class Br { Ar b; } });

mutualrec2.d-mixin-1(1): Error: undefined identifier Br, did you mean class Ar?

This seems like a surprising limitation of mixin, though, which isn't highlighted by the spec.

Actually it is covered by the spec.

See:

https://dlang.org/spec/expression.html#mixin_expressions

It clearly says:

Each AssignExpression in the ArgumentList is evaluated at compile time

Which means that Br cannot be used in Ar since it cannot be evaluated at compile time during the mixin of Ar.

October 04, 2021

On Monday, 4 October 2021 at 11:38:04 UTC, bauss wrote:

>

Actually it is covered by the spec.

See:

https://dlang.org/spec/expression.html#mixin_expressions

It clearly says:

Each AssignExpression in the ArgumentList is evaluated at compile time

Which means that Br cannot be used in Ar since it cannot be evaluated at compile time during the mixin of Ar.

That's not what that sentence means. Here's the full version, without the part you cut off:

>

Each AssignExpression in the ArgumentList is evaluated at compile time, and the result must be representable as a string. The resulting strings are concatenated to form a string. The text contents of the string must be compilable as a valid Expression, and is compiled as such.

In other words: the string being mixed in is what's evaluated at compile-time. The code inside the string is treated the same way as any other code.