Skip to content

Commit 09662f8

Browse files
committed
Fix coercive hashing at 0/1 boundaries and for negative zero.
1 parent 0fd3257 commit 09662f8

5 files changed

Lines changed: 169 additions & 16 deletions

File tree

src/Util/UniqueExtractor.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,22 @@ public static function getString(mixed $var, bool $strict): string
4646
$var instanceof \Closure => 'closure_' . \spl_object_id($var),
4747
\is_object($var) => 'object_' . ($strict ? \spl_object_id($var) : self::serializeObject($var)),
4848
\is_float($var) && \is_nan($var) => 'double_NAN',
49-
\gettype($var) === 'boolean' => 'boolean_' . \intval($var),
49+
$strict && \is_bool($var) => 'boolean_' . \intval($var),
5050
/** @phpstan-ignore cast.string */
5151
$strict => \gettype($var) . '_' . (string) $var,
52-
!$var => 'boolean_0',
53-
/** @phpstan-ignore argument.type */
54-
\strval($var) === '1' => 'boolean_1',
55-
\is_numeric($var) => 'numeric_' . \floatval($var),
52+
\is_bool($var) => 'numeric_' . self::normalizeNumeric(\floatval($var)),
53+
\is_numeric($var) => 'numeric_' . self::normalizeNumeric(\floatval($var)),
54+
$var === null || $var === '' => 'numeric_0',
5655
/** @phpstan-ignore cast.string */
5756
default => 'scalar_' . (string) $var,
5857
};
5958
}
6059

60+
private static function normalizeNumeric(float $value): string
61+
{
62+
return $value === 0.0 ? '0' : (string) $value;
63+
}
64+
6165
/**
6266
* @throws \InvalidArgumentException if the object cannot be serialized
6367
*/

tests/Set/DistinctTest.php

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ public static function dataProviderForArray(): array
173173
[
174174
[true, 1, '1', 1.0, '1.0'],
175175
false,
176-
[true, 1.0],
176+
[true],
177177
],
178178
[
179179
[true, 1, '1', 1.1, '1.1'],
@@ -473,7 +473,7 @@ public static function dataProviderForGenerators(): array
473473
[
474474
$gen([true, 1, '1', 1.0, '1.0']),
475475
false,
476-
[true, 1.0],
476+
[true],
477477
],
478478
[
479479
$gen([true, 1, '1', 1.1, '1.1']),
@@ -773,7 +773,7 @@ public static function dataProviderForIterators(): array
773773
[
774774
$iter([true, 1, '1', 1.0, '1.0']),
775775
false,
776-
[true, 1.0],
776+
[true],
777777
],
778778
[
779779
$iter([true, 1, '1', 1.1, '1.1']),
@@ -1073,7 +1073,7 @@ public static function dataProviderForTraversables(): array
10731073
[
10741074
$trav([true, 1, '1', 1.0, '1.0']),
10751075
false,
1076-
[true, 1.0],
1076+
[true],
10771077
],
10781078
[
10791079
$trav([true, 1, '1', 1.1, '1.1']),
@@ -1293,6 +1293,38 @@ public function testScientificNotationCoerciveArray(): void
12931293
$this->assertSame('1e2', $result[0]);
12941294
}
12951295

1296+
/**
1297+
* @test distinct coercive collapses numeric forms loose-equal to 1
1298+
*/
1299+
public function testOneBoundaryCoerciveArray(): void
1300+
{
1301+
// When
1302+
$result = [];
1303+
foreach (Set::distinct([1, '1.0', '1e0', '01', true], false) as $datum) {
1304+
$result[] = $datum;
1305+
}
1306+
1307+
// Then
1308+
$this->assertCount(1, $result);
1309+
$this->assertSame(1, $result[0]);
1310+
}
1311+
1312+
/**
1313+
* @test distinct coercive collapses numeric forms loose-equal to 0
1314+
*/
1315+
public function testZeroBoundaryCoerciveArray(): void
1316+
{
1317+
// When
1318+
$result = [];
1319+
foreach (Set::distinct([0, '0.0', '0e0', false], false) as $datum) {
1320+
$result[] = $datum;
1321+
}
1322+
1323+
// Then
1324+
$this->assertCount(1, $result);
1325+
$this->assertSame(0, $result[0]);
1326+
}
1327+
12961328
/**
12971329
* @test distinct strict keeps scientific notation and equivalent integer separate
12981330
*/

tests/Set/IntersectionCoerciveTest.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,29 @@ public static function dataProviderForArray(): array
186186
],
187187
['php', 'typescript'],
188188
],
189+
// 1-boundary
190+
[
191+
[
192+
[1, 2, 3],
193+
['01', '02', '03'],
194+
],
195+
[1, 2, 3],
196+
],
197+
[
198+
[
199+
[1, 2, 3],
200+
['1.0', '2.0', '3.0'],
201+
],
202+
[1, 2, 3],
203+
],
204+
// 0-boundary
205+
[
206+
[
207+
[0, 1],
208+
['0.0', '1.0'],
209+
],
210+
[0, 1],
211+
],
189212
];
190213
}
191214

