Bug 20443 – Case where code compiles depending on order of declaration

Status
NEW
Severity
regression
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2019-12-12T06:20:14Z
Last change time
2024-12-13T19:06:25Z
Keywords
rejects-valid
Assigned to
No Owner
Creator
basile-z
Moved to GitHub: dmd#19648 →

Comments

Comment #0 by b2.temp — 2019-12-12T06:20:14Z
--- enum isCopyable(S) = is(typeof( { S foo = S.init; S copy = foo; } )); struct SumType(T) { static if (!isCopyable!T) @disable this(this); } version(none) class Class2 { Struct1!Class1 subscribers; } struct Struct1(T) { bool[T] items; } struct CallbackType1 { void delegate(Struct1!Class2) func; } alias CallbackType = SumType!CallbackType1; class Class1 { CallbackType _callback; this(CallbackType callback) { // commenting out this ctor compiles OK _callback = callback; } } version(all) class Class2 { Struct1!Class1 subscribers; } void main() {} --- change the version(all) to version(none) and the version(none) no version(all) and the code compiles. run.dlang.io indicates a 2.066 regression 2.064 to 2.065.0: Success and no output 2.066.0 to 2.078.1: Failure with output: onlineapp.d(29): Error: struct onlineapp.SumType!(CallbackType1).SumType is not copyable because it is annotated with @disable Since 2.079.1: Failure with output: onlineapp.d(29): Error: struct `onlineapp.SumType!(CallbackType1).SumType` is not copyable because it is annotated with `@disable`
Comment #1 by redapple82570 — 2019-12-12T09:04:03Z
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; } }
Comment #2 by redapple82570 — 2019-12-12T09:06:08Z
see this comment on github for Paul's analysis of the problem: https://github.com/pbackus/sumtype/issues/35#issuecomment-563452054
Comment #3 by bugzilla — 2019-12-12T10:05:33Z
(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.
Comment #4 by bugzilla — 2019-12-12T10:08:30Z
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.
Comment #5 by redapple82570 — 2019-12-13T16:45:10Z
(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; } }
Comment #6 by ttanjo — 2022-09-23T09:53:37Z
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`.
Comment #7 by robert.schadek — 2024-12-13T19:06:25Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/19648 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB