TypeCombination   F
last analyzed

Complexity

Total Complexity 320

Size/Duplication

Total Lines 1286
Duplicated Lines 18.51 %

Coupling/Cohesion

Components 1
Dependencies 30

Importance

Changes 0
Metric Value
dl 238
loc 1286
rs 0.8
c 0
b 0
f 0
wmc 320
lcom 1
cbo 30

4 Methods

Rating   Name   Duplication   Size   Complexity  
F combineTypes() 50 441 120
F scrapeTypeProperties() 178 671 184
B getSharedTypes() 10 33 11
A getClassLikes() 0 26 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like TypeCombination often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TypeCombination, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Psalm\Internal\Type;
3
4
use function array_filter;
5
use function array_intersect_key;
6
use function array_keys;
7
use function array_merge;
8
use function array_values;
9
use function count;
10
use function get_class;
11
use function in_array;
12
use function is_int;
13
use Psalm\Codebase;
14
use Psalm\Type;
15
use Psalm\Type\Atomic;
16
use Psalm\Type\Atomic\ObjectLike;
17
use Psalm\Type\Atomic\Scalar;
18
use Psalm\Type\Atomic\TArray;
19
use Psalm\Type\Atomic\TArrayKey;
20
use Psalm\Type\Atomic\TBool;
21
use Psalm\Type\Atomic\TCallable;
22
use Psalm\Type\Atomic\TCallableArray;
23
use Psalm\Type\Atomic\TCallableObject;
24
use Psalm\Type\Atomic\TCallableObjectLikeArray;
25
use Psalm\Type\Atomic\TCallableString;
26
use Psalm\Type\Atomic\TClassString;
27
use Psalm\Type\Atomic\TEmpty;
28
use Psalm\Type\Atomic\TEmptyMixed;
29
use Psalm\Type\Atomic\TFalse;
30
use Psalm\Type\Atomic\TFloat;
31
use Psalm\Type\Atomic\TGenericObject;
32
use Psalm\Type\Atomic\TInt;
33
use Psalm\Type\Atomic\TIterable;
34
use Psalm\Type\Atomic\TList;
35
use Psalm\Type\Atomic\TLiteralClassString;
36
use Psalm\Type\Atomic\TLiteralFloat;
37
use Psalm\Type\Atomic\TLiteralInt;
38
use Psalm\Type\Atomic\TLiteralString;
39
use Psalm\Type\Atomic\TLowercaseString;
40
use Psalm\Type\Atomic\TMixed;
41
use Psalm\Type\Atomic\TNamedObject;
42
use Psalm\Type\Atomic\TNonEmptyArray;
43
use Psalm\Type\Atomic\TNonEmptyList;
44
use Psalm\Type\Atomic\TNonEmptyLowercaseString;
45
use Psalm\Type\Atomic\TNonEmptyMixed;
46
use Psalm\Type\Atomic\TNonEmptyString;
47
use Psalm\Type\Atomic\TNull;
48
use Psalm\Type\Atomic\TObject;
49
use Psalm\Type\Atomic\TScalar;
50
use Psalm\Type\Atomic\TString;
51
use Psalm\Type\Atomic\TTemplateParam;
52
use Psalm\Type\Atomic\TTraitString;
53
use Psalm\Type\Atomic\TTrue;
54
use Psalm\Type\Union;
55
use function strpos;
56
use function substr;
57
58
/**
59
 * @internal
60
 */