@@ -368,6 +391,29 @@ public static function dataProviderForGenerators(): array
368391
],
369392
['php', 'typescript'],
370393
],
394+
// 1-boundary
395+
[
396+
[
397+
$gen([1, 2, 3]),
398+
$gen(['01', '02', '03']),
399+
],
400+
[1, 2, 3],
401+
],
402+
[
403+
[
404+
$gen([1, 2, 3]),
405+
$gen(['1.0', '2.0', '3.0']),
406+
],
407+
[1, 2, 3],
408+
],
409+
// 0-boundary
410+
[
411+
[
412+
$gen([0, 1]),
413+
$gen(['0.0', '1.0']),
414+
],
415+
[0, 1],
416+
],
371417
];
372418
}
373419

@@ -550,6 +596,29 @@ public static function dataProviderForIterators(): array
550596
],
551597
['php', 'typescript'],
552598
],
599+
// 1-boundary
600+
[
601+
[
602+
$iter([1, 2, 3]),
603+
$iter(['01', '02', '03']),
604+
],
605+
[1, 2, 3],
606+
],
607+
[
608+
[
609+
$iter([1, 2, 3]),
610+
$iter(['1.0', '2.0', '3.0']),
611+
],
612+
[1, 2, 3],
613+
],
614+
// 0-boundary
615+
[
616+
[
617+
$iter([0, 1]),
618+
$iter(['0.0', '1.0']),
619+
],
620+
[0, 1],
621+
],
553622
];
554623
}
555624

@@ -732,6 +801,29 @@ public static function dataProviderForTraversables(): array
732801
],
733802
['php', 'typescript'],
734803
],
804+
// 1-boundary
805+
[
806+
[
807+
$trav([1, 2, 3]),
808+
$trav(['01', '02', '03']),
809+
],
810+
[1, 2, 3],
811+
],
812+
[
813+
[
814+
$trav([1, 2, 3]),
815+
$trav(['1.0', '2.0', '3.0']),
816+
],
817+
[1, 2, 3],
818+
],
819+
// 0-boundary
820+
[
821+
[
822+
$trav([0, 1]),
823+
$trav(['0.0', '1.0']),
824+
],
825+
[0, 1],
826+
],
735827
];
736828
}
737829

tests/Summary/ContainsCoerciveTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,31 @@ public static function dataProviderForTrueArray(): array
135135
[[100, 200, 300], '1e2'],
136136
[['1e2', 200, 300], 100],
137137

138+
// 1-boundary: numeric string forms loose-equal to 1
139+
[[1], '1.0'],
140+
[[1], '1e0'],
141+
[[1], '01'],
142+
[[1], '+1'],
143+
[['1.0'], 1],
144+
[['1e0'], 1],
145+
[['01'], 1],
146+
[[true], '1.0'],
147+
[[true], '1e0'],
148+
149+
// 0-boundary: numeric string forms loose-equal to 0
150+
[[0], '0.0'],
151+
[[0], '0e0'],
152+
[[0], '00'],
153+
[['0.0'], 0],
154+
[['0e0'], 0],
155+
[[false], '0.0'],
156+
[[0], -0.0],
157+
[[0], '-0'],
158+
[[0], '-0.0'],
159+
[[-0.0], 0],
160+
[['-0'], 0],
161+
[['-0.0'], 0.0],
162+
138163
// null/false/0/'0' all hash to falsy-equivalent under coercive
139164
[[null], false],
140165
[[null], 0],

tests/Util/UniqueExtractorTest.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,31 +149,31 @@ public static function dataProviderForNotStrict(): array
149149
],
150150
[
151151
true,
152-
'boolean_1'
152+
'numeric_1'
153153
],
154154
[
155155
false,
156-
'boolean_0'
156+
'numeric_0'
157157
],
158158
[
159159
1,
160-
'boolean_1'
160+
'numeric_1'
161161
],
162162
[
163163
54,
164164
'numeric_54'
165165
],
166166
[
167167
0,
168-
'boolean_0'
168+
'numeric_0'
169169
],
170170
[
171171
-4,
172172
'numeric_-4'
173173
],
174174
[
175175
1.0,
176-
'boolean_1'
176+
'numeric_1'
177177
],
178178
[
179179
1.1,
@@ -185,7 +185,7 @@ public static function dataProviderForNotStrict(): array
185185
],
186186
[
187187
'1',
188-
'boolean_1'
188+
'numeric_1'
189189
],
190190
[
191191
'1.0',
@@ -205,7 +205,7 @@ public static function dataProviderForNotStrict(): array
205205
],
206206
[
207207
null,
208-
'boolean_0'
208+
'numeric_0'
209209
],
210210
];
211211
}

0 commit comments

Comments
 (0)