Bug 22010 – Link error with mutually recursive SumType / struct with opEquals

Status
NEW
Severity
normal
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2021-06-09T06:23:46Z
Last change time
2024-12-13T19:16:55Z
Keywords
link-failure
Assigned to
No Owner
Creator
Vladimir Panteleev
Moved to GitHub: dmd#19942 →

Comments

Comment #0 by dlang-bugzilla — 2021-06-09T06:23:46Z
///////////////////////////////////// test.d //////////////////////////////////// import std.sumtype; struct S { Ref!Node node; } alias Node = SumType!S; private struct Ref(T) { private T* _ref_ptr; this(ref T value) { _ref_ptr = &value; } /// bool opEquals(ref const Ref!T other) { return *_ref_ptr == *other._ref_ptr; } } void main() {} ///////////////////////////////////////////////////////////////////////////////// This produces: --- /usr/sbin/ld: test.o: in function `_D3std7sumtype__T7SumTypeTS4test1SZQs__T8opEqualsTSQBxQBw__TQBrTQBmZQBzTxSQCuQCt__TQCoTQCjZQCwZQCdMFKxQBdZb': test.d:(.text._D3std7sumtype__T7SumTypeTS4test1SZQs__T8opEqualsTSQBxQBw__TQBrTQBmZQBzTxSQCuQCt__TQCoTQCjZQCwZQCdMFKxQBdZb[_D3std7sumtype__T7SumTypeTS4test1SZQs__T8opEqualsTSQBxQBw__TQBrTQBmZQBzTxSQCuQCt__TQCoTQCjZQCwZQCdMFKxQBdZb]+0x21): undefined reference to `_D3std7sumtype__T7SumTypeTS4test1SZQs__T8opEqualsTxSQByQBx__TQBsTQBnZQCaTxQxZQBlMxFNbKxQBkZb' collect2: error: ld returned 1 exit status Error: linker exited with status 1 --- Demangled: --- /usr/sbin/ld: test.o: in function `bool std.sumtype.SumType!(test.S).SumType.opEquals!(std.sumtype.SumType!(test.S).SumType, const(std.sumtype.SumType!(test.S).SumType)).opEquals(ref const(std.sumtype.SumType!(test.S).SumType))': test.d:(.text.bool std.sumtype.SumType!(test.S).SumType.opEquals!(std.sumtype.SumType!(test.S).SumType, const(std.sumtype.SumType!(test.S).SumType)).opEquals(ref const(std.sumtype.SumType!(test.S).SumType))[bool std.sumtype.SumType!(test.S).SumType.opEquals!(std.sumtype.SumType!(test.S).SumType, const(std.sumtype.SumType!(test.S).SumType)).opEquals(ref const(std.sumtype.SumType!(test.S).SumType))]+0x21): undefined reference to `const nothrow bool std.sumtype.SumType!(test.S).SumType.opEquals!(const(std.sumtype.SumType!(test.S).SumType), const(std.sumtype.SumType!(test.S).SumType)).opEquals(ref const(std.sumtype.SumType!(test.S).SumType))' collect2: error: ld returned 1 exit status Error: linker exited with status 1 --- After dustmite-ing std.sumtype: /////////////////////////////////// test.d ////////////////////////////////// import std.meta: AliasSeq, Map = staticMap; import std.traits ; import std.typecons ; struct This {} struct SumType(Types) { alias Types = AliasSeq!( ReplaceTypeUnless!( isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType) ); union Storage { // Workaround for https://issues.dlang.org/show_bug.cgi?id=20068 template memberName(T) { mixin("enum memberName = `values_", "`;"); } static foreach (T; Types) mixin("T ", memberName!T, ";"); } Storage storage; inout(T) get(T)() inout { return __traits(getMember, storage, Storage.memberName!T); } bool opEquals(this This, Rhs)(Rhs rhs) { static if (is(This == Rhs)) AliasSeq!(this, rhs).match!((value, rhsValue) { value == rhsValue; }); alias CommonSumType = Rhs; return cast(CommonSumType) this == rhs; } } enum isSumTypeInstance(T) = is(Args); template match(handlers...) { auto match(SumTypes...)(SumTypes args) { matchImpl!handlers(args); } } template canMatch(alias handler, Ts...) { enum canMatch = (Ts args) => handler(args); } template Iota(size_t n) { alias Iota = AliasSeq!(1, 1); } template matchImpl(handlers...) { auto matchImpl(SumTypes...)(SumTypes args) { struct TagTuple { size_t[SumTypes.length] tags; alias tags this; static fromCaseId() { TagTuple result; // Most-significant to least-significant static foreach(i; 0 .. result.length) return result; } } template valueTypes(size_t caseId) { enum tags = TagTuple.fromCaseId; template getType(size_t i) { alias T = SumTypes[i].Types; alias getType = typeof(args[i].get!T()); } alias valueTypes = Map!(getType, Iota!(tags.length)); } enum numCases = SumTypes.length; static foreach (caseId; 0 .. numCases) foreach (handler; handlers) static if (canMatch!(handler, valueTypes!caseId)) { } } } struct S { Ref!Node node; } alias Node = SumType!S; struct Ref(T) { T* _ref_ptr; bool opEquals(const Ref!T other) { return *_ref_ptr == *other._ref_ptr; } } /////////////////////////////////////////////////////////////////////////////
Comment #1 by jlourenco5691 — 2021-06-09T12:08:08Z
I think this is because it uses `match` internally. You are passing a `const Ref!T` in your `opEquals` which makes its `TagTuple` a `const(TagTuple)`. When trying to match it won't be able to. As a workaround fix, removing `const` from `opEquals` solves your issue. Also, refactoring your `opEquals` to a template (to allow the program to compile), gives a more propper error when comparing the types: ```d /+dub.sdl: dependency "sumtype" version="~>1.1.1" +/ import sumtype; struct S { Ref!Node node; } alias Node = SumType!S; private struct Ref(T) { private T* _ref_ptr; this(ref T value) { _ref_ptr = &value; } /// bool opEquals()(ref const Ref!T other) { return *_ref_ptr == *other._ref_ptr; } } void main() { S s; assert(S.init == s); } ``` Produces (tested with sumtype-1.1.1): --- .dub/packages/sumtype-1.1.1/sumtype/src/sumtype.d(1718,13): Error: need `this` for `tags` of type `ulong[2]` .dub/packages/sumtype-1.1.1/sumtype/src/sumtype.d(1718,13): Error: need `this` for `tags` of type `ulong[2]` Error: `this` for `__invariant430` needs to be type `TagTuple` not type `const(TagTuple)` .dub/packages/sumtype-1.1.1/sumtype/src/sumtype.d(1828,4): Error: static assert: "`handlers[0]` of type `template` never matches" .dub/packages/sumtype-1.1.1/sumtype/src/sumtype.d(1448,46): instantiated from here: `matchImpl!(const(SumType!(S)), const(SumType!(S)))` .dub/packages/sumtype-1.1.1/sumtype/src/sumtype.d(578,31): instantiated from here: `match!(const(SumType!(S)), const(SumType!(S)))` .dub/packages/sumtype-1.1.1/sumtype/src/sumtype.d(587,11): instantiated from here: `opEquals!(const(SumType!(S)), const(SumType!(S)))` onlineapp.d(14,53): instantiated from here: `opEquals!(SumType!(S), const(SumType!(S)))` onlineapp.d(10,9): instantiated from here: `opEquals!()` dmd failed with exit code 1. --- This should work though.
Comment #2 by dlang-bugzilla — 2021-06-09T12:32:57Z
(In reply to João Lourenço from comment #1) > I think this is because it uses `match` internally. A link error like this should never happen no matter what the code does (unless it's something intentional like an extern declaration with no definition, of course.) > As a workaround fix, removing `const` from `opEquals` solves your issue. Thanks but this is a reduced example meant to illustrate the compiler bug.
Comment #3 by jlourenco5691 — 2021-06-09T17:21:20Z
(In reply to Vladimir Panteleev from comment #2) > Thanks but this is a reduced example meant to illustrate the compiler bug. Yes, I mislead the issue and thought it was about SumType itself.
Comment #4 by pro.mathias.lang — 2022-07-06T15:51:43Z
I believe I hit this issue as well. The key seems to be the mutual recursion. In my case, the code is the following: ``` import std.sumtype; import std.stdio; alias ST = SumType!(int, long, Custom[]); struct Custom { int a; ST b; } void main () { const SumType!(int, long, Custom[]) st = [ Custom(42), Custom(69) ]; version(all) writeln(st); assert(st.toString() == "NULL"); } ``` With this, I get a compiler error: ``` /usr/include/dmd/phobos/std/sumtype.d(1945): Error: need `this` for `tags` of type `ulong[1]` Error: `this` for `__invariant1322` needs to be type `TagTuple` not type `const(TagTuple)` /usr/include/dmd/phobos/std/sumtype.d(2102): Error: static assert: "No matching handler for types `(const(Custom[]))`" /usr/include/dmd/phobos/std/sumtype.d(1662): instantiated from here: `matchImpl!(const(SumType!(int, long, Custom[])))` /usr/include/dmd/phobos/std/sumtype.d(787): instantiated from here: `match!(const(SumType!(int, long, Custom[])))` st.d(16): instantiated from here: `toString!(const(SumType!(int, long, Custom[])))` ``` Now if you remove the `writeln` in `version(all)`, you end up with: ``` ld: error: undefined symbol: _D3std6format5write__T11formatValueTSQBj5array__T8AppenderTAyaZQoTAxS2st6CustomTaZQCiFKQBzKQzMKxSQDrQDq4spec__T10FormatSpecTaZQpZv >>> referenced by st.d >>> st.o:(_D3std4conv__T5toStrTAyaTAxS2st6CustomZQzFQrZQy) collect2: error: ld returned 1 exit status Error: linker exited with status 1 ``` Looking at SumType's code, it seems the instantiation of `formatValue` happens here: https://github.com/dlang/phobos/blob/dba1bbe271a9b2d7f24edeebbc77846e29904e41/std/sumtype.d#L807
Comment #5 by robert.schadek — 2024-12-13T19:16:55Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/19942 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB