Thread overview
[Issue 20443] Case where code compiles depending on order of declaration
Dec 12, 2019
Daniel X
Dec 12, 2019
Walter Bright
Dec 12, 2019
Walter Bright
Dec 13, 2019
Daniel X
December 12, 2019
https://issues.dlang.org/show_bug.cgi?id=20443

Daniel X <redapple82570@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |redapple82570@gmail.com

--- Comment #1 from Daniel X <redapple82570@gmail.com> ---
further reduced, thanks to Paul Backus (author of 'sumtype' who generously looked into this issue for us)

---
struct SumType(T) {
    import std.traits: isCopyable;
    static if (!isCopyable!T)
        @disable this(this);
}

struct CallbackType1 {
    bool[Class1] func;   // changing to Class1[] compiles OK
}

// moving Class1 above CallbackType1 compiles OK
class Class1 {
    SumType!CallbackType1 _callback;
    this(SumType!CallbackType1 callback) { // commenting out this ctor compiles
OK
        _callback = callback;
    }
}

--
December 12, 2019
https://issues.dlang.org/show_bug.cgi?id=20443

Walter Bright <bugzilla@digitalmars.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |bugzilla@digitalmars.com

--- Comment #3 from Walter Bright <bugzilla@digitalmars.com> ---
(In reply to Daniel X from comment #1)
>     import std.traits: isCopyable;

Test cases are much better if they don't import libraries (which can be very large). It's also undesirable to have Phobos dependencies in the test suite.

--
December 12, 2019
https://issues.dlang.org/show_bug.cgi?id=20443

--- Comment #4 from Walter Bright <bugzilla@digitalmars.com> ---
https://github.com/pbackus/sumtype/issues/35#issuecomment-563452054 reproduced here for convenience:

------------------------------

Here's my best attempt at an explanation:

During semantic analysis of CallbackType1, the forward reference to Class1 triggers on-demand semantic analysis of Class1, which in turn triggers on-demand semantic analysis of SumType!CallbackType1.

Normally, this would trigger on-demand analysis of CallbackType1, but the compiler notices it's already started analyzing CallbackType1 and terminates the recursion.

While analyzing SumType!CallbackType1, the compiler evaluates isCopyable!CallbackType1. The source code for isCopyable looks like this:

enum isCopyable(S) = is(typeof(
    { S foo = S.init; S copy = foo; }
));
When the lambda inside isCopyable attempts to declare an instance of
CallbackType1 as a local variable, it fails, because semantic analysis of
CallbackType1 is still in progress and its size is not yet known. You can
verify this by copy & pasting the lambda above into the reduced version of
SumType, before the static if statement. Doing so will produce the following
error:

Error: struct `onlineapp.CallbackType1` no size because of forward reference Because of this error, compilation of the lambda fails, and isCopyable!CallbackType1 evaluates to false.

Swapping the order of the declarations fixes the issue because it changes DMD's semantic-analysis "call stack" from this:

CallbackType1 → Class1 → SumType!CallbackType1

to this:

Class1 → SumType!CallbackType1 → CallbackType1

As long as SumType!CallbackType1 is encountered before CallbackType1, DMD is able to finish analyzing CallbackType1 before it evaluates isCopyable!CallbackType1, and thus does not encounter the error described above.

Fixing this in DMD is likely to be quite difficult. However, it may be possible to find a workaround for isCopyable that allows it to work with incomplete types.

--
December 13, 2019
https://issues.dlang.org/show_bug.cgi?id=20443

--- Comment #5 from Daniel X <redapple82570@gmail.com> ---
(In reply to Walter Bright from comment #3)
> Test cases are much better if they don't import libraries

Gotcha. Updated for convenience:

------
enum isCopyable(S) = is(typeof(
    { S foo = S.init; S copy = foo; }
));

struct SumType(T) {
    static if (!isCopyable!T)
        @disable this(this);
}

struct CallbackType1 {
    bool[Class1] func;   // changing to Class1[] compiles OK
}

// moving Class1 above CallbackType1 compiles OK
class Class1 {
    SumType!CallbackType1 _callback;
    this(SumType!CallbackType1 callback) { // commenting out this ctor compiles
OK
        _callback = callback;
    }
}

--
September 23
https://issues.dlang.org/show_bug.cgi?id=20443

Tomoya Tanjo <ttanjo@gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |ttanjo@gmail.com

--- Comment #6 from Tomoya Tanjo <ttanjo@gmail.com> ---
I have a similar issue as reported in the issue 22184.

run.dlang.io: https://run.dlang.io/is/VZtiPO


```dlang
class A
{
    static if (isHashable!B) {}
}

class B
{
    static if (isHashable!C) {}
}

class C
{
    static if (isHashable!B && isHashable!int) {}
}

enum isHashable(T) = __traits(compiles,
    () { T.init; }
);

void main(){}
```

I confirmed it does not work with dmd 2.100.2.
It works when the order of declaration is `C`, `A`, and `B`.

--