1+ <?php
2+
3+ declare (strict_types=1 );
4+
5+ use TypePHP \Tests \Fixtures \Domain \Car ;
6+ use TypePHP \Tests \Fixtures \Domain \Dog ;
7+
8+ /**
9+ * 1. Function with @param string[] and native `iterable` typehint
10+ *
11+ * @param string[] $extensions
12+ */
13+ function testTraversableToStringArray (iterable $ extensions ): array
14+ {
15+ $ result = [];
16+ foreach ($ extensions as $ ext ) {
17+ $ result [] = $ ext ;
18+ }
19+
20+ return $ result ;
21+ }
22+
23+ /**
24+ * 2. Function with @param Dog[] and native `Traversable` typehint
25+ *
26+ * @param Dog[] $dogs
27+ */
28+ function testTraversableToDogArray (Traversable $ dogs ): int
29+ {
30+ return iterator_count ($ dogs );
31+ }
32+
33+ /**
34+ * 3. Function with @param positive-int[] and un-typehinted parameter
35+ *
36+ * @param positive-int[] $numbers
37+ */
38+ function testTraversableToPositiveIntArray ($ numbers ): int
39+ {
40+ $ count = 0 ;
41+ foreach ($ numbers as $ num ) {
42+ $ count ++;
43+ }
44+
45+ return $ count ;
46+ }
47+
48+ describe ('Traversable & Generator Support for Array Types (Type[]) ' , function () {
49+ test ('accepts ArrayIterator instance matching string[] contract ' , function () {
50+ $ iterator = new ArrayIterator (['ext1 ' , 'ext2 ' , 'ext3 ' ]);
51+
52+ expect (testTraversableToStringArray ($ iterator ))->toBe (['ext1 ' , 'ext2 ' , 'ext3 ' ]);
53+ });
54+
55+ test ('accepts Generator instance yielding valid string[] elements ' , function () {
56+ $ generator = (function () {
57+ yield 'foo ' ;
58+ yield 'bar ' ;
59+ })();
60+
61+ expect (testTraversableToStringArray ($ generator ))->toBe (['foo ' , 'bar ' ]);
62+ });
63+
64+ test ('throws TypeError when Traversable yields an element violating string[] contract ' , function () {
65+ $ badIterator = new ArrayIterator (['ext1 ' , 12345 , 'ext3 ' ]);
66+
67+ expect (fn () => testTraversableToStringArray ($ badIterator ))
68+ ->toThrow (TypeError::class, '[1] must be of type string ' )
69+ ;
70+ });
71+
72+ test ('accepts Traversable instance matching object Dog[] contract ' , function () {
73+ $ dogIterator = new ArrayIterator ([new Dog (), new Dog ()]);
74+
75+ expect (testTraversableToDogArray ($ dogIterator ))->toBe (2 );
76+ });
77+
78+ test ('throws TypeError when Traversable contains an object violating Dog[] contract ' , function () {
79+ $ badDogIterator = new ArrayIterator ([new Dog (), new Car ()]);
80+
81+ expect (fn () => testTraversableToDogArray ($ badDogIterator ))
82+ ->toThrow (TypeError::class, 'must be of type TypePHP\Tests\Fixtures\Domain\Dog ' )
83+ ;
84+ });
85+
86+ test ('accepts Traversable for un-typehinted parameter with positive-int[] docblock ' , function () {
87+ $ numbers = new ArrayIterator ([10 , 20 , 30 ]);
88+
89+ expect (testTraversableToPositiveIntArray ($ numbers ))->toBe (3 );
90+ });
91+
92+ test ('throws TypeError when Traversable yields negative integer for positive-int[] docblock ' , function () {
93+ $ badNumbers = new ArrayIterator ([10 , -5 , 30 ]);
94+
95+ expect (fn () => testTraversableToPositiveIntArray ($ badNumbers ))
96+ ->toThrow (TypeError::class, 'positive-int ' )
97+ ;
98+ });
99+
100+ test ('rejects non-array and non-traversable values (e.g. string or plain stdClass) ' , function () {
101+ expect (fn () => testTraversableToPositiveIntArray ('not_iterable ' ))
102+ ->toThrow (TypeError::class, 'must be of type array ' )
103+ ;
104+
105+ expect (fn () => testTraversableToPositiveIntArray (new stdClass ()))
106+ ->toThrow (TypeError::class, 'must be of type array ' )
107+ ;
108+ });
109+ });
0 commit comments