Completed
Push — master ( ba85a6...34f627 )
by Marco
12s queued 10s
created

TypeIsContravariantTest::testContravariance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 3
dl 0
loc 9
rs 10
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\TypeIsContravariant;
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 TypeIsContravariantTest extends TestCase
17
{
18
    /** @dataProvider checkedTypes */
19
    public function testContravariance(
20
        ?ReflectionType $type,
21
        ?ReflectionType $newType,
22
        bool $expectedToBeContravariant
23
    ) : void {
24
        self::assertSame(
25
            $expectedToBeContravariant,
26
            (new TypeIsContravariant())
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 no type is contravariant with itself'                          => [
54
                null,
55
                null,
56
                true,
57
            ],
58
            'no type to void type is not contravariant'                                => [
59
                null,
60
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
61
                false,
62
            ],
63
            'void type to no type is contravariant'                                    => [
64
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
65
                null,
66
                true,
67
            ],
68
            'void type to scalar type is contravariant'                                => [
69
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
70
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
71
                true,
72
            ],
73
            'void type to class type is contravariant'                                 => [
74
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
75
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
76
                true,
77
            ],
78
            'scalar type to no type is contravariant'                                  => [
79
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
80
                null,
81
                true,
82
            ],
83
            'no type to scalar type is not contravariant'                              => [
84
                null,
85
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
86
                false,
87
            ],
88
            'class type to no type is contravariant'                                   => [
89
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
90
                null,
91
                true,
92
            ],
93
            'no type to class type is not contravariant'                               => [
94
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
95
                null,
96
                true,
97
            ],
98
            'iterable to array is not contravariant'                 => [
99
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
100
                ReflectionType::createFromTypeAndReflector('array', false, $reflector),
101
                false,
102
            ],
103
            'array to iterable is contravariant'                 => [
104
                ReflectionType::createFromTypeAndReflector('array', false, $reflector),
105
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
106
                true,
107
            ],
108
            'iterable to non-iterable class type is not contravariant'                 => [
109
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
110
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
111
                false,
112
            ],
113
            'iterable to iterable class type is not contravariant'                         => [
114
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
115
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
116
                false,
117
            ],
118
            'non-iterable class to iterable type is not contravariant'                 => [
119
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
120
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
121
                false,
122
            ],
123
            'iterable class type to iterable is not contravariant'                     => [
124
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
125
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
126
                false,
127
            ],
128
            'object to class type is not contravariant'                                => [
129
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
130
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
131
                false,
132
            ],
133
            'class type to object is contravariant'                                    => [
134
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
135
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
136
                true,
137
            ],
138
            'class type to scalar type is not contravariant'                           => [
139
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
140
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
141
                false,
142
            ],
143
            'scalar type to class type is not contravariant'                           => [
144
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
145
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
146
                false,
147
            ],
148
            'scalar type (string) to different scalar type (int) is not contravariant' => [
149
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
150
                ReflectionType::createFromTypeAndReflector('int', false, $reflector),
151
                false,
152
            ],
153
            'scalar type (int) to different scalar type (float) is not contravariant'  => [
154
                ReflectionType::createFromTypeAndReflector('int', false, $reflector),
155
                ReflectionType::createFromTypeAndReflector('float', false, $reflector),
156
                false,
157
            ],
158
            'object type to scalar type is not contravariant'                          => [
159
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
160
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
161
                false,
162
            ],
163
            'scalar type to object type is not contravariant'                          => [
164
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
165
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
166
                false,
167
            ],
168
            'class to superclass is contravariant'                                     => [
169
                ReflectionType::createFromTypeAndReflector('BClass', false, $reflector),
170
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
171
                true,
172
            ],
173
            'class to subclass is not contravariant'                                   => [
174
                ReflectionType::createFromTypeAndReflector('BClass', false, $reflector),
175
                ReflectionType::createFromTypeAndReflector('CClass', false, $reflector),
176
                false,
177
            ],
178
            'class to implemented interface is contravariant'                          => [
179
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
180
                ReflectionType::createFromTypeAndReflector('AnInterface', false, $reflector),
181
                true,
182
            ],
183
            'class to not implemented interface is not contravariant'                  => [
184
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
185
                ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
186
                false,
187
            ],
188
            'interface to parent interface is contravariant'                           => [
189
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
190
                ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
191
                true,
192
            ],
193
            'interface to child interface is contravariant'                            => [
194
                ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
195
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
196
                false,
197
            ],
198
        ];
199
    }
200
201
    /** @dataProvider existingTypes */
202
    public function testContravarianceConsidersSameTypeAlwaysContravariant(?ReflectionType $type) : void
203
    {
204
        self::assertTrue(
205
            (new TypeIsContravariant())
206
                ->__invoke($type, $type)
207
        );
208
    }
209
210
    /** @return (null|ReflectionType)[][] */
211
    public function existingTypes() : array
212
    {
213
        $reflector = new ClassReflector(new StringSourceLocator(
214
            <<<'PHP'
215
<?php
216
217
interface Traversable {}
218
class AClass {}
219
PHP
220
            ,
221
            (new BetterReflection())->astLocator()
222
        ));
223
224
        return array_merge(
225
            [[null]],
226
            array_merge(...array_map(
227
                function (string $type) use ($reflector) : array {
228
                    return [
229
                        [ReflectionType::createFromTypeAndReflector($type, false, $reflector)],
230
                        [ReflectionType::createFromTypeAndReflector($type, true, $reflector)],
231
                    ];
232
                },
233
                [
234
                    'int',
235
                    'string',
236
                    'float',
237
                    'bool',
238
                    'array',
239
                    'iterable',
240
                    'callable',
241
                    'Traversable',
242
                    'AClass',
243
                ]
244
            ))
245
        );
246
    }
247
248
    /** @dataProvider existingNullableTypeStrings */
249
    public function testContravarianceConsidersNullability(string $type) : void
250
    {
251
        $reflector   = new ClassReflector(new StringSourceLocator(
252
            <<<'PHP'
253
<?php
254
255
interface Traversable {}
256
class AClass {}
257
PHP
258
            ,
259
            (new BetterReflection())->astLocator()
260
        ));
261
        $nullable    = ReflectionType::createFromTypeAndReflector($type, true, $reflector);
262
        $notNullable = ReflectionType::createFromTypeAndReflector($type, false, $reflector);
263
264
        $isContravariant = new TypeIsContravariant();
265
266
        self::assertFalse($isContravariant->__invoke($nullable, $notNullable));
267
        self::assertTrue($isContravariant->__invoke($notNullable, $nullable));
268
    }
269
270
    /** @return string[][] */
271
    public function existingNullableTypeStrings() : array
272
    {
273
        return [
274
            ['int'],
275
            ['string'],
276
            ['float'],
277
            ['bool'],
278
            ['array'],
279
            ['iterable'],
280
            ['callable'],
281
            ['Traversable'],
282
            ['AClass'],
283
        ];
284
    }
285
}
286