1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
|
/**
* This module contains compiler support determining equality of arrays.
*
* Copyright: Copyright Digital Mars 2000 - 2020.
* License: Distributed under the
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
* (See accompanying file LICENSE)
* Source: $(DRUNTIMESRC core/internal/_array/_equality.d)
*/
module core.internal.array.equality;
// The compiler lowers `lhs == rhs` to `__equals(lhs, rhs)` for
// * dynamic arrays,
// * (most) arrays of different (unqualified) element types, and
// * arrays of structs with custom opEquals.
// The scalar-only overload takes advantage of known properties of scalars to
// reduce template instantiation. This is expected to be the most common case.
bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs)
@nogc nothrow pure @trusted
if (__traits(isScalar, T1) && __traits(isScalar, T2))
{
const length = lhs.length;
static if (T1.sizeof == T2.sizeof
// Signedness needs to match for types that promote to int.
// (Actually it would be okay to memcmp bool[] and byte[] but that is
// probably too uncommon to be worth checking for.)
&& (T1.sizeof >= 4 || __traits(isUnsigned, T1) == __traits(isUnsigned, T2))
&& !__traits(isFloating, T1) && !__traits(isFloating, T2))
{
if (__ctfe)
return length == rhs.length && isEqual(lhs.ptr, rhs.ptr, length);
else
{
// This would improperly allow equality of integers and pointers
// but the CTFE branch will stop this function from compiling then.
import core.stdc.string : memcmp;
return length == rhs.length &&
(!length || 0 == memcmp(cast(const void*) lhs.ptr, cast(const void*) rhs.ptr, length * T1.sizeof));
}
}
else
{
return length == rhs.length && isEqual(lhs.ptr, rhs.ptr, length);
}
}
bool __equals(T1, T2)(scope T1[] lhs, scope T2[] rhs)
if (!__traits(isScalar, T1) || !__traits(isScalar, T2))
{
if (lhs.length != rhs.length)
return false;
if (lhs.length == 0)
return true;
static if (useMemcmp!(T1, T2))
{
if (!__ctfe)
{
static bool trustedMemcmp(scope T1[] lhs, scope T2[] rhs) @trusted @nogc nothrow pure
{
pragma(inline, true);
import core.stdc.string : memcmp;
return memcmp(cast(void*) lhs.ptr, cast(void*) rhs.ptr, lhs.length * T1.sizeof) == 0;
}
return trustedMemcmp(lhs, rhs);
}
else
{
foreach (const i; 0 .. lhs.length)
{
if (at(lhs, i) != at(rhs, i))
return false;
}
return true;
}
}
else
{
foreach (const i; 0 .. lhs.length)
{
if (at(lhs, i) != at(rhs, i))
return false;
}
return true;
}
}
/******************************
* Helper function for __equals().
* Outlined to enable __equals() to be inlined, as dmd cannot inline loops.
*/
private
bool isEqual(T1, T2)(scope const T1* t1, scope const T2* t2, size_t length)
{
foreach (const i; 0 .. length)
if (t1[i] != t2[i])
return false;
return true;
}
@safe unittest
{
assert(__equals([], []));
assert(!__equals([1, 2], [1, 2, 3]));
}
@safe unittest
{
auto a = "hello"c;
assert(a != "hel");
assert(a != "helloo");
assert(a != "betty");
assert(a == "hello");
assert(a != "hxxxx");
float[] fa = [float.nan];
assert(fa != fa);
}
@safe unittest
{
struct A
{
int a;
}
auto arr1 = [A(0), A(2)];
auto arr2 = [A(0), A(1)];
auto arr3 = [A(0), A(1)];
assert(arr1 != arr2);
assert(arr2 == arr3);
}
@safe unittest
{
struct A
{
int a;
int b;
bool opEquals(const A other)
{
return this.a == other.b && this.b == other.a;
}
}
auto arr1 = [A(1, 0), A(0, 1)];
auto arr2 = [A(1, 0), A(0, 1)];
auto arr3 = [A(0, 1), A(1, 0)];
assert(arr1 != arr2);
assert(arr2 == arr3);
}
// https://issues.dlang.org/show_bug.cgi?id=18252
@safe unittest
{
string[int][] a1, a2;
assert(__equals(a1, a2));
assert(a1 == a2);
a1 ~= [0: "zero"];
a2 ~= [0: "zero"];
assert(__equals(a1, a2));
assert(a1 == a2);
a2[0][1] = "one";
assert(!__equals(a1, a2));
assert(a1 != a2);
}
private:
// - Recursively folds static array types to their element type,
// - maps void to ubyte, and
// - pointers to size_t.
template BaseType(T)
{
static if (__traits(isStaticArray, T))
alias BaseType = BaseType!(typeof(T.init[0]));
else static if (is(immutable T == immutable void))
alias BaseType = ubyte;
else static if (is(T == E*, E))
alias BaseType = size_t;
else
alias BaseType = T;
}
// Use memcmp if the element sizes match and both base element types are integral.
// Due to int promotion, disallow small integers of diverging signed-ness though.
template useMemcmp(T1, T2)
{
static if (T1.sizeof != T2.sizeof)
enum useMemcmp = false;
else
{
alias B1 = BaseType!T1;
alias B2 = BaseType!T2;
enum useMemcmp = __traits(isIntegral, B1) && __traits(isIntegral, B2)
&& !( (B1.sizeof < 4 || B2.sizeof < 4) && __traits(isUnsigned, B1) != __traits(isUnsigned, B2) );
}
}
unittest
{
enum E { foo, bar }
static assert(useMemcmp!(byte, byte));
static assert(useMemcmp!(ubyte, ubyte));
static assert(useMemcmp!(void, const void));
static assert(useMemcmp!(void, immutable bool));
static assert(useMemcmp!(void, inout char));
static assert(useMemcmp!(void, shared ubyte));
static assert(!useMemcmp!(void, byte)); // differing signed-ness
static assert(!useMemcmp!(char[8], byte[8])); // ditto
static assert(useMemcmp!(short, short));
static assert(useMemcmp!(wchar, ushort));
static assert(!useMemcmp!(wchar, short)); // differing signed-ness
static assert(useMemcmp!(int, uint)); // no promotion, ignoring signed-ness
static assert(useMemcmp!(dchar, E));
static assert(useMemcmp!(immutable void*, size_t));
static assert(useMemcmp!(double*, ptrdiff_t));
static assert(useMemcmp!(long[2][3], const(ulong)[2][3]));
static assert(!useMemcmp!(float, float));
static assert(!useMemcmp!(double[2], double[2]));
static assert(!useMemcmp!(Object, Object));
static assert(!useMemcmp!(int[], int[]));
}
// https://issues.dlang.org/show_bug.cgi?id=21094
unittest
{
static class C
{
int a;
}
static struct S
{
bool isValid;
C fib;
inout(C) get() pure @safe @nogc nothrow inout
{
return isValid ? fib : C.init;
}
T opCast(T : C)() const { return null; }
alias get this;
}
auto foo(S[] lhs, S[] rhs)
{
return lhs == rhs;
}
}
// Returns a reference to an array element, eliding bounds check and
// casting void to ubyte.
pragma(inline, true)
ref at(T)(T[] r, size_t i) @trusted
// exclude opaque structs due to https://issues.dlang.org/show_bug.cgi?id=20959
if (!(is(T == struct) && !is(typeof(T.sizeof))))
{
static if (is(immutable T == immutable void))
return (cast(ubyte*) r.ptr)[i];
else
return r.ptr[i];
}
|