61
class TypeCombination
62
{
63
    /** @var array<string, Atomic> */
64
    private $value_types = [];
65
66
    /** @var array<string, TNamedObject>|null */
67
    private $named_object_types = [];
68
69
    /** @var array<int, Union> */
70
    private $array_type_params = [];
71
72
    /** @var array<string, array<int, Union>> */
73
    private $builtin_type_params = [];
74
75
    /** @var array<string, array<int, Union>> */
76
    private $object_type_params = [];
77
78
    /** @var array<int, bool>|null */
79
    private $array_counts = [];
80
81
    /** @var bool */
82
    private $array_sometimes_filled = false;
83
84
    /** @var bool */
85
    private $array_always_filled = true;
86
87
    /** @var array<string|int, Union> */
88
    private $objectlike_entries = [];
89
90
    /** @var bool */
91
    private $objectlike_sealed = true;
92
93
    /** @var ?Union */
94
    private $objectlike_key_type = null;
95
96
    /** @var ?Union */
97
    private $objectlike_value_type = null;
98
99
    /** @var bool */
100
    private $has_mixed = false;
101
102
    /** @var bool */
103
    private $empty_mixed = false;
104
105
    /** @var bool */
106
    private $non_empty_mixed = false;
107
108
    /** @var ?bool */
109
    private $mixed_from_loop_isset = null;
110
111
    /** @var array<string, Atomic\TLiteralString>|null */
112
    private $strings = [];
113
114
    /** @var array<string, Atomic\TLiteralInt>|null */
115
    private $ints = [];
116
117
    /** @var array<string, Atomic\TLiteralFloat>|null */
118
    private $floats = [];
119
120
    /** @var array<string, Atomic\TNamedObject|Atomic\TObject>|null */
121
    private $class_string_types = [];
122
123
    /**
124
     * @var array<string, TNamedObject|TTemplateParam|TIterable|TObject>|null
125
     */
126
    private $extra_types;
127
128
    /** @var ?bool */
129
    private $all_arrays_lists;
130
131
    /** @var ?bool */
132
    private $all_arrays_callable;
133
134
    /** @var ?bool */
135
    private $all_arrays_class_string_maps;
136
137
    /** @var array<string, bool> */
138
    private $class_string_map_names = [];
139
140
    /** @var array<string, ?TNamedObject> */
141
    private $class_string_map_as_types = [];
142
143
    /**
144
     * Combines types together
145
     *  - so `int + string = int|string`
146
     *  - so `array<int> + array<string> = array<int|string>`
147
     *  - and `array<int> + string = array<int>|string`
148
     *  - and `array<empty> + array<empty> = array<empty>`
149
     *  - and `array<string> + array<empty> = array<string>`
150
     *  - and `array + array<string> = array<mixed>`
151
     *
152
     * @param  non-empty-list<Atomic>    $types
0 ignored issues
show
Documentation introduced by
The doc-type non-empty-list<Atomic> could not be parsed: Unknown type name "non-empty-list" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
153
     * @param  int    $literal_limit any greater number of literal types than this
154
     *                               will be merged to a scalar
155
     *
156
     * @return Union
157
     * @psalm-suppress TypeCoercion
158
     */
159
    public static function combineTypes(
160
        array $types,
161
        Codebase $codebase = null,
162
        bool $overwrite_empty_array = false,
163
        bool $allow_mixed_union = true,
164
        int $literal_limit = 500
165
    ) {
166
        if (in_array(null, $types, true)) {
167
            return Type::getMixed();
168
        }
169
170
        if (count($types) === 1) {
171
            $union_type = new Union([$types[0]]);
172
173
            if ($types[0]->from_docblock) {
174
                $union_type->from_docblock = true;
175
            }
176
177
            return $union_type;
178
        }
179
180
        $combination = new TypeCombination();
181
182
        $from_docblock = false;
183
184
        foreach ($types as $type) {
185
            $from_docblock = $from_docblock || $type->from_docblock;
186
187
            $result = self::scrapeTypeProperties(
188
                $type,
189
                $combination,
190
                $codebase,
191
                $overwrite_empty_array,
192
                $allow_mixed_union,
193
                $literal_limit
194
            );
195
196
            if ($result) {
197
                if ($from_docblock) {
198
                    $result->from_docblock = true;
199
                }
200
201
                return $result;
202
            }
203
        }
204
205
        if (count($combination->value_types) === 1
206
            && !count($combination->objectlike_entries)
207
            && !$combination->array_type_params
208
            && !$combination->builtin_type_params
209
            && !$combination->object_type_params
210
            && !$combination->named_object_types
211
            && !$combination->strings
212
            && !$combination->class_string_types
213
            && !$combination->ints
214
            && !$combination->floats
215
        ) {
216 View Code Duplication
            if (isset($combination->value_types['false'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
217
                $union_type = Type::getFalse();
218
219
                if ($from_docblock) {
220
                    $union_type->from_docblock = true;
221
                }
222
223
                return $union_type;
224
            }
225
226 View Code Duplication
            if (isset($combination->value_types['true'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
                $union_type = Type::getTrue();
228
229
                if ($from_docblock) {
230
                    $union_type->from_docblock = true;
231
                }
232
233
                return $union_type;
234
            }
235 View Code Duplication
        } elseif (isset($combination->value_types['void'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
236
            unset($combination->value_types['void']);
237
238
            // if we're merging with another type, we cannot represent it in PHP
239
            $from_docblock = true;
240
241
            if (!isset($combination->value_types['null'])) {
242
                $combination->value_types['null'] = new TNull();
243
            }
244
        }
245
246 View Code Duplication
        if (isset($combination->value_types['true']) && isset($combination->value_types['false'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
247
            unset($combination->value_types['true'], $combination->value_types['false']);
248
249
            $combination->value_types['bool'] = new TBool();
250
        }
251
252
        if ($combination->array_type_params
253
            && (isset($combination->named_object_types['Traversable'])
254
                || isset($combination->builtin_type_params['Traversable']))
255
            && (
256
                ($codebase && !$codebase->config->allow_phpstorm_generics)
257
                || isset($combination->builtin_type_params['Traversable'])
258
                || (isset($combination->named_object_types['Traversable'])
259
                    && $combination->named_object_types['Traversable']->from_docblock)
260
            )
261
        ) {
262
            $array_param_types = $combination->array_type_params;
263
            $traversable_param_types = $combination->builtin_type_params['Traversable']
264
                ?? [Type::getMixed(), Type::getMixed()];
265
266
            $combined_param_types = [];
267
268
            foreach ($array_param_types as $i => $array_param_type) {
269
                $combined_param_types[$i] = Type::combineUnionTypes($array_param_type, $traversable_param_types[$i]);
270
            }
271
272
            $combination->value_types['iterable'] = new TIterable($combined_param_types);
273
274
            $combination->array_type_params = [];
275
276
            /**
277
             * @psalm-suppress PossiblyNullArrayAccess
278
             */
279
            unset(
280
                $combination->value_types['array'],
281
                $combination->named_object_types['Traversable'],
282
                $combination->builtin_type_params['Traversable']
283
            );
284
        }
285
286
        if ($combination->empty_mixed && $combination->non_empty_mixed) {
287
            $combination->value_types['mixed'] = new TMixed((bool) $combination->mixed_from_loop_isset);
288
        }
289
290
        $new_types = [];
291
292
        if (count($combination->objectlike_entries)) {
293
            if ($combination->array_type_params
294
                && $combination->array_type_params[0]->allStringLiterals()
295
                && $combination->array_always_filled
296
            ) {
297
                foreach ($combination->array_type_params[0]->getAtomicTypes() as $atomic_key_type) {
298
                    if ($atomic_key_type instanceof TLiteralString) {
299
                        $combination->objectlike_entries[$atomic_key_type->value]
300
                            = $combination->array_type_params[1];
301
                    }
302
                }
303
304
                $combination->array_type_params = [];
305
                $combination->objectlike_sealed = false;
306
            }
307
308
            if (!$combination->array_type_params
309
                || $combination->array_type_params[1]->isEmpty()
310
            ) {
311
                if (!$overwrite_empty_array
312
                    && ($combination->array_type_params
313
                        && ($combination->array_type_params[1]->isEmpty()
314
                            || $combination->array_type_params[1]->isMixed()))
315
                ) {
316
                    foreach ($combination->objectlike_entries as $objectlike_entry) {
317
                        $objectlike_entry->possibly_undefined = true;
318
                    }
319
                }
320
321
                if ($combination->objectlike_value_type
322
                    && $combination->objectlike_value_type->isMixed()
323
                ) {
324
                    $combination->objectlike_entries = array_filter(
325
                        $combination->objectlike_entries,
326
                        function (Type\Union $type) : bool {
327
                            return !$type->possibly_undefined;
328
                        }
329
                    );
330
                }
331
332
                if ($combination->objectlike_entries) {
333
                    if ($combination->all_arrays_callable) {
334
                        $objectlike = new TCallableObjectLikeArray($combination->objectlike_entries);
335
                    } else {
336
                        $objectlike = new ObjectLike($combination->objectlike_entries);
337
                    }
338
339
                    if ($combination->objectlike_sealed && !$combination->array_type_params) {
340
                        $objectlike->sealed = true;
341
                    }
342
343
                    if ($combination->objectlike_key_type) {
344
                        $objectlike->previous_key_type = $combination->objectlike_key_type;
345
                    } elseif ($combination->array_type_params
346
                        && $combination->array_type_params[0]->isArrayKey()
347
                    ) {
348
                        $objectlike->previous_key_type = $combination->array_type_params[0];
349
                    }
350
351
                    if ($combination->objectlike_value_type) {
352
                        $objectlike->previous_value_type = $combination->objectlike_value_type;
353
                    } elseif ($combination->array_type_params
354
                        && $combination->array_type_params[1]->isMixed()
355
                    ) {
356
                        $objectlike->previous_value_type = $combination->array_type_params[1];
357
                    }
358
359
                    if ($combination->all_arrays_lists) {
360
                        $objectlike->is_list = true;
361
                    }
362
363
                    $new_types[] = $objectlike;
364
                } else {
365
                    $new_types[] = new Type\Atomic\TArray([Type::getArrayKey(), Type::getMixed()]);
366
                }
367
368
                // if we're merging an empty array with an object-like, clobber empty array
369
                $combination->array_type_params = [];
370
            }
371
        }
372
373
        if ($generic_type_params = $combination->array_type_params) {
374
            if ($combination->objectlike_entries) {
375
                $objectlike_generic_type = null;
376
377
                $objectlike_keys = [];
378
379
                foreach ($combination->objectlike_entries as $property_name => $property_type) {
380
                    if ($objectlike_generic_type) {
381
                        $objectlike_generic_type = Type::combineUnionTypes(
382
                            $property_type,
383
                            $objectlike_generic_type,
384
                            $codebase,
385
                            $overwrite_empty_array
386
                        );
387
                    } else {
388
                        $objectlike_generic_type = clone $property_type;
389
                    }
390
391
                    if (is_int($property_name)) {
392
                        if (!isset($objectlike_keys['int'])) {
393
                            $objectlike_keys['int'] = new TInt;
394
                        }
395
                    } else {
396
                        if (!isset($objectlike_keys['string'])) {
397
                            $objectlike_keys['string'] = new TString;
398
                        }
399
                    }
400
                }
401
402
                if ($combination->objectlike_value_type) {
403
                    $objectlike_generic_type = Type::combineUnionTypes(
404
                        $combination->objectlike_value_type,
405
                        $objectlike_generic_type,
0 ignored issues
show
Documentation introduced by
$objectlike_generic_type is of type null|object, but the function expects a object<Psalm\Type\Union>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
406
                        $codebase,
407
                        $overwrite_empty_array
408
                    );
409
                }
410
411
                $objectlike_generic_type->possibly_undefined = false;
412
413
                $objectlike_key_type = new Type\Union(array_values($objectlike_keys));
414
415
                if ($combination->objectlike_key_type) {
416
                    $objectlike_key_type = Type::combineUnionTypes(
417
                        $combination->objectlike_key_type,
418
                        $objectlike_key_type,
419
                        $codebase,
420
                        $overwrite_empty_array
421
                    );
422
                }
423
424
                $generic_type_params[0] = Type::combineUnionTypes(
425
                    $generic_type_params[0],
426
                    $objectlike_key_type,
427
                    $codebase,
428
                    $overwrite_empty_array,
429
                    $allow_mixed_union
430
                );
431
432
                if (!$generic_type_params[1]->isMixed()) {
433
                    $generic_type_params[1] = Type::combineUnionTypes(
434
                        $generic_type_params[1],
435
                        $objectlike_generic_type,
436
                        $codebase,
437
                        $overwrite_empty_array,
438
                        $allow_mixed_union
439
                    );
440
                }
441
            }
442
443
            if ($combination->all_arrays_callable) {
444
                $array_type = new TCallableArray($generic_type_params);
445
            } elseif ($combination->array_always_filled
446
                || ($combination->array_sometimes_filled && $overwrite_empty_array)
447
                || ($combination->objectlike_entries
448
                    && $combination->objectlike_sealed
449
                    && $overwrite_empty_array)
450
            ) {
451
                if ($combination->all_arrays_lists) {
452
                    $array_type = new TNonEmptyList($generic_type_params[1]);
453
454 View Code Duplication
                    if ($combination->array_counts && count($combination->array_counts) === 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
455
                        $array_type->count = array_keys($combination->array_counts)[0];
0 ignored issues
show
Documentation Bug introduced by
It seems like \array_keys($combination->array_counts)[0] can also be of type string. However, the property $count is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
456
                    }
457 View Code Duplication
                } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
458
                    $array_type = new TNonEmptyArray($generic_type_params);
459
460
                    if ($combination->array_counts && count($combination->array_counts) === 1) {
461
                        $array_type->count = array_keys($combination->array_counts)[0];
462
                    }
463
                }
464
            } else {
465
                if ($combination->all_arrays_class_string_maps
466
                    && count($combination->class_string_map_as_types) === 1
467
                    && count($combination->class_string_map_names) === 1
468
                ) {
469
                    $array_type = new Type\Atomic\TClassStringMap(
470
                        array_keys($combination->class_string_map_names)[0],
471
                        array_values($combination->class_string_map_as_types)[0],
472
                        $generic_type_params[1]
473
                    );
474
                } elseif ($combination->all_arrays_lists) {
475
                    $array_type = new TList($generic_type_params[1]);
476
                } else {
477
                    $array_type = new TArray($generic_type_params);
478
                }
479
            }
480
481
            $new_types[] = $array_type;
482
        }
483
484
        if ($combination->extra_types) {
485
            $combination->extra_types = self::combineTypes(
486
                array_values($combination->extra_types),
487
                $codebase
488
            )->getAtomicTypes();
489
        }
490
491
        foreach ($combination->builtin_type_params as $generic_type => $generic_type_params) {
492
            if ($generic_type === 'iterable') {
493
                $new_types[] = new TIterable($generic_type_params);
494
            } else {
495
                $generic_object = new TGenericObject($generic_type, $generic_type_params);
496
                $generic_object->extra_types = $combination->extra_types;
497
                $new_types[] = $generic_object;
498
499
                if ($combination->named_object_types) {
500
                    unset($combination->named_object_types[$generic_type]);
501
                }
502
            }
503
        }
504
505
        foreach ($combination->object_type_params as $generic_type => $generic_type_params) {
506
            $generic_type = substr($generic_type, 0, (int) strpos($generic_type, '<'));
507
508
            $generic_object = new TGenericObject($generic_type, $generic_type_params);
509
            $generic_object->extra_types = $combination->extra_types;
510
            $new_types[] = $generic_object;
511
        }
512
513
        if ($combination->class_string_types) {
514
            if ($combination->strings) {
515
                foreach ($combination->strings as $k => $string) {
516
                    if ($string instanceof TLiteralClassString) {
517
                        $combination->class_string_types[$string->value] = new TNamedObject($string->value);
518
                        unset($combination->strings[$k]);
519
                    }
520
                }
521
            }
522
523
            if (!isset($combination->value_types['string'])) {
524
                $object_type = self::combineTypes(
525
                    array_values($combination->class_string_types),
526
                    $codebase
527
                );
528
529 View Code Duplication
                foreach ($object_type->getAtomicTypes() as $object_atomic_type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
530
                    if ($object_atomic_type instanceof TNamedObject) {
531
                        $new_types[] = new TClassString($object_atomic_type->value, $object_atomic_type);
532
                    } elseif ($object_atomic_type instanceof TObject) {
533
                        $new_types[] = new TClassString();
534
                    }
535
                }
536
            }
537
        }
538
539
        if ($combination->strings) {
540
            $new_types = array_merge($new_types, array_values($combination->strings));
541
        }
542
543
        if ($combination->ints) {
544
            $new_types = array_merge($new_types, array_values($combination->ints));
545
        }
546
547
        if ($combination->floats) {
548
            $new_types = array_merge($new_types, array_values($combination->floats));
549
        }
550
551
        if (isset($combination->value_types['string'])
552
            && isset($combination->value_types['int'])
553
            && isset($combination->value_types['bool'])
554
            && isset($combination->value_types['float'])
555
        ) {
556
            unset(
557
                $combination->value_types['string'],
558
                $combination->value_types['int'],
559
                $combination->value_types['bool'],
560
                $combination->value_types['float']
561
            );
562
            $combination->value_types['scalar'] = new TScalar;
563
        }
564
565
        if ($combination->named_object_types !== null) {
566
            $combination->value_types += $combination->named_object_types;
567
        }
568
569
        $has_empty = (int) isset($combination->value_types['empty']);
570
571
        foreach ($combination->value_types as $type) {
572
            if ($type instanceof TMixed
573
                && $combination->mixed_from_loop_isset
574
                && (count($combination->value_types) > (1 + $has_empty) || count($new_types) > $has_empty)
575
            ) {
576
                continue;
577
            }
578
579
            if ($type instanceof TEmpty
580
                && (count($combination->value_types) > 1 || count($new_types))
581
            ) {
582
                continue;
583
            }
584
585
            $new_types[] = $type;
586
        }
587
588
        if (!$new_types) {
589
            throw new \UnexpectedValueException('There should be types here');
590
        }
591
592
        $union_type = new Union($new_types);
593
594
        if ($from_docblock) {
595
            $union_type->from_docblock = true;
596
        }
597
598
        return $union_type;
599
    }
600
601
    /**
602
     * @param  Atomic  $type
603
     * @param  TypeCombination $combination
604
     * @param  Codebase|null   $codebase
605
     *
606
     * @return null|Union
607
     */
608
    private static function scrapeTypeProperties(
609
        Atomic $type,
610
        TypeCombination $combination,
611
        $codebase,
612
        bool $overwrite_empty_array,
613
        bool $allow_mixed_union,
614
        int $literal_limit
615
    ) {
616
        if ($type instanceof TMixed) {
617
            $combination->has_mixed = true;
618
            if ($type->from_loop_isset) {
619
                if ($combination->mixed_from_loop_isset === null) {
620
                    $combination->mixed_from_loop_isset = true;
621
                } else {
622
                    return null;
623
                }
624
            } else {
625
                $combination->mixed_from_loop_isset = false;
626
            }
627
628
            if ($type instanceof TNonEmptyMixed) {
629
                $combination->non_empty_mixed = true;
630
631
                if ($combination->empty_mixed) {
632
                    return null;
633
                }
634
            } elseif ($type instanceof TEmptyMixed) {
635
                $combination->empty_mixed = true;
636
637
                if ($combination->non_empty_mixed) {
638
                    return null;
639
                }
640
            } else {
641
                $combination->empty_mixed = true;
642
                $combination->non_empty_mixed = true;
643
            }
644
645
            if (!$allow_mixed_union) {
646
                return Type::getMixed((bool) $combination->mixed_from_loop_isset);
647
            }
648
        }
649
650
        // deal with false|bool => bool
651
        if (($type instanceof TFalse || $type instanceof TTrue) && isset($combination->value_types['bool'])) {
652
            return null;
653
        }
654
655 View Code Duplication
        if (get_class($type) === TBool::class && isset($combination->value_types['false'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
656
            unset($combination->value_types['false']);
657
        }
658
659 View Code Duplication
        if (get_class($type) === TBool::class && isset($combination->value_types['true'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
660
            unset($combination->value_types['true']);
661
        }
662
663
        if ($type instanceof TArray && isset($combination->builtin_type_params['iterable'])) {
664
            $type_key = 'iterable';
665
        } elseif ($type instanceof TArray
666
            && $type->type_params[1]->isMixed()
667
            && isset($combination->value_types['iterable'])
668
        ) {
669
            $type_key = 'iterable';
670
            $combination->builtin_type_params['iterable'] = [Type::getMixed(), Type::getMixed()];
671
        } elseif ($type instanceof TNamedObject
672
            && $type->value === 'Traversable'
673
            && (isset($combination->builtin_type_params['iterable']) || isset($combination->value_types['iterable']))
674
        ) {
675
            $type_key = 'iterable';
676
677
            if (!isset($combination->builtin_type_params['iterable'])) {
678
                $combination->builtin_type_params['iterable'] = [Type::getMixed(), Type::getMixed()];
679
            }
680
681
            if (!$type instanceof TGenericObject) {
682
                $type = new TGenericObject($type->value, [Type::getMixed(), Type::getMixed()]);
683
            }
684
        } elseif ($type instanceof TNamedObject && ($type->value === 'Traversable' || $type->value === 'Generator')) {
685
            $type_key = $type->value;
686
        } else {
687
            $type_key = $type->getKey();
688
        }
689
690
        if ($type instanceof TIterable
691
            && $combination->array_type_params
692
            && ($type->has_docblock_params || $combination->array_type_params[1]->isMixed())
693
        ) {
694
            if (!isset($combination->builtin_type_params['iterable'])) {
695
                $combination->builtin_type_params['iterable'] = $combination->array_type_params;
696
            } else {
697 View Code Duplication
                foreach ($combination->array_type_params as $i => $array_type_param) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
698
                    $iterable_type_param = $combination->builtin_type_params['iterable'][$i];
699
                    $combination->builtin_type_params['iterable'][$i] = Type::combineUnionTypes(
700
                        $iterable_type_param,
701
                        $array_type_param
702
                    );
703
                }
704
            }
705
706
            $combination->array_type_params = [];
707
        }
708
709
        if ($type instanceof TIterable
710
            && (isset($combination->named_object_types['Traversable'])
711
                || isset($combination->builtin_type_params['Traversable']))
712
        ) {
713
            if (!isset($combination->builtin_type_params['iterable'])) {
714
                $combination->builtin_type_params['iterable']
715
                    = $combination->builtin_type_params['Traversable'] ?? [Type::getMixed(), Type::getMixed()];
716
            } elseif (isset($combination->builtin_type_params['Traversable'])) {
717 View Code Duplication
                foreach ($combination->builtin_type_params['Traversable'] as $i => $array_type_param) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
718
                    $iterable_type_param = $combination->builtin_type_params['iterable'][$i];
719
                    $combination->builtin_type_params['iterable'][$i] = Type::combineUnionTypes(
720
                        $iterable_type_param,
721
                        $array_type_param
722
                    );
723
                }
724
            } else {
725
                $combination->builtin_type_params['iterable'] = [Type::getMixed(), Type::getMixed()];
726
            }
727
728
            /** @psalm-suppress PossiblyNullArrayAccess */
729
            unset(
730
                $combination->named_object_types['Traversable'],
731
                $combination->builtin_type_params['Traversable']
732
            );
733
        }
734
735
        if ($type instanceof TNamedObject
736
            || $type instanceof TTemplateParam
737
            || $type instanceof TIterable
738
            || $type instanceof Type\Atomic\TObjectWithProperties
739
        ) {
740
            if ($type->extra_types) {
741
                $combination->extra_types = array_merge(
742
                    $combination->extra_types ?: [],
743
                    $type->extra_types
744
                );
745
            }
746
        }
747
748
        if ($type instanceof TArray && $type_key === 'array') {
749
            if ($type instanceof TCallableArray && isset($combination->value_types['callable'])) {
750
                return;
751
            }
752
753 View Code Duplication
            foreach ($type->type_params as $i => $type_param) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
754
                if (isset($combination->array_type_params[$i])) {
755
                    $combination->array_type_params[$i] = Type::combineUnionTypes(
756
                        $combination->array_type_params[$i],
757
                        $type_param,
758
                        $codebase,
759
                        $overwrite_empty_array
760
                    );
761
                } else {
762
                    $combination->array_type_params[$i] = $type_param;
763
                }
764
            }
765
766 View Code Duplication
            if ($type instanceof TNonEmptyArray) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
767
                if ($combination->array_counts !== null) {
768
                    if ($type->count === null) {
769
                        $combination->array_counts = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $array_counts.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
770
                    } else {
771
                        $combination->array_counts[$type->count] = true;
772
                    }
773
                }
774
775
                $combination->array_sometimes_filled = true;
776
            } else {
777
                $combination->array_always_filled = false;
778
            }
779
780
            if (!$type->type_params[1]->isEmpty()) {
781
                $combination->all_arrays_lists = false;
782
                $combination->all_arrays_class_string_maps = false;
783
            }
784
785 View Code Duplication
            if ($type instanceof TCallableArray) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
786
                if ($combination->all_arrays_callable !== false) {
787
                    $combination->all_arrays_callable = true;
788
                }
789
            } else {
790
                $combination->all_arrays_callable = false;
791
            }
792
793
            return null;
794
        }
795
796
        if ($type instanceof TList) {
797 View Code Duplication
            foreach ([Type::getInt(), $type->type_param] as $i => $type_param) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
798
                if (isset($combination->array_type_params[$i])) {
799
                    $combination->array_type_params[$i] = Type::combineUnionTypes(
800
                        $combination->array_type_params[$i],
801
                        $type_param,
802
                        $codebase,
803
                        $overwrite_empty_array
804
                    );
805
                } else {
806
                    $combination->array_type_params[$i] = $type_param;
807
                }
808
            }
809
810 View Code Duplication
            if ($type instanceof TNonEmptyList) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
811
                if ($combination->array_counts !== null) {
812
                    if ($type->count === null) {
813
                        $combination->array_counts = null;
814
                    } else {
815
                        $combination->array_counts[$type->count] = true;
816
                    }
817
                }
818
819
                $combination->array_sometimes_filled = true;
820
            } else {
821
                $combination->array_always_filled = false;
822
            }
823
824
            if ($combination->all_arrays_lists !== false) {
825
                $combination->all_arrays_lists = true;
826
            }
827
828
            $combination->all_arrays_callable = false;
829
            $combination->all_arrays_class_string_maps = false;
830
831
            return null;
832
        }
833
834
        if ($type instanceof Atomic\TClassStringMap) {
835 View Code Duplication
            foreach ([$type->getStandinKeyParam(), $type->value_param] as $i => $type_param) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
836
                if (isset($combination->array_type_params[$i])) {
837
                    $combination->array_type_params[$i] = Type::combineUnionTypes(
838
                        $combination->array_type_params[$i],
839
                        $type_param,
840
                        $codebase,
841
                        $overwrite_empty_array
842
                    );
843
                } else {
844
                    $combination->array_type_params[$i] = $type_param;
845
                }
846
            }
847
848
            $combination->array_always_filled = false;
849
850
            if ($combination->all_arrays_class_string_maps !== false) {
851
                $combination->all_arrays_class_string_maps = true;
852
                $combination->class_string_map_names[$type->param_name] = true;
853
                $combination->class_string_map_as_types[(string) $type->as_type] = $type->as_type;
854
            }
855
856
            return null;
857
        }
858
859
        if (($type instanceof TGenericObject && ($type->value === 'Traversable' || $type->value === 'Generator'))
860
            || ($type instanceof TIterable && $type->has_docblock_params)
861
            || ($type instanceof TArray && $type_key === 'iterable')
862
        ) {
863 View Code Duplication
            foreach ($type->type_params as $i => $type_param) {
0 ignored issues
show
Bug introduced by
The property type_params does not seem to exist in Psalm\Type\Atomic.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
864
                if (isset($combination->builtin_type_params[$type_key][$i])) {
865
                    $combination->builtin_type_params[$type_key][$i] = Type::combineUnionTypes(
866
                        $combination->builtin_type_params[$type_key][$i],
867
                        $type_param,
868
                        $codebase,
869
                        $overwrite_empty_array
870
                    );
871
                } else {
872
                    $combination->builtin_type_params[$type_key][$i] = $type_param;
873
                }
874
            }
875
876
            return null;
877
        }
878
879
        if ($type instanceof TGenericObject) {
880 View Code Duplication
            foreach ($type->type_params as $i => $type_param) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
881
                if (isset($combination->object_type_params[$type_key][$i])) {
882
                    $combination->object_type_params[$type_key][$i] = Type::combineUnionTypes(
883
                        $combination->object_type_params[$type_key][$i],
884
                        $type_param,
885
                        $codebase,
886
                        $overwrite_empty_array
887
                    );
888
                } else {
889
                    $combination->object_type_params[$type_key][$i] = $type_param;
890
                }
891
            }
892
893
            return null;
894
        }
895
896
        if ($type instanceof ObjectLike) {
897
            if ($type instanceof TCallableObjectLikeArray && isset($combination->value_types['callable'])) {
898
                return;
899
            }
900
901
            $existing_objectlike_entries = (bool) $combination->objectlike_entries;
902
            $possibly_undefined_entries = $combination->objectlike_entries;
903
            $combination->objectlike_sealed = $combination->objectlike_sealed && $type->sealed;
904
905 View Code Duplication
            if ($type->previous_value_type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
906
                if (!$combination->objectlike_value_type) {
907
                    $combination->objectlike_value_type = $type->previous_value_type;
908
                } else {
909
                    $combination->objectlike_value_type = Type::combineUnionTypes(
910
                        $type->previous_value_type,
911
                        $combination->objectlike_value_type,
912
                        $codebase,
913
                        $overwrite_empty_array
914
                    );
915
                }
916
            }
917
918 View Code Duplication
            if ($type->previous_key_type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
919
                if (!$combination->objectlike_key_type) {
920
                    $combination->objectlike_key_type = $type->previous_key_type;
921
                } else {
922
                    $combination->objectlike_key_type = Type::combineUnionTypes(
923
                        $type->previous_key_type,
924
                        $combination->objectlike_key_type,
925
                        $codebase,
926
                        $overwrite_empty_array
927
                    );
928
                }
929
            }
930
931
            foreach ($type->properties as $candidate_property_name => $candidate_property_type) {
932
                $value_type = isset($combination->objectlike_entries[$candidate_property_name])
933
                    ? $combination->objectlike_entries[$candidate_property_name]
934
                    : null;
935
936
                if (!$value_type) {
937
                    $combination->objectlike_entries[$candidate_property_name] = clone $candidate_property_type;
938
                    // it's possibly undefined if there are existing objectlike entries and
939
                    $combination->objectlike_entries[$candidate_property_name]->possibly_undefined
940
                        = $existing_objectlike_entries || $candidate_property_type->possibly_undefined;
941
                } else {
942
                    $combination->objectlike_entries[$candidate_property_name] = Type::combineUnionTypes(
943
                        $value_type,
944
                        $candidate_property_type,
945
                        $codebase,
946
                        $overwrite_empty_array
947
                    );
948
                }
949
950
                if (!$type->previous_value_type) {
951
                    unset($possibly_undefined_entries[$candidate_property_name]);
952
                }
953
            }
954
955
            if ($combination->array_counts !== null) {
956
                $combination->array_counts[count($type->properties)] = true;
957
            }
958
959
            foreach ($possibly_undefined_entries as $possibly_undefined_type) {
960
                $possibly_undefined_type->possibly_undefined = true;
961
            }
962
963
            if (!$type->is_list) {
964
                $combination->all_arrays_lists = false;
965
            } elseif ($combination->all_arrays_lists !== false) {
966
                $combination->all_arrays_lists = true;
967
            }
968
969 View Code Duplication
            if ($type instanceof TCallableObjectLikeArray) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
970
                if ($combination->all_arrays_callable !== false) {
971
                    $combination->all_arrays_callable = true;
972
                }
973
            } else {
974
                $combination->all_arrays_callable = false;
975
            }
976
977
            $combination->all_arrays_class_string_maps = false;
978
979
            return null;
980
        }
981
982
        if ($type instanceof TObject) {
983
            if ($type instanceof TCallableObject && isset($combination->value_types['callable'])) {
984
                return;
985
            }
986
987
            $combination->named_object_types = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $named_object_types.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
988
            $combination->value_types[$type_key] = $type;
989
990
            return null;
991
        }
992
993
        if ($type instanceof TIterable) {
994
            $combination->value_types[$type_key] = $type;
995
996
            return null;
997
        }
998
999
        if ($type instanceof TNamedObject) {
1000
            if ($combination->named_object_types === null) {
1001
                return null;
1002
            }
1003
1004
            if (isset($combination->named_object_types[$type_key])) {
1005
                return null;
1006
            }
1007
1008
            if (!$codebase) {
1009
                $combination->named_object_types[$type_key] = $type;
1010
1011
                return null;
1012
            }
1013
1014
            if (!$codebase->classlikes->classOrInterfaceExists($type_key)) {
1015
                // write this to the main list
1016
                $combination->value_types[$type_key] = $type;
1017
1018
                return null;
1019
            }
1020
1021
            $is_class = $codebase->classExists($type_key);
1022
1023
            foreach ($combination->named_object_types as $key => $_) {
1024
                if ($codebase->classExists($key)) {
1025
                    if ($codebase->classExtendsOrImplements($key, $type_key)) {
1026
                        unset($combination->named_object_types[$key]);
1027
                        continue;
1028
                    }
1029
1030
                    if ($is_class) {
1031
                        if ($codebase->classExtends($type_key, $key)) {
1032
                            return null;
1033
                        }
1034
                    }
1035
                } else {
1036
                    if ($codebase->interfaceExtends($key, $type_key)) {
1037
                        unset($combination->named_object_types[$key]);
1038
                        continue;
1039
                    }
1040
1041
                    if ($is_class) {
1042
                        if ($codebase->classImplements($type_key, $key)) {
1043
                            return null;
1044
                        }
1045
                    } else {
1046
                        if ($codebase->interfaceExtends($type_key, $key)) {
1047
                            return null;
1048
                        }
1049
                    }
1050
                }
1051
            }
1052
1053
            $combination->named_object_types[$type_key] = $type;
1054
1055
            return null;
1056
        }
1057
1058
        if ($type instanceof TScalar) {
1059
            $combination->strings = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $strings.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1060
            $combination->ints = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $ints.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1061
            $combination->floats = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $floats.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1062
            unset(
1063
                $combination->value_types['string'],
1064
                $combination->value_types['int'],
1065
                $combination->value_types['bool'],
1066
                $combination->value_types['true'],
1067
                $combination->value_types['false'],
1068
                $combination->value_types['float']
1069
            );
1070
            $combination->value_types[$type_key] = $type;
1071
1072
            return null;
1073
        }
1074
1075
        if ($type instanceof Scalar && isset($combination->value_types['scalar'])) {
1076
            return null;
1077
        }
1078
1079
        if ($type instanceof TArrayKey) {
1080
            $combination->strings = null;
1081
            $combination->ints = null;
1082
            unset(
1083
                $combination->value_types['string'],
1084
                $combination->value_types['int']
1085
            );
1086
            $combination->value_types[$type_key] = $type;
1087
1088
            return null;
1089
        }
1090
1091
        if ($type instanceof TString) {
1092
            if ($type instanceof TCallableString && isset($combination->value_types['callable'])) {
1093
                return;
1094
            }
1095
1096
            if (isset($combination->value_types['array-key'])) {
1097
                return null;
1098
            }
1099
1100
            if ($type instanceof Type\Atomic\TTemplateParamClass) {
1101
                $combination->value_types[$type_key] = $type;
1102 View Code Duplication
            } elseif ($type instanceof Type\Atomic\TClassString) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1103
                if (!$type->as_type) {
1104
                    $combination->class_string_types['object'] = new TObject();
1105
                } else {
1106
                    $combination->class_string_types[$type->as] = $type->as_type;
1107
                }
1108
            } elseif ($type instanceof TLiteralString) {
1109
                if ($combination->strings !== null && count($combination->strings) < $literal_limit) {
1110
                    $combination->strings[$type_key] = $type;
1111
                } else {
1112
                    $shared_classlikes = $codebase ? $combination->getSharedTypes($codebase) : [];
1113
1114
                    $combination->strings = null;
1115
1116
                    if (isset($combination->value_types['string'])
1117
                        && $combination->value_types['string'] instanceof Type\Atomic\TNumericString
1118
                        && \is_numeric($type->value)
1119
                    ) {
1120
                        // do nothing
1121
                    } elseif (isset($combination->value_types['class-string'])
1122
                        && $type instanceof TLiteralClassString
1123
                    ) {
1124
                        // do nothing
1125
                    } elseif ($type instanceof TLiteralClassString) {
1126
                        $type_classlikes = $codebase
1127
                            ? self::getClassLikes($codebase, $type->value)
1128
                            : [];
1129
1130
                        $mutual = array_intersect_key($type_classlikes, $shared_classlikes);
1131
1132
                        if ($mutual) {
1133
                            $first_class = array_keys($mutual)[0];
1134
1135
                            $combination->class_string_types[$first_class] = new TNamedObject($first_class);
1136
                        } else {
1137
                            $combination->class_string_types['object'] = new TObject();
1138
                        }
1139
                    } else {
1140
                        $combination->value_types['string'] = new TString();
1141
                    }
1142
                }
1143
            } else {
1144
                $type_key = 'string';
1145
1146
                if (!isset($combination->value_types['string'])) {
1147
                    if ($combination->strings) {
1148
                        if ($type instanceof Type\Atomic\TNumericString) {
1149
                            $has_non_numeric_string = false;
1150
1151
                            foreach ($combination->strings as $string_type) {
1152
                                if (!\is_numeric($string_type->value)) {
1153
                                    $has_non_numeric_string = true;
1154
                                    break;
1155
                                }
1156
                            }
1157
1158
                            if ($has_non_numeric_string) {
1159
                                $combination->value_types['string'] = new TString();
1160
                            } else {
1161
                                $combination->value_types['string'] = $type;
1162
                            }
1163
1164
                            $combination->strings = null;
1165
                        } else {
1166
                            $has_non_literal_class_string = false;
1167
1168
                            $shared_classlikes = $codebase ? $combination->getSharedTypes($codebase) : [];
1169
1170
                            foreach ($combination->strings as $string_type) {
1171
                                if (!$string_type instanceof TLiteralClassString) {
1172
                                    $has_non_literal_class_string = true;
1173
                                    break;
1174
                                }
1175
                            }
1176
1177
                            if ($has_non_literal_class_string ||
1178
                                !$type instanceof TClassString
1179
                            ) {
1180
                                $combination->value_types[$type_key] = new TString();
1181
                            } else {
1182 View Code Duplication
                                if (isset($shared_classlikes[$type->as]) && $type->as_type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1183
                                    $combination->class_string_types[$type->as] = $type->as_type;
1184
                                } else {
1185
                                    $combination->class_string_types['object'] = new TObject();
1186
                                }
1187
                            }
1188
                        }
1189
                    } else {
1190
                        $combination->value_types[$type_key] = $type;
1191
                    }
1192
                } elseif (get_class($combination->value_types['string']) !== TString::class) {
1193
                    if (get_class($type) === TString::class) {
1194
                        $combination->value_types['string'] = $type;
1195
                    } elseif ($combination->value_types['string'] instanceof TTraitString
1196
                        && $type instanceof TClassString
1197
                    ) {
1198
                        $combination->value_types['trait-string'] = $combination->value_types['string'];
1199
                        $combination->value_types['class-string'] = $type;
1200
1201
                        unset($combination->value_types['string']);
1202
                    } elseif (get_class($combination->value_types['string']) !== get_class($type)) {
1203
                        if (get_class($type) === TNonEmptyString::class
1204
                            && get_class($combination->value_types['string']) === TNonEmptyLowercaseString::class
1205
                        ) {
1206
                            $combination->value_types['string'] = $type;
1207 View Code Duplication
                        } elseif (get_class($combination->value_types['string']) === TNonEmptyString::class
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1208
                            && get_class($type) === TNonEmptyLowercaseString::class
1209
                        ) {
1210
                            $combination->value_types['string'] = $combination->value_types['string'];
1211
                        } elseif (get_class($type) === TLowercaseString::class
1212
                            && get_class($combination->value_types['string']) === TNonEmptyLowercaseString::class
1213
                        ) {
1214
                            $combination->value_types['string'] = $type;
1215 View Code Duplication
                        } elseif (get_class($combination->value_types['string']) === TLowercaseString::class
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1216
                            && get_class($type) === TNonEmptyLowercaseString::class
1217
                        ) {
1218
                            $combination->value_types['string'] = $combination->value_types['string'];
1219
                        } else {
1220
                            $combination->value_types['string'] = new TString();
1221
                        }
1222
                    }
1223
                }
1224
1225
                $combination->strings = null;
1226
            }
1227
1228
            return null;
1229
        }
1230
1231
        if ($type instanceof TInt) {
1232
            if (isset($combination->value_types['array-key'])) {
1233
                return null;
1234
            }
1235
1236
            if ($type instanceof TLiteralInt) {
1237 View Code Duplication
                if ($combination->ints !== null && count($combination->ints) < $literal_limit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1238
                    $combination->ints[$type_key] = $type;
1239
                } else {
1240
                    $combination->ints = null;
1241
                    $combination->value_types['int'] = new TInt();
1242
                }
1243
            } else {
1244
                $combination->ints = null;
1245
                $combination->value_types['int'] = $type;
1246
            }
1247
1248
            return null;
1249
        }
1250
1251
        if ($type instanceof TFloat) {
1252
            if ($type instanceof TLiteralFloat) {
1253 View Code Duplication
                if ($combination->floats !== null && count($combination->floats) < $literal_limit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1254
                    $combination->floats[$type_key] = $type;
1255
                } else {
1256
                    $combination->floats = null;
1257
                    $combination->value_types['float'] = new TFloat();
1258
                }
1259
            } else {
1260
                $combination->floats = null;
1261
                $combination->value_types['float'] = $type;
1262
            }
1263
1264
            return null;
1265
        }
1266
1267
        if ($type instanceof TCallable && $type_key === 'callable') {
1268
            if (($combination->value_types['string'] ?? null) instanceof TCallableString) {
1269
                unset($combination->value_types['string']);
1270
            } elseif (!empty($combination->array_type_params) && $combination->all_arrays_callable) {
1271
                $combination->array_type_params = [];
1272
            } elseif (isset($combination->value_types['callable-object'])) {
1273
                unset($combination->value_types['callable-object']);
1274
            }
1275
        }
1276
1277
        $combination->value_types[$type_key] = $type;
1278
    }
1279
1280
    /**
1281
     * @return array<string, bool>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1282
     */
1283
    private function getSharedTypes(Codebase $codebase) : array
1284
    {
1285
        /** @var array<string, bool>|null */
1286
        $shared_classlikes = null;
1287
1288
        if ($this->strings) {
1289
            foreach ($this->strings as $string_type) {
1290
                $classlikes = self::getClassLikes($codebase, $string_type->value);
1291
1292 View Code Duplication
                if ($shared_classlikes === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1293
                    $shared_classlikes = $classlikes;
1294
                } elseif ($shared_classlikes) {
1295
                    $shared_classlikes = array_intersect_key($shared_classlikes, $classlikes);
1296
                }
1297
            }
1298
        }
1299
1300
        if ($this->class_string_types) {
1301
            foreach ($this->class_string_types as $value_type) {
1302
                if ($value_type instanceof TNamedObject) {
1303
                    $classlikes = self::getClassLikes($codebase, $value_type->value);
1304
1305 View Code Duplication
                    if ($shared_classlikes === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1306
                        $shared_classlikes = $classlikes;
1307
                    } elseif ($shared_classlikes) {
1308
                        $shared_classlikes = array_intersect_key($shared_classlikes, $classlikes);
1309
                    }
1310
                }
1311
            }
1312
        }
1313
1314
        return $shared_classlikes ?: [];
1315
    }
1316
1317
    /**
1318
     * @return array<string, bool>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1319
     */
1320
    private static function getClassLikes(Codebase $codebase, string $fq_classlike_name)
1321
    {
1322
        try {
1323
            $class_storage = $codebase->classlike_storage_provider->get($fq_classlike_name);
1324
        } catch (\InvalidArgumentException $e) {
1325
            return [];
1326
        }
1327
1328
        $classlikes = [];
1329
1330
        $classlikes[$fq_classlike_name] = true;
1331
1332
        foreach ($class_storage->parent_classes as $parent_class) {
1333
            $classlikes[$parent_class] = true;
1334
        }
1335
1336
        foreach ($class_storage->parent_interfaces as $parent_interface) {
1337
            $classlikes[$parent_interface] = true;
1338
        }
1339
1340
        foreach ($class_storage->class_implements as $interface) {
1341
            $classlikes[$interface] = true;
1342
        }
1343
1344
        return $classlikes;
1345
    }
1346
}
1347