Completed
Pull Request — master (#95)
by Marco
04:36
created

TypeIsCovariantTest::existingTypes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 21
nc 1
nop 0
dl 0
loc 32
rs 9.584
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace RoaveTest\BackwardCompatibility\DetectChanges\Variance;
6
7
use PHPUnit\Framework\TestCase;
8
use Roave\BackwardCompatibility\DetectChanges\Variance\TypeIsCovariant;
9
use Roave\BetterReflection\BetterReflection;
10
use Roave\BetterReflection\Reflection\ReflectionType;
11
use Roave\BetterReflection\Reflector\ClassReflector;
12
use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator;
13
use function array_map;
14
use function array_merge;
15
16
final class TypeIsCovariantTest extends TestCase
17
{
18
    /** @dataProvider checkedTypes */
19
    public function testCovariance(
20
        ?ReflectionType $type,
21
        ?ReflectionType $newType,
22
        bool $expectedToBeContravariant
23
    ) : void {
24
        self::assertSame(
25
            $expectedToBeContravariant,
26
            (new TypeIsCovariant())
27
                ->__invoke($type, $newType)
28
        );
29
    }
30
31
    /** @return (null|bool|ReflectionType)[][] */
32
    public function checkedTypes() : array
33
    {
34
        $reflector = new ClassReflector(new StringSourceLocator(
35
            <<<'PHP'
36
<?php
37
38
interface Traversable {}
39
interface Iterator extends Traversable {}
40
interface AnInterface {}
41
interface AnotherInterface {}
42
class AnotherClass implements AnInterface {}
43
class AnotherClassWithMultipleInterfaces implements AnInterface, AnotherInterface {}
44
class AClass {}
45
class BClass extends AClass {}
46
class CClass extends BClass {}
47
PHP
48
            ,
49
            (new BetterReflection())->astLocator()
50
        ));
51
52
        return [
53
            'no type to void type is covariant'                    => [
54
                null,
55
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
56
                true,
57
            ],
58
            'void type to no type is not covariant'                => [
59
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
60
                null,
61
                false,
62
            ],
63
            'void type to scalar type is not covariant'            => [
64
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
65
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
66
                false,
67
            ],
68
            'void type to class type is covariant'                 => [
69
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
70
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
71
                false,
72
            ],
73
            'scalar type to no type is not covariant'              => [
74
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
75
                null,
76
                false,
77
            ],
78
            'no type to scalar type is covariant'                  => [
79
                null,
80
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
81
                true,
82
            ],
83
            'class type to no type is not covariant'               => [
84
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
85
                null,
86
                false,
87
            ],
88
            'no type to class type is not contravariant'           => [
89
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
90
                null,
91
                false,
92
            ],
93
            'iterable to array is covariant' => [
94
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
95
                ReflectionType::createFromTypeAndReflector('array', false, $reflector),
96
                true,
97
            ],
98
            'iterable to scalar is not covariant' => [
99
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
100
                ReflectionType::createFromTypeAndReflector('int', false, $reflector),
101
                false,
102
            ],
103
            'scalar to iterable is not covariant' => [
104
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
105
                ReflectionType::createFromTypeAndReflector('int', false, $reflector),
106
                false,
107
            ],
108
            'array to iterable is not covariant'         => [
109
                ReflectionType::createFromTypeAndReflector('array', false, $reflector),
110
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
111
                false,
112
            ],
113
            'iterable to non-iterable class type is not covariant' => [
114
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
115
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
116
                false,
117
            ],
118
            'iterable to iterable class type is covariant'         => [
119
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
120
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
121
                true,
122
            ],
123
            'non-iterable class to iterable type is not covariant' => [
124
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
125
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
126
                false,
127
            ],
128
            'iterable class type to iterable is not covariant'     => [
129
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
130
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
131
                false,
132
            ],
133
            'object to class type is covariant'                    => [
134
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
135
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
136
                true,
137
            ],
138
            'class type to object is not covariant'                => [
139
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
140
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
141
                false,
142
            ],
143
144
            'class type to scalar type is not covariant'                           => [
145
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
146
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
147
                false,
148
            ],
149
            'scalar type to class type is not covariant'                           => [
150
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
151
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
152
                false,
153
            ],
154
            'scalar type (string) to different scalar type (int) is not covariant' => [
155
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
156
                ReflectionType::createFromTypeAndReflector('int', false, $reflector),
157
                false,
158
            ],
159
            'scalar type (int) to different scalar type (float) is not covariant'  => [
160
                ReflectionType::createFromTypeAndReflector('int', false, $reflector),
161
                ReflectionType::createFromTypeAndReflector('float', false, $reflector),
162
                false,
163
            ],
164
            'object type to scalar type is not contravariant'                      => [
165
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
166
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
167
                false,
168
            ],
169
            'scalar type to object type is not covariant'                          => [
170
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
171
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
172
                false,
173
            ],
174
            'class to superclass is not covariant'                                 => [
175
                ReflectionType::createFromTypeAndReflector('BClass', false, $reflector),
176
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
177
                false,
178
            ],
179
            'class to subclass is covariant'                                       => [
180
                ReflectionType::createFromTypeAndReflector('BClass', false, $reflector),
181
                ReflectionType::createFromTypeAndReflector('CClass', false, $reflector),
182
                true,
183
            ],
184
            'class to implemented interface is not covariant'                      => [
185
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
186
                ReflectionType::createFromTypeAndReflector('AnInterface', false, $reflector),
187
                false,
188
            ],
189
            'interface to implementing class is covariant'                         => [
190
                ReflectionType::createFromTypeAndReflector('AnInterface', false, $reflector),
191
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
192
                true,
193
            ],
194
            'class to not implemented interface is not covariant'                  => [
195
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
196
                ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
197
                false,
198
            ],
199
            'interface to parent interface is not covariant'                       => [
200
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
201
                ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
202
                false,
203
            ],
204
            'interface to child interface is covariant'                            => [
205
                ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
206
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
207
                true,
208
            ],
209
        ];
210
    }
211
212
    /** @dataProvider existingTypes */
213
    public function testCovarianceConsidersSameTypeAlwaysCovariant(?ReflectionType $type) : void
214
    {
215
        self::assertTrue(
216
            (new TypeIsCovariant())
217
                ->__invoke($type, $type)
218
        );
219
    }
220
221
    /** @return (null|ReflectionType)[][] */
222
    public function existingTypes() : array
223
    {
224
        $reflector = new ClassReflector(new StringSourceLocator(
225
            <<<'PHP'
226
<?php
227
228
interface Traversable {}
229
class AClass {}
230
PHP
231
            ,
232
            (new BetterReflection())->astLocator()
233
        ));
234
235
        return array_merge(
236
            [[null]],
237
            array_merge(...array_map(
238
                function (string $type) use ($reflector) : array {
239
                    return [
240
                        [ReflectionType::createFromTypeAndReflector($type, false, $reflector)],
241
                        [ReflectionType::createFromTypeAndReflector($type, true, $reflector)],
242
                    ];
243
                },
244
                [
245
                    'int',
246
                    'string',
247
                    'float',
248
                    'bool',
249
                    'array',
250
                    'iterable',
251
                    'callable',
252
                    'Traversable',
253
                    'AClass',
254
                ]
255
            ))
256
        );
257
    }
258
259
    /** @dataProvider existingNullableTypeStrings */
260
    public function testCovarianceConsidersNullability(string $type) : void
261
    {
262
        $reflector   = new ClassReflector(new StringSourceLocator(
263
            <<<'PHP'
264
<?php
265
266
interface Traversable {}
267
class AClass {}
268
PHP
269
            ,
270
            (new BetterReflection())->astLocator()
271
        ));
272
        $nullable    = ReflectionType::createFromTypeAndReflector($type, true, $reflector);
273
        $notNullable = ReflectionType::createFromTypeAndReflector($type, false, $reflector);
274
275
        $isCovariant = new TypeIsCovariant();
276
277
        self::assertTrue($isCovariant->__invoke($nullable, $notNullable));
278
        self::assertFalse($isCovariant->__invoke($notNullable, $nullable));
279
    }
280
281
    /** @return string[][] */
282
    public function existingNullableTypeStrings() : array
283
    {
284
        return [
285
            ['int'],
286
            ['string'],
287
            ['float'],
288
            ['bool'],
289
            ['array'],
290
            ['iterable'],
291
            ['callable'],
292
            ['Traversable'],
293
            ['AClass'],
294
        ];
295
    }
296
}
297