Bug 21109 – Wrong result when using sort() on enum arrays

Status
NEW
Severity
critical
Priority
P2
Component
dmd
Product
D
Version
D2
Platform
All
OS
Mac OS X
Creation time
2020-08-04T04:15:01Z
Last change time
2024-12-13T19:10:34Z
Assigned to
No Owner
Creator
Andrej Mitrovic
Moved to GitHub: dmd#19757 →

Comments

Comment #0 by andrej.mitrovich — 2020-08-04T04:15:01Z
----- import std.algorithm; import std.stdio; struct S { int i; int[] arr; } void main () { enum q1 = [ S(2, [0, 0]), ]; enum q2 = [ S(2, [0, 0]), ]; enum q3 = [ S(2, [1, 1]), ]; foreach (idx; 0 .. 10) // 10 iterations { const unique_count = [q1, q2, q3].sort.uniq.count; writefln("Uniques: %s", unique_count); } } ----- This produces: ``` $ dmd -run test.d > Uniques: 2 Uniques: 2 Uniques: 2 Uniques: 2 Uniques: 2 Uniques: 3 Uniques: 2 Uniques: 2 Uniques: 2 Uniques: 2 ``` Notice how in one run the number of uniques was 3! If I change `enum` to `auto` the bug goes away.
Comment #1 by andrej.mitrovich — 2020-08-04T04:17:58Z
Using DMD v2.093.0.
Comment #2 by andrej.mitrovich — 2020-08-04T04:19:11Z
I've confirmed the issue with ldc2 as well. So it looks like it's a front-end bug.
Comment #3 by andrej.mitrovich — 2020-08-04T04:23:51Z
I think this is related to `sort`. It's possible that sort compares by pointers if two structs are otherwise equivalent. And then using `enum` has a different effect on arrays, possibly optimizing and storing the same pointer in two structs (making them equal).
Comment #4 by andrej.mitrovich — 2020-08-04T04:33:30Z
(In reply to Andrej Mitrovic from comment #3) > I think this is related to `sort`. It's possible that sort compares by > pointers if two structs are otherwise equivalent. > > And then using `enum` has a different effect on arrays, possibly optimizing > and storing the same pointer in two structs (making them equal). Sorry the actual reasoning should be: `enum` always allocates a new array, therefore the pointer addresses change each time. It's possible opCmp does a value comparison first, and then a pointer comparison. But I don't think pointers should be compared at all.. at least not when dealing with arrays and not naked pointers in a struct.
Comment #5 by boris2.9 — 2020-08-04T09:42:04Z
There is no 'opCmp' defined for the struct so the runtime just use a memcmp for the comparison. The code itself calls '__cmp' from core/internal/array/comparison.d (because the element are an array '[S(2, [0, 0])]') and there the 3 'static if' conditions fail but if you define 'opCmp' the second path is taken. So the following works: struct S { int[] arr; int opCmp(ref const S s) const { return arr < s.arr; } } There is a comment on clone.d file that says: "Essentially, a struct which does not define opCmp is not comparable." This concise phrase should be on the spec.
Comment #6 by andrej.mitrovich — 2020-08-07T08:14:18Z
(In reply to Boris Carvajal from comment #5) > There is no 'opCmp' defined for the struct so the runtime just use a memcmp > for the comparison. > The code itself calls '__cmp' from core/internal/array/comparison.d (because > the element are an array '[S(2, [0, 0])]') and there the 3 'static if' > conditions fail but if you define 'opCmp' the second path is taken. > > So the following works: > > struct S > { > int[] arr; > int opCmp(ref const S s) const { return arr < s.arr; } > } > > There is a comment on clone.d file that says: > > "Essentially, a struct which does not define opCmp is not comparable." > > This concise phrase should be on the spec. Hmm. In the past the compiler would also do memcmp when a struct didn't define `opEquals`, but we changed it so it does member-by-member `==` comparison. I wonder if we can do something similar for opCmp, or if that wouldn't make sense..
Comment #7 by simen.kjaras — 2020-08-07T08:50:57Z
We can reduce this issue down to this code: struct S { int[] arr; } unittest { import std.stdio; foreach (idx; 0 .. 10) { writeln([S([0])] < [S([0])]); } } I'm not convinced that being able to compare two arrays like this should be possible at all - the rule that for any T where T.init < T.init does not compile, [T.init] < [T.init] should not compile seems sensible to me. If we need it to work, it should definitely do a member-by-member comparison. Not least, this should be documented.
Comment #8 by robert.schadek — 2024-12-13T19:10:34Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/19757 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB