TypeIsContravariantTest::checkedTypes()   B
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 165
Code Lines 122

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 122
nc 1
nop 0
dl 0
loc 165
rs 8
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    /**
19
     * @dataProvider checkedTypes
20
     */
21
    public function testContravariance(
22
        ?ReflectionType $type,
23
        ?ReflectionType $newType,
24
        bool $expectedToBeContravariant
25
    ) : void {
26
        self::assertSame(
27
            $expectedToBeContravariant,
28
            (new TypeIsContravariant())
29
                ->__invoke($type, $newType)
30
        );
31
    }
32
33
    /**
34
     * @return array<string, array<int, bool|ReflectionType|null>>
35
     *
36
     * @psalm-return array<string, array{0: ReflectionType|null, 1: ReflectionType|null, 2: bool}>
37
     */
38
    public function checkedTypes() : array
39
    {
40
        $reflector = new ClassReflector(new StringSourceLocator(
41
            <<<'PHP'
42
<?php
43
44
interface Traversable {}
45
interface Iterator extends Traversable {}
46
interface AnInterface {}
47
interface AnotherInterface {}
48
class AnotherClass implements AnInterface {}
49
class AnotherClassWithMultipleInterfaces implements AnInterface, AnotherInterface {}
50
class AClass {}
51
class BClass extends AClass {}
52
class CClass extends BClass {}
53
PHP
54
            ,
55
            (new BetterReflection())->astLocator()
56
        ));
57
58
        return [
59
            'no type to no type is contravariant with itself'                          => [
60
                null,
61
                null,
62
                true,
63
            ],
64
            'no type to void type is not contravariant'                                => [
65
                null,
66
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
67
                false,
68
            ],
69
            'void type to no type is contravariant'                                    => [
70
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
71
                null,
72
                true,
73
            ],
74
            'void type to scalar type is contravariant'                                => [
75
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
76
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
77
                true,
78
            ],
79
            'void type to class type is contravariant'                                 => [
80
                ReflectionType::createFromTypeAndReflector('void', false, $reflector),
81
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
82
                true,
83
            ],
84
            'scalar type to no type is contravariant'                                  => [
85
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
86
                null,
87
                true,
88
            ],
89
            'no type to scalar type is not contravariant'                              => [
90
                null,
91
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
92
                false,
93
            ],
94
            'class type to no type is contravariant'                                   => [
95
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
96
                null,
97
                true,
98
            ],
99
            'no type to class type is not contravariant'                               => [
100
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
101
                null,
102
                true,
103
            ],
104
            'iterable to array is not contravariant'                 => [
105
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
106
                ReflectionType::createFromTypeAndReflector('array', false, $reflector),
107
                false,
108
            ],
109
            'array to iterable is contravariant'                 => [
110
                ReflectionType::createFromTypeAndReflector('array', false, $reflector),
111
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
112
                true,
113
            ],
114
            'iterable to non-iterable class type is not contravariant'                 => [
115
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
116
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
117
                false,
118
            ],
119
            'iterable to iterable class type is not contravariant'                         => [
120
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
121
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
122
                false,
123
            ],
124
            'non-iterable class to iterable type is not contravariant'                 => [
125
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
126
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
127
                false,
128
            ],
129
            'iterable class type to iterable is not contravariant'                     => [
130
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
131
                ReflectionType::createFromTypeAndReflector('iterable', false, $reflector),
132
                false,
133
            ],
134
            'object to class type is not contravariant'                                => [
135
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
136
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
137
                false,
138
            ],
139
            'class type to object is contravariant'                                    => [
140
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
141
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
142
                true,
143
            ],
144
            'class type to scalar type is not contravariant'                           => [
145
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
146
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
147
                false,
148
            ],
149
            'scalar type to class type is not contravariant'                           => [
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 contravariant' => [
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 contravariant'  => [
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 contravariant'                          => [
170
                ReflectionType::createFromTypeAndReflector('string', false, $reflector),
171
                ReflectionType::createFromTypeAndReflector('object', false, $reflector),
172
                false,
173
            ],
174
            'class to superclass is contravariant'                                     => [
175
                ReflectionType::createFromTypeAndReflector('BClass', false, $reflector),
176
                ReflectionType::createFromTypeAndReflector('AClass', false, $reflector),
177
                true,
178
            ],
179
            'class to subclass is not contravariant'                                   => [
180
                ReflectionType::createFromTypeAndReflector('BClass', false, $reflector),
181
                ReflectionType::createFromTypeAndReflector('CClass', false, $reflector),
182
                false,
183
            ],
184
            'class to implemented interface is contravariant'                          => [
185
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
186
                ReflectionType::createFromTypeAndReflector('AnInterface', false, $reflector),
187
                true,
188
            ],
189
            'class to not implemented interface is not contravariant'                  => [
190
                ReflectionType::createFromTypeAndReflector('AnotherClassWithMultipleInterfaces', false, $reflector),
191
                ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
192
                false,
193
            ],
194
            'interface to parent interface is contravariant'                           => [
195
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
196
                ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
197
                true,
198
            ],
199
            'interface to child interface is contravariant'                            => [
200
                ReflectionType::createFromTypeAndReflector('Traversable', false, $reflector),
201
                ReflectionType::createFromTypeAndReflector('Iterator', false, $reflector),
202
                false,
203
            ],
204
        ];
205
    }
206
207
    /**
208
     * @dataProvider existingTypes
209
     */
210
    public function testContravarianceConsidersSameTypeAlwaysContravariant(?ReflectionType $type) : void
211
    {
212
        self::assertTrue(
213
            (new TypeIsContravariant())
214
                ->__invoke($type, $type)
215
        );
216
    }
217
218
    /** @return (ReflectionType|null)[][] */
219
    public function existingTypes() : array
220
    {
221
        $reflector = new ClassReflector(new StringSourceLocator(
222
            <<<'PHP'
223
<?php
224
225
interface Traversable {}
226
class AClass {}
227
PHP
228
            ,
229
            (new BetterReflection())->astLocator()
230
        ));
231
232
        return array_merge(
233
            [[null]],
234
            array_merge(...array_map(
235
                static function (string $type) use ($reflector) : array {
236
                    return [
237
                        [ReflectionType::createFromTypeAndReflector($type, false, $reflector)],
238
                        [ReflectionType::createFromTypeAndReflector($type, true, $reflector)],
239
                    ];
240
                },
241
                [
242
                    'int',
243
                    'string',
244
                    'float',
245
                    'bool',
246
                    'array',
247
                    'iterable',
248
                    'callable',
249
                    'Traversable',
250
                    'AClass',
251
                ]
252
            ))
253
        );
254
    }
255
256
    /**
257
     * @dataProvider existingNullableTypeStrings
258
     */
259
    public function testContravarianceConsidersNullability(string $type) : void
260
    {
261
        $reflector   = new ClassReflector(new StringSourceLocator(
262
            <<<'PHP'
263
<?php
264
265
interface Traversable {}
266
class AClass {}
267
PHP
268
            ,
269
            (new BetterReflection())->astLocator()
270
        ));
271
        $nullable    = ReflectionType::createFromTypeAndReflector($type, true, $reflector);
272
        $notNullable = ReflectionType::createFromTypeAndReflector($type, false, $reflector);
273
274
        $isContravariant = new TypeIsContravariant();
275
276
        self::assertFalse($isContravariant->__invoke($nullable, $notNullable));
277
        self::assertTrue($isContravariant->__invoke($notNullable, $nullable));
278
    }
279
280
    /** @return string[][] */
281
    public function existingNullableTypeStrings() : array
282
    {
283
        return [
284
            ['int'],
285
            ['string'],
286
            ['float'],
287
            ['bool'],
288
            ['array'],
289
            ['iterable'],
290
            ['callable'],
291
            ['Traversable'],
292
            ['AClass'],
293
        ];
294
    }
295
}
296