TypeAnalyzer::getCallableMethodIdFromObjectLike()   D
last analyzed

Complexity

Conditions 27
Paths 39

Size

Total Lines 76

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
nc 39
nop 4
dl 0
loc 76
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
namespace Psalm\Internal\Analyzer;
3
4
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
5
use Psalm\Codebase;
6
use Psalm\Internal\Codebase\InternalCallMapHandler;
7
use Psalm\Type;
8
use Psalm\Type\Atomic\ObjectLike;
9
use Psalm\Type\Atomic\TObjectWithProperties;
10
use Psalm\Type\Atomic\Scalar;
11
use Psalm\Type\Atomic\TArray;
12
use Psalm\Type\Atomic\TArrayKey;
13
use Psalm\Type\Atomic\TBool;
14
use Psalm\Type\Atomic\TClassString;
15
use Psalm\Type\Atomic\TClassStringMap;
16
use Psalm\Type\Atomic\TCallable;
17
use Psalm\Type\Atomic\TCallableString;
18
use Psalm\Type\Atomic\TEmptyMixed;
19
use Psalm\Type\Atomic\TFalse;
20
use Psalm\Type\Atomic\TFloat;
21
use Psalm\Type\Atomic\TGenericObject;
22
use Psalm\Type\Atomic\TList;
23
use Psalm\Type\Atomic\TTemplateParam;
24
use Psalm\Type\Atomic\TTemplateParamClass;
25
use Psalm\Type\Atomic\GetClassT;
26
use Psalm\Type\Atomic\GetTypeT;
27
use Psalm\Type\Atomic\TConditional;
28
use Psalm\Type\Atomic\THtmlEscapedString;
29
use Psalm\Type\Atomic\TInt;
30
use Psalm\Type\Atomic\TIterable;
31
use Psalm\Type\Atomic\TLiteralClassString;
32
use Psalm\Type\Atomic\TLiteralFloat;
33
use Psalm\Type\Atomic\TLiteralInt;
34
use Psalm\Type\Atomic\TLiteralString;
35
use Psalm\Type\Atomic\TMixed;
36
use Psalm\Type\Atomic\TNamedObject;
37
use Psalm\Type\Atomic\TNever;
38
use Psalm\Type\Atomic\TNonEmptyArray;
39
use Psalm\Type\Atomic\TNonEmptyList;
40
use Psalm\Type\Atomic\TNonEmptyString;
41
use Psalm\Type\Atomic\TNull;
42
use Psalm\Type\Atomic\TNumeric;
43
use Psalm\Type\Atomic\TNumericString;
44
use Psalm\Type\Atomic\TObject;
45
use Psalm\Type\Atomic\TScalar;
46
use Psalm\Type\Atomic\TSingleLetter;
47
use Psalm\Type\Atomic\TString;
48
use Psalm\Type\Atomic\TTraitString;
49
use Psalm\Type\Atomic\TTrue;
50
use function get_class;
51
use function array_merge;
52
use function strtolower;
53
use function in_array;
54
use function array_values;
55
use function count;
56
use function is_string;
57
use function array_fill;
58
use function array_keys;
59
use function array_reduce;
60
use function end;
61
use function array_unique;
62
63
/**
64
 * @internal
65
 */
66
class TypeAnalyzer
67
{
68
    /**
69
     * Does the input param type match the given param type
70
     */
71
    public static function isContainedBy(
72
        Codebase $codebase,
73
        Type\Union $input_type,
74
        Type\Union $container_type,
75
        bool $ignore_null = false,
76
        bool $ignore_false = false,
77
        ?TypeComparisonResult $union_comparison_result = null,
78
        bool $allow_interface_equality = false
79
    ) : bool {
80
        if ($union_comparison_result) {
81
            $union_comparison_result->scalar_type_match_found = true;
82
        }
83
84
        if ($input_type->possibly_undefined && !$container_type->possibly_undefined) {
85
            return false;
86
        }
87
88
        if ($container_type->hasMixed() && !$container_type->isEmptyMixed()) {
89
            return true;
90
        }
91
92
        $container_has_template = $container_type->hasTemplateOrStatic();
93
94
        $input_atomic_types = \array_reverse($input_type->getAtomicTypes());
95
96
        while ($input_type_part = \array_pop($input_atomic_types)) {
97
            if ($input_type_part instanceof TNull && $ignore_null) {
98
                continue;
99
            }
100
101
            if ($input_type_part instanceof TFalse && $ignore_false) {
102
                continue;
103
            }
104
105
            if ($input_type_part instanceof TTemplateParam
106
                && !$container_has_template
107
                && !$input_type_part->extra_types
108
            ) {
109
                $input_atomic_types = array_merge($input_type_part->as->getAtomicTypes(), $input_atomic_types);
110
                continue;
111
            }
112
113
            $type_match_found = false;
114
            $scalar_type_match_found = false;
115
            $all_to_string_cast = true;
116
117
            $all_type_coerced = null;
118
            $all_type_coerced_from_mixed = null;
119
            $all_type_coerced_from_as_mixed = null;
120
121
            $some_type_coerced = false;
122
            $some_type_coerced_from_mixed = false;
123
124
            if ($input_type_part instanceof TArrayKey
125
                && ($container_type->hasInt() && $container_type->hasString())
126
            ) {
127
                continue;
128
            }
129
130
            foreach ($container_type->getAtomicTypes() as $container_type_part) {
131
                if ($ignore_null
132
                    && $container_type_part instanceof TNull
133
                    && !$input_type_part instanceof TNull
134
                ) {
135
                    continue;
136
                }
137
138
                if ($ignore_false
139
                    && $container_type_part instanceof TFalse
140
                    && !$input_type_part instanceof TFalse
141
                ) {
142
                    continue;
143
                }
144
145
                if ($union_comparison_result) {
146
                    $atomic_comparison_result = new TypeComparisonResult();
147
                } else {
148
                    $atomic_comparison_result = null;
149
                }
150
151
                $is_atomic_contained_by = self::isAtomicContainedBy(
152
                    $codebase,
153
                    $input_type_part,
154
                    $container_type_part,
155
                    $allow_interface_equality,
156
                    true,
157
                    $atomic_comparison_result
158
                );
159
160
                if ($input_type_part instanceof TMixed
161
                    && $input_type->from_template_default
162
                    && $input_type->from_docblock
163
                    && $atomic_comparison_result
164
                    && $atomic_comparison_result->type_coerced_from_mixed
165
                ) {
166
                    $atomic_comparison_result->type_coerced_from_as_mixed = true;
167
                }
168
169
                if ($atomic_comparison_result) {
170
                    if ($atomic_comparison_result->scalar_type_match_found !== null) {
171
                        $scalar_type_match_found = $atomic_comparison_result->scalar_type_match_found;
172
                    }
173
174
                    if ($union_comparison_result
175
                        && $atomic_comparison_result->type_coerced_from_scalar !== null
176
                    ) {
177
                        $union_comparison_result->type_coerced_from_scalar
178
                            = $atomic_comparison_result->type_coerced_from_scalar;
179
                    }
180
181
                    if ($is_atomic_contained_by
182
                        && $union_comparison_result
183
                        && $atomic_comparison_result->replacement_atomic_type
184
                    ) {
185
                        if (!$union_comparison_result->replacement_union_type) {
186
                            $union_comparison_result->replacement_union_type = clone $input_type;
187
                        }
188
189
                        $union_comparison_result->replacement_union_type->removeType($input_type->getKey());
190
191
                        $union_comparison_result->replacement_union_type->addType(
192
                            $atomic_comparison_result->replacement_atomic_type
193
                        );
194
                    }
195
                }
196
197
                if ($input_type_part instanceof TNumeric
198
                    && $container_type->hasString()
199
                    && $container_type->hasInt()
200
                    && $container_type->hasFloat()
201
                ) {
202
                    $scalar_type_match_found = false;
203
                    $is_atomic_contained_by = true;
204
                }
205
206
                if ($atomic_comparison_result) {
207
                    if ($atomic_comparison_result->type_coerced) {
208
                        $some_type_coerced = true;
209
                    }
210
211
                    if ($atomic_comparison_result->type_coerced_from_mixed) {
212
                        $some_type_coerced_from_mixed = true;
213
                    }
214
215
                    if ($atomic_comparison_result->type_coerced !== true || $all_type_coerced === false) {
216
                        $all_type_coerced = false;
217
                    } else {
218
                        $all_type_coerced = true;
219
                    }
220
221
                    if ($atomic_comparison_result->type_coerced_from_mixed !== true
222
                        || $all_type_coerced_from_mixed === false
223
                    ) {
224
                        $all_type_coerced_from_mixed = false;
225
                    } else {
226
                        $all_type_coerced_from_mixed = true;
227
                    }
228
229
                    if ($atomic_comparison_result->type_coerced_from_as_mixed !== true
230
                        || $all_type_coerced_from_as_mixed === false
231
                    ) {
232
                        $all_type_coerced_from_as_mixed = false;
233
                    } else {
234
                        $all_type_coerced_from_as_mixed = true;
235
                    }
236
                }
237
238
                if ($is_atomic_contained_by) {
239
                    $type_match_found = true;
240
241
                    if ($atomic_comparison_result) {
242
                        if ($atomic_comparison_result->to_string_cast !== true) {
243
                            $all_to_string_cast = false;
244
                        }
245
                    }
246
247
                    $all_type_coerced_from_mixed = false;
248
                    $all_type_coerced_from_as_mixed = false;
249
                    $all_type_coerced = false;
250
                }
251
            }
252
253
            if ($union_comparison_result) {
254
                // only set this flag if we're definite that the only
255
                // reason the type match has been found is because there
256
                // was a __toString cast
257
                if ($all_to_string_cast && $type_match_found) {
258
                    $union_comparison_result->to_string_cast = true;
259
                }
260
261
                if ($all_type_coerced) {
262
                    $union_comparison_result->type_coerced = true;
263
                }
264
265
                if ($all_type_coerced_from_mixed) {
266
                    $union_comparison_result->type_coerced_from_mixed = true;
267
268
                    if (($input_type->from_template_default && $input_type->from_docblock)
269
                        || $all_type_coerced_from_as_mixed
270
                    ) {
271
                        $union_comparison_result->type_coerced_from_as_mixed = true;
272
                    }
273
                }
274
            }
275
276
            if (!$type_match_found) {
277
                if ($union_comparison_result) {
278
                    if ($some_type_coerced) {
279
                        $union_comparison_result->type_coerced = true;
280
                    }
281
282
                    if ($some_type_coerced_from_mixed) {
283
                        $union_comparison_result->type_coerced_from_mixed = true;
284
285
                        if (($input_type->from_template_default && $input_type->from_docblock)
286
                            || $all_type_coerced_from_as_mixed
287
                        ) {
288
                            $union_comparison_result->type_coerced_from_as_mixed = true;
289
                        }
290
                    }
291
292
                    if (!$scalar_type_match_found) {
293
                        $union_comparison_result->scalar_type_match_found = false;
294
                    }
295
                }
296
297
                return false;
298
            }
299
        }
300
301
        return true;
302
    }
303
304
    /**
305
     * Used for comparing signature typehints, uses PHP's light contravariance rules
306
     *
307
     * @param  ?Type\Union  $input_type
0 ignored issues
show
Documentation introduced by
The doc-type ?Type\Union could not be parsed: Unknown type name "?Type\Union" 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...
308
     * @param  Type\Union   $container_type
309
     *
310
     * @return bool
311
     */
312
    public static function isContainedByInPhp(
313
        ?Type\Union $input_type,
314
        Type\Union $container_type
315
    ) {
316
        if (!$input_type) {
317
            return false;
318
        }
319
320
        if ($input_type->getId() === $container_type->getId()) {
321
            return true;
322
        }
323
324
        if ($input_type->isNullable() && !$container_type->isNullable()) {
325
            return false;
326
        }
327
328
        $input_type_not_null = clone $input_type;
329
        $input_type_not_null->removeType('null');
330
331
        $container_type_not_null = clone $container_type;
332
        $container_type_not_null->removeType('null');
333
334
        if ($input_type_not_null->getId() === $container_type_not_null->getId()) {
335
            return true;
336
        }
337
338
        if ($input_type_not_null->hasArray() && $container_type_not_null->hasType('iterable')) {
339
            return true;
340
        }
341
342
        return false;
343
    }
344
345
    /**
346
     * Used for comparing docblock types to signature types before we know about all types
347
     *
348
     * @param  Type\Union   $input_type
349
     * @param  Type\Union   $container_type
350
     */
351
    public static function isSimplyContainedBy(
352
        Type\Union $input_type,
353
        Type\Union $container_type
354
    ) : bool {
355
        if ($input_type->getId() === $container_type->getId()) {
356
            return true;
357
        }
358
359
        if ($input_type->isNullable() && !$container_type->isNullable()) {
360
            return false;
361
        }
362
363
        $input_type_not_null = clone $input_type;
364
        $input_type_not_null->removeType('null');
365
366
        $container_type_not_null = clone $container_type;
367
        $container_type_not_null->removeType('null');
368
369
        foreach ($input_type->getAtomicTypes() as $input_key => $input_type_part) {
370
            foreach ($container_type->getAtomicTypes() as $container_key => $container_type_part) {
371
                if (get_class($container_type_part) === TNamedObject::class
372
                    && $input_type_part instanceof TNamedObject
373
                    && $input_type_part->value === $container_type_part->value
374
                ) {
375
                    continue 2;
376
                }
377
378
                if ($input_key === $container_key) {
379
                    continue 2;
380
                }
381
            }
382
383
            return false;
384
        }
385
386
387
388
        return true;
389
    }
390
391
    /**
392
     * Does the input param type match the given param type
393
     *
394
     * @param  Type\Union   $input_type
395
     * @param  Type\Union   $container_type
396
     * @param  bool         $ignore_null
397
     * @param  bool         $ignore_false
398
     *
399
     * @return bool
400
     */
401
    public static function canBeContainedBy(
402
        Codebase $codebase,
403
        Type\Union $input_type,
404
        Type\Union $container_type,
405
        $ignore_null = false,
406
        $ignore_false = false,
407
        array &$matching_input_keys = []
408
    ) {
409
        if ($container_type->hasMixed()) {
410
            return true;
411
        }
412
413
        if ($input_type->possibly_undefined && !$container_type->possibly_undefined) {
414
            return false;
415
        }
416
417
        foreach ($container_type->getAtomicTypes() as $container_type_part) {
418
            if ($container_type_part instanceof TNull && $ignore_null) {
419
                continue;
420
            }
421
422
            if ($container_type_part instanceof TFalse && $ignore_false) {
423
                continue;
424
            }
425
426
            foreach ($input_type->getAtomicTypes() as $input_type_part) {
427
                $atomic_comparison_result = new TypeComparisonResult();
428
                $is_atomic_contained_by = self::isAtomicContainedBy(
429
                    $codebase,
430
                    $input_type_part,
431
                    $container_type_part,
432
                    false,
433
                    false,
434
                    $atomic_comparison_result
435
                );
436
437
                if (($is_atomic_contained_by && !$atomic_comparison_result->to_string_cast)
438
                    || $atomic_comparison_result->type_coerced_from_mixed
439
                ) {
440
                    $matching_input_keys[$input_type_part->getKey()] = true;
441
                }
442
            }
443
        }
444
445
        return !!$matching_input_keys;
446
    }
447
448
    /**
449
     * Can any part of the $type1 be equal to any part of $type2
450
     *
451
     * @return bool
452
     */
453
    public static function canExpressionTypesBeIdentical(
454
        Codebase $codebase,
455
        Type\Union $type1,
456
        Type\Union $type2
457
    ) {
458
        if ($type1->hasMixed() || $type2->hasMixed()) {
459
            return true;
460
        }
461
462
        if ($type1->isNullable() && $type2->isNullable()) {
463
            return true;
464
        }
465
466
        foreach ($type1->getAtomicTypes() as $type1_part) {
467
            foreach ($type2->getAtomicTypes() as $type2_part) {
468
                $first_comparison_result = new TypeComparisonResult();
469
                $second_comparison_result = new TypeComparisonResult();
470
471
                $either_contains = (self::isAtomicContainedBy(
472
                    $codebase,
473
                    $type1_part,
474
                    $type2_part,
475
                    true,
476
                    false,
477
                    $first_comparison_result
478
                )
479
                    && !$first_comparison_result->to_string_cast
480
                ) || (self::isAtomicContainedBy(
481
                    $codebase,
482
                    $type2_part,
483
                    $type1_part,
484
                    true,
485
                    false,
486
                    $second_comparison_result
487
                )
488
                    && !$second_comparison_result->to_string_cast
489
                ) || ($first_comparison_result->type_coerced
490
                    && $second_comparison_result->type_coerced
491
                );
492
493
                if ($either_contains) {
494
                    return true;
495
                }
496
            }
497
        }
498
499
        return false;
500
    }
501
502
    /**
503
     * @param  Codebase       $codebase
504
     * @param  TNamedObject|TTemplateParam|TIterable  $input_type_part
505
     * @param  TNamedObject|TTemplateParam|TIterable  $container_type_part
506
     * @param  bool           $allow_interface_equality
507
     *
508
     * @return bool
509
     */
510
    private static function isObjectContainedByObject(
511
        Codebase $codebase,
512
        $input_type_part,
513
        $container_type_part,
514
        $allow_interface_equality,
515
        ?TypeComparisonResult $atomic_comparison_result
516
    ) {
517
        $intersection_input_types = $input_type_part->extra_types ?: [];
518
        $intersection_input_types[$input_type_part->getKey(false)] = $input_type_part;
519
520
        if ($input_type_part instanceof TTemplateParam) {
521
            foreach ($input_type_part->as->getAtomicTypes() as $g) {
522
                if ($g instanceof TNamedObject && $g->extra_types) {
523
                    $intersection_input_types = array_merge(
524
                        $intersection_input_types,
525
                        $g->extra_types
526
                    );
527
                }
528
            }
529
        }
530
531
        $intersection_container_types = $container_type_part->extra_types ?: [];
532
        $intersection_container_types[$container_type_part->getKey(false)] = $container_type_part;
533
534
        if ($container_type_part instanceof TTemplateParam) {
535
            foreach ($container_type_part->as->getAtomicTypes() as $g) {
536
                if ($g instanceof TNamedObject && $g->extra_types) {
537
                    $intersection_container_types = array_merge(
538
                        $intersection_container_types,
539
                        $g->extra_types
540
                    );
541
                }
542
            }
543
        }
544
545
        foreach ($intersection_container_types as $container_type_key => $intersection_container_type) {
546
            $container_was_static = false;
547
548
            if ($intersection_container_type instanceof TIterable) {
549
                $intersection_container_type_lower = 'iterable';
550
            } elseif ($intersection_container_type instanceof TObjectWithProperties) {
551
                $intersection_container_type_lower = 'object';
552
            } elseif ($intersection_container_type instanceof TTemplateParam) {
553
                if (!$allow_interface_equality) {
554
                    if (isset($intersection_input_types[$container_type_key])) {
555
                        continue;
556
                    }
557
558
                    if (\substr($intersection_container_type->defining_class, 0, 3) === 'fn-') {
559
                        foreach ($intersection_input_types as $intersection_input_type) {
560
                            if ($intersection_input_type instanceof TTemplateParam
561
                                && \substr($intersection_input_type->defining_class, 0, 3) === 'fn-'
562
                                && $intersection_input_type->defining_class
563
                                    !== $intersection_container_type->defining_class
564
                            ) {
565
                                continue 2;
566
                            }
567
                        }
568
                    }
569
570
                    return false;
571
                }
572
573
                if ($intersection_container_type->as->isMixed()) {
574
                    continue;
575
                }
576
577
                $intersection_container_type_lower = null;
578
579
                foreach ($intersection_container_type->as->getAtomicTypes() as $g) {
580
                    if ($g instanceof TNull) {
581
                        continue;
582
                    }
583
584
                    if ($g instanceof TObject) {
585
                        continue 2;
586
                    }
587
588
                    if (!$g instanceof TNamedObject) {
589
                        continue 2;
590
                    }
591
592
                    $intersection_container_type_lower = strtolower($g->value);
593
                }
594
595
                if ($intersection_container_type_lower === null) {
596
                    return false;
597
                }
598
            } else {
599
                $container_was_static = $intersection_container_type->was_static;
600
601
                $intersection_container_type_lower = strtolower(
602
                    $codebase->classlikes->getUnAliasedName(
603
                        $intersection_container_type->value
604
                    )
605
                );
606
            }
607
608
            foreach ($intersection_input_types as $intersection_input_type) {
609
                $input_was_static = false;
610
611
                if ($intersection_input_type instanceof TIterable) {
612
                    $intersection_input_type_lower = 'iterable';
613
                } elseif ($intersection_input_type instanceof TObjectWithProperties) {
614
                    $intersection_input_type_lower = 'object';
615
                } elseif ($intersection_input_type instanceof TTemplateParam) {
616
                    if ($intersection_input_type->as->isMixed()) {
617
                        continue;
618
                    }
619
620
                    $intersection_input_type_lower = null;
621
622
                    foreach ($intersection_input_type->as->getAtomicTypes() as $g) {
623
                        if ($g instanceof TNull) {
624
                            continue;
625
                        }
626
627
                        if (!$g instanceof TNamedObject) {
628
                            continue 2;
629
                        }
630
631
                        $intersection_input_type_lower = strtolower($g->value);
632
                    }
633
634
                    if ($intersection_input_type_lower === null) {
635
                        return false;
636
                    }
637
                } else {
638
                    $input_was_static = $intersection_input_type->was_static;
639
640
                    $intersection_input_type_lower = strtolower(
641
                        $codebase->classlikes->getUnAliasedName(
642
                            $intersection_input_type->value
643
                        )
644
                    );
645
                }
646
647
                if ($intersection_container_type instanceof TTemplateParam
648
                    && $intersection_input_type instanceof TTemplateParam
649
                ) {
650
                    if ($intersection_container_type->param_name !== $intersection_input_type->param_name
651
                        || ((string)$intersection_container_type->defining_class
652
                            !== (string)$intersection_input_type->defining_class
653
                            && \substr($intersection_input_type->defining_class, 0, 3) !== 'fn-'
654
                            && \substr($intersection_container_type->defining_class, 0, 3) !== 'fn-')
655
                    ) {
656
                        if (\substr($intersection_input_type->defining_class, 0, 3) !== 'fn-') {
657
                            $input_class_storage = $codebase->classlike_storage_provider->get(
658
                                $intersection_input_type->defining_class
659
                            );
660
661
                            if (isset($input_class_storage->template_type_extends
662
                                    [$intersection_container_type->defining_class]
663
                                    [$intersection_container_type->param_name])
664
                            ) {
665
                                continue;
666
                            }
667
                        }
668
669
                        return false;
670
                    }
671
                }
672
673
                if (!$intersection_container_type instanceof TTemplateParam
674
                    || $intersection_input_type instanceof TTemplateParam
675
                ) {
676
                    if ($intersection_container_type_lower === $intersection_input_type_lower) {
677
                        if ($container_was_static
678
                            && !$input_was_static
679
                            && !$intersection_input_type instanceof TTemplateParam
680
                        ) {
681
                            if ($atomic_comparison_result) {
682
                                $atomic_comparison_result->type_coerced = true;
683
                            }
684
685
                            continue;
686
                        }
687
688
                        continue 2;
689
                    }
690
691
                    if ($intersection_input_type_lower === 'generator'
692
                        && in_array($intersection_container_type_lower, ['iterator', 'traversable', 'iterable'], true)
693
                    ) {
694
                        continue 2;
695
                    }
696
697
                    if ($intersection_container_type_lower === 'iterable') {
698
                        if ($intersection_input_type_lower === 'traversable'
699
                            || ($codebase->classlikes->classExists($intersection_input_type_lower)
700
                                && $codebase->classlikes->classImplements(
701
                                    $intersection_input_type_lower,
702
                                    'Traversable'
703
                                ))
704
                            || ($codebase->classlikes->interfaceExists($intersection_input_type_lower)
705
                                && $codebase->classlikes->interfaceExtends(
706
                                    $intersection_input_type_lower,
707
                                    'Traversable'
708
                                ))
709
                        ) {
710
                            continue 2;
711
                        }
712
                    }
713
714
                    if ($intersection_input_type_lower === 'traversable'
715
                        && $intersection_container_type_lower === 'iterable'
716
                    ) {
717
                        continue 2;
718
                    }
719
720
                    $input_type_is_interface = $codebase->interfaceExists($intersection_input_type_lower);
721
                    $container_type_is_interface = $codebase->interfaceExists($intersection_container_type_lower);
722
723
                    if ($allow_interface_equality && $input_type_is_interface && $container_type_is_interface) {
724
                        continue 2;
725
                    }
726
727
                    if ($codebase->classExists($intersection_input_type_lower)
728
                        && $codebase->classOrInterfaceExists($intersection_container_type_lower)
729
                        && $codebase->classExtendsOrImplements(
730
                            $intersection_input_type_lower,
731
                            $intersection_container_type_lower
732
                        )
733
                    ) {
734
                        if ($container_was_static && !$input_was_static) {
735
                            if ($atomic_comparison_result) {
736
                                $atomic_comparison_result->type_coerced = true;
737
                            }
738
739
                            continue;
740
                        }
741
742
                        continue 2;
743
                    }
744
745
                    if ($input_type_is_interface
746
                        && $codebase->interfaceExtends(
747
                            $intersection_input_type_lower,
748
                            $intersection_container_type_lower
749
                        )
750
                    ) {
751
                        continue 2;
752
                    }
753
                }
754
755
                if (ExpressionAnalyzer::isMock($intersection_input_type_lower)) {
756
                    return true;
757
                }
758
            }
759
760
            return false;
761
        }
762
763
        return true;
764
    }
765
766
    /**
767
     * Does the input param atomic type match the given param atomic type
768
     */
769
    public static function isAtomicContainedBy(
770
        Codebase $codebase,
771
        Type\Atomic $input_type_part,
772
        Type\Atomic $container_type_part,
773
        bool $allow_interface_equality = false,
774
        bool $allow_float_int_equality = true,
775
        ?TypeComparisonResult $atomic_comparison_result = null
776
    ) : bool {
777
        if (($container_type_part instanceof TTemplateParam
778
                || ($container_type_part instanceof TNamedObject
779
                    && isset($container_type_part->extra_types)))
780
            && ($input_type_part instanceof TTemplateParam
781
                || ($input_type_part instanceof TNamedObject
782
                    && isset($input_type_part->extra_types)))
783
        ) {
784
            return self::isObjectContainedByObject(
785
                $codebase,
786
                $input_type_part,
0 ignored issues
show
Documentation introduced by
$input_type_part is of type object<Psalm\Type\Atomic>, but the function expects a object<Psalm\Type\Atomic...\Type\Atomic\TIterable>.

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...
787
                $container_type_part,
0 ignored issues
show
Documentation introduced by
$container_type_part is of type object<Psalm\Type\Atomic>, but the function expects a object<Psalm\Type\Atomic...\Type\Atomic\TIterable>.

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...
788
                $allow_interface_equality,
789
                $atomic_comparison_result
790
            );
791
        }
792
793
        if ($container_type_part instanceof TMixed
794
            || ($container_type_part instanceof TTemplateParam
795
                && $container_type_part->as->isMixed()
0 ignored issues
show
Bug introduced by
The method isMixed cannot be called on $container_type_part->as (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
796
                && !$container_type_part->extra_types
797
                && $input_type_part instanceof TMixed)
798
        ) {
799
            if (get_class($container_type_part) === TEmptyMixed::class
800
                && get_class($input_type_part) === TMixed::class
801
            ) {
802
                if ($atomic_comparison_result) {
803
                    $atomic_comparison_result->type_coerced = true;
804
                    $atomic_comparison_result->type_coerced_from_mixed = true;
805
                }
806
807
                return false;
808
            }
809
810
            return true;
811
        }
812
813
        if ($input_type_part instanceof TNever || $input_type_part instanceof Type\Atomic\TEmpty) {
814
            return true;
815
        }
816
817
        if ($input_type_part instanceof TMixed
818
            || ($input_type_part instanceof TTemplateParam
819
                && $input_type_part->as->isMixed()
0 ignored issues
show
Bug introduced by
The method isMixed cannot be called on $input_type_part->as (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
820
                && !$input_type_part->extra_types)
821
        ) {
822
            if ($atomic_comparison_result) {
823
                $atomic_comparison_result->type_coerced = true;
824
                $atomic_comparison_result->type_coerced_from_mixed = true;
825
            }
826
827
            return false;
828
        }
829
830
        if ($input_type_part instanceof TNull) {
831
            if ($container_type_part instanceof TNull) {
832
                return true;
833
            }
834
835
            if ($container_type_part instanceof TTemplateParam
836
                && ($container_type_part->as->isNullable() || $container_type_part->as->isMixed())
0 ignored issues
show
Bug introduced by
The method isNullable cannot be called on $container_type_part->as (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Bug introduced by
The method isMixed cannot be called on $container_type_part->as (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
837
            ) {
838
                return true;
839
            }
840
841
            return false;
842
        }
843
844
        if ($container_type_part instanceof TNull) {
845
            return false;
846
        }
847
848
        if ($container_type_part instanceof ObjectLike
849
            && $input_type_part instanceof TArray
850
        ) {
851
            $all_string_int_literals = true;
852
853
            $properties = [];
854
855
            foreach ($input_type_part->type_params[0]->getAtomicTypes() as $atomic_key_type) {
856
                if ($atomic_key_type instanceof TLiteralString || $atomic_key_type instanceof TLiteralInt) {
857
                    $properties[$atomic_key_type->value] = $input_type_part->type_params[1];
858
                } else {
859
                    $all_string_int_literals = false;
860
                }
861
            }
862
863
            if ($all_string_int_literals && $properties) {
864
                $input_type_part = new ObjectLike($properties);
865
            }
866
        }
867
868
        if ($container_type_part instanceof TNonEmptyString
869
            && get_class($input_type_part) === TString::class
870
        ) {
871
            if ($atomic_comparison_result) {
872
                $atomic_comparison_result->type_coerced = true;
873
            }
874
875
            return false;
876
        }
877
878
        if (($input_type_part instanceof Type\Atomic\TLowercaseString
879
                || $input_type_part instanceof Type\Atomic\TNonEmptyLowercaseString)
880
            && get_class($container_type_part) === TString::class
881
        ) {
882
            return true;
883
        }
884
885
        if (($container_type_part instanceof Type\Atomic\TLowercaseString
886
                || $container_type_part instanceof Type\Atomic\TNonEmptyLowercaseString)
887
            && $input_type_part instanceof TString
888
        ) {
889
            if (($input_type_part instanceof Type\Atomic\TLowercaseString
890
                    && $container_type_part instanceof Type\Atomic\TLowercaseString)
891
                || ($input_type_part instanceof Type\Atomic\TNonEmptyLowercaseString
892
                    && $container_type_part instanceof Type\Atomic\TNonEmptyLowercaseString)
893
            ) {
894
                return true;
895
            }
896
897
            if ($input_type_part instanceof Type\Atomic\TNonEmptyLowercaseString
898
                && $container_type_part instanceof Type\Atomic\TLowercaseString
899
            ) {
900
                return true;
901
            }
902
903
            if ($input_type_part instanceof Type\Atomic\TLowercaseString
904
                && $container_type_part instanceof Type\Atomic\TNonEmptyLowercaseString
905
            ) {
906
                if ($atomic_comparison_result) {
907
                    $atomic_comparison_result->type_coerced = true;
908
                }
909
910
                return false;
911
            }
912
913
            if ($input_type_part instanceof TLiteralString) {
914
                if (strtolower($input_type_part->value) === $input_type_part->value) {
915
                    return $input_type_part->value || $container_type_part instanceof Type\Atomic\TLowercaseString;
916
                }
917
918
                return false;
919
            }
920
921
            if ($input_type_part instanceof TClassString) {
922
                return false;
923
            }
924
925
            if ($atomic_comparison_result) {
926
                $atomic_comparison_result->type_coerced = true;
927
            }
928
929
            return false;
930
        }
931
932
        if ($input_type_part->shallowEquals($container_type_part)
933
            || ($input_type_part instanceof Type\Atomic\TCallableObjectLikeArray
934
                && $container_type_part instanceof TArray)
935
            || ($input_type_part instanceof TCallable
936
                && $container_type_part instanceof TCallable)
937
            || ($input_type_part instanceof Type\Atomic\TFn
938
                && $container_type_part instanceof Type\Atomic\TFn)
939
            || (($input_type_part instanceof TNamedObject
940
                    || ($input_type_part instanceof TTemplateParam
941
                        && $input_type_part->as->hasObjectType())
0 ignored issues
show
Bug introduced by
The method hasObjectType cannot be called on $input_type_part->as (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
942
                    || $input_type_part instanceof TIterable)
943
                && ($container_type_part instanceof TNamedObject
944
                    || ($container_type_part instanceof TTemplateParam
945
                        && $container_type_part->isObjectType())
946
                    || $container_type_part instanceof TIterable)
947
                && self::isObjectContainedByObject(
948
                    $codebase,
949
                    $input_type_part,
0 ignored issues
show
Documentation introduced by
$input_type_part is of type object<Psalm\Type\Atomic>, but the function expects a object<Psalm\Type\Atomic...\Type\Atomic\TIterable>.

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...
950
                    $container_type_part,
0 ignored issues
show
Documentation introduced by
$container_type_part is of type object<Psalm\Type\Atomic>, but the function expects a object<Psalm\Type\Atomic...\Type\Atomic\TIterable>.

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...
951
                    $allow_interface_equality,
952
                    $atomic_comparison_result
953
                )
954
            )
955
        ) {
956
            return self::isMatchingTypeContainedBy(
957
                $codebase,
958
                $input_type_part,
959
                $container_type_part,
960
                $atomic_comparison_result ?: new TypeComparisonResult(),
961
                $allow_interface_equality
962
            );
963
        }
964
965
        if ($container_type_part instanceof TTemplateParam && $input_type_part instanceof TTemplateParam) {
966
            return self::isContainedBy(
967
                $codebase,
968
                $input_type_part->as,
0 ignored issues
show
Documentation introduced by
$input_type_part->as is of type string, 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...
969
                $container_type_part->as,
0 ignored issues
show
Documentation introduced by
$container_type_part->as is of type string, 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...
970
                false,
971
                false,
972
                $atomic_comparison_result,
973
                $allow_interface_equality
974
            );
975
        }
976
977
        if ($container_type_part instanceof TTemplateParam) {
978
            foreach ($container_type_part->as->getAtomicTypes() as $container_as_type_part) {
0 ignored issues
show
Bug introduced by
The method getAtomicTypes cannot be called on $container_type_part->as (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
979
                if (self::isAtomicContainedBy(
980
                    $codebase,
981
                    $input_type_part,
982
                    $container_as_type_part,
983
                    $allow_interface_equality,
984
                    $allow_float_int_equality,
985
                    $atomic_comparison_result
986
                )) {
987
                    if ($allow_interface_equality
988
                        || ($input_type_part instanceof TArray
989
                            && !$input_type_part->type_params[1]->isEmpty())
990
                        || $input_type_part instanceof ObjectLike
991
                    ) {
992
                        return true;
993
                    }
994
                }
995
            }
996
997
            return false;
998
        }
999
1000
        if ($container_type_part instanceof TConditional) {
1001
            $atomic_types = array_merge(
1002
                array_values($container_type_part->if_type->getAtomicTypes()),
1003
                array_values($container_type_part->else_type->getAtomicTypes())
1004
            );
1005
1006
            foreach ($atomic_types as $container_as_type_part) {
1007
                if (self::isAtomicContainedBy(
1008
                    $codebase,
1009
                    $input_type_part,
1010
                    $container_as_type_part,
1011
                    $allow_interface_equality,
1012
                    $allow_float_int_equality,
1013
                    $atomic_comparison_result
1014
                )) {
1015
                    return true;
1016
                }
1017
            }
1018
1019
            return false;
1020
        }
1021
1022
        if ($input_type_part instanceof TTemplateParam) {
1023
            if ($input_type_part->extra_types) {
1024
                foreach ($input_type_part->extra_types as $extra_type) {
1025
                    if (self::isAtomicContainedBy(
1026
                        $codebase,
1027
                        $extra_type,
1028
                        $container_type_part,
1029
                        $allow_interface_equality,
1030
                        $allow_float_int_equality,
1031
                        $atomic_comparison_result
1032
                    )) {
1033
                        return true;
1034
                    }
1035
                }
1036
            }
1037
1038
            foreach ($input_type_part->as->getAtomicTypes() as $input_as_type_part) {
0 ignored issues
show
Bug introduced by
The method getAtomicTypes cannot be called on $input_type_part->as (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1039
                if ($input_as_type_part instanceof TNull && $container_type_part instanceof TNull) {
1040
                    continue;
1041
                }
1042
1043
                if (self::isAtomicContainedBy(
1044
                    $codebase,
1045
                    $input_as_type_part,
1046
                    $container_type_part,
1047
                    $allow_interface_equality,
1048
                    $allow_float_int_equality,
1049
                    $atomic_comparison_result
1050
                )) {
1051
                    return true;
1052
                }
1053
            }
1054
1055
            return false;
1056
        }
1057
1058
        if ($input_type_part instanceof TConditional) {
1059
            $input_atomic_types = array_merge(
1060
                array_values($input_type_part->if_type->getAtomicTypes()),
1061
                array_values($input_type_part->else_type->getAtomicTypes())
1062
            );
1063
1064
            foreach ($input_atomic_types as $input_as_type_part) {
1065
                if (self::isAtomicContainedBy(
1066
                    $codebase,
1067
                    $input_as_type_part,
1068
                    $container_type_part,
1069
                    $allow_interface_equality,
1070
                    $allow_float_int_equality,
1071
                    $atomic_comparison_result
1072
                )) {
1073
                    return true;
1074
                }
1075
            }
1076
1077
            return false;
1078
        }
1079
1080
        if ($container_type_part instanceof GetClassT) {
1081
            $first_type = array_values($container_type_part->as_type->getAtomicTypes())[0];
1082
1083
            $container_type_part = new TClassString(
1084
                'object',
1085
                $first_type instanceof TNamedObject ? $first_type : null
1086
            );
1087
        }
1088
1089
        if ($input_type_part instanceof GetClassT) {
1090
            $first_type = array_values($input_type_part->as_type->getAtomicTypes())[0];
1091
1092
            if ($first_type instanceof TTemplateParam) {
1093
                $object_type = array_values($first_type->as->getAtomicTypes())[0];
1094
1095
                $input_type_part = new TTemplateParamClass(
1096
                    $first_type->param_name,
1097
                    $first_type->as->getId(),
1098
                    $object_type instanceof TNamedObject ? $object_type : null,
1099
                    $first_type->defining_class
1100
                );
1101
            } else {
1102
                $input_type_part = new TClassString(
1103
                    'object',
1104
                    $first_type instanceof TNamedObject ? $first_type : null
1105
                );
1106
            }
1107
        }
1108
1109
        if ($input_type_part instanceof GetTypeT) {
1110
            $input_type_part = new TString();
1111
1112
            if ($container_type_part instanceof TLiteralString) {
1113
                return isset(ClassLikeAnalyzer::GETTYPE_TYPES[$container_type_part->value]);
1114
            }
1115
        }
1116
1117
        if ($container_type_part instanceof GetTypeT) {
1118
            $container_type_part = new TString();
1119
1120
            if ($input_type_part instanceof TLiteralString) {
1121
                return isset(ClassLikeAnalyzer::GETTYPE_TYPES[$input_type_part->value]);
1122
            }
1123
        }
1124
1125
        if ($input_type_part->shallowEquals($container_type_part)) {
1126
            return self::isMatchingTypeContainedBy(
1127
                $codebase,
1128
                $input_type_part,
1129
                $container_type_part,
1130
                $atomic_comparison_result ?: new TypeComparisonResult(),
1131
                $allow_interface_equality
1132
            );
1133
        }
1134
1135
        if ($input_type_part instanceof TFalse
1136
            && $container_type_part instanceof TBool
1137
            && !($container_type_part instanceof TTrue)
1138
        ) {
1139
            return true;
1140
        }
1141
1142
        if ($input_type_part instanceof TTrue
1143
            && $container_type_part instanceof TBool
1144
            && !($container_type_part instanceof TFalse)
1145
        ) {
1146
            return true;
1147
        }
1148
1149
        // from https://wiki.php.net/rfc/scalar_type_hints_v5:
1150
        //
1151
        // > int types can resolve a parameter type of float
1152
        if ($input_type_part instanceof TInt
1153
            && $container_type_part instanceof TFloat
1154
            && !$container_type_part instanceof TLiteralFloat
1155
            && $allow_float_int_equality
1156
        ) {
1157
            return true;
1158
        }
1159
1160
        if ($input_type_part instanceof TNamedObject
1161
            && $input_type_part->value === 'static'
1162
            && $container_type_part instanceof TNamedObject
1163
            && strtolower($container_type_part->value) === 'self'
1164
        ) {
1165
            return true;
1166
        }
1167
1168
        if ($container_type_part instanceof TCallable && $input_type_part instanceof Type\Atomic\TFn) {
1169
            $all_types_contain = true;
1170
1171
            if (self::compareCallable(
1172
                $codebase,
1173
                $input_type_part,
1174
                $container_type_part,
1175
                $atomic_comparison_result ?: new TypeComparisonResult(),
1176
                $all_types_contain
1177
            ) === false
1178
            ) {
1179
                return false;
1180
            }
1181
1182
            if (!$all_types_contain) {
1183
                return false;
1184
            }
1185
        }
1186
1187
        if ($input_type_part instanceof TNamedObject &&
1188
            $input_type_part->value === 'Closure' &&
1189
            $container_type_part instanceof TCallable
1190
        ) {
1191
            return true;
1192
        }
1193
1194
        if ($input_type_part instanceof TObject &&
1195
            $container_type_part instanceof TCallable
1196
        ) {
1197
            return true;
1198
        }
1199
1200
        if ($input_type_part instanceof Type\Atomic\TCallableObject &&
1201
            $container_type_part instanceof TObject
1202
        ) {
1203
            return true;
1204
        }
1205
1206
        if ($container_type_part instanceof TNumeric &&
1207
            $input_type_part->isNumericType()
1208
        ) {
1209
            return true;
1210
        }
1211
1212
        if ($container_type_part instanceof TArrayKey
1213
            && $input_type_part instanceof TNumeric
1214
        ) {
1215
            return true;
1216
        }
1217
1218
        if ($container_type_part instanceof TArrayKey
1219
            && ($input_type_part instanceof TInt
1220
                || $input_type_part instanceof TString
1221
                || $input_type_part instanceof Type\Atomic\TTemplateKeyOf)
1222
        ) {
1223
            return true;
1224
        }
1225
1226
        if ($input_type_part instanceof Type\Atomic\TTemplateKeyOf) {
1227
            foreach ($input_type_part->as->getAtomicTypes() as $atomic_type) {
0 ignored issues
show
Bug introduced by
The method getAtomicTypes cannot be called on $input_type_part->as (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1228
                if ($atomic_type instanceof TArray) {
1229
                    foreach ($atomic_type->type_params[0]->getAtomicTypes() as $array_key_atomic) {
1230
                        if (!self::isAtomicContainedBy(
1231
                            $codebase,
1232
                            $array_key_atomic,
1233
                            $container_type_part,
1234
                            $allow_interface_equality,
1235
                            $allow_float_int_equality,
1236
                            $atomic_comparison_result
1237
                        )) {
1238
                            return false;
1239
                        }
1240
                    }
1241
                }
1242
            }
1243
1244
            return true;
1245
        }
1246
1247
        if ($input_type_part instanceof TArrayKey &&
1248
            ($container_type_part instanceof TInt || $container_type_part instanceof TString)
1249
        ) {
1250
            if ($atomic_comparison_result) {
1251
                $atomic_comparison_result->type_coerced = true;
1252
                $atomic_comparison_result->type_coerced_from_mixed = true;
1253
                $atomic_comparison_result->scalar_type_match_found = true;
1254
            }
1255
1256
            return false;
1257
        }
1258
1259
        if (($container_type_part instanceof ObjectLike
1260
                && $input_type_part instanceof ObjectLike)
1261
            || ($container_type_part instanceof TObjectWithProperties
1262
                && $input_type_part instanceof TObjectWithProperties)
1263
        ) {
1264
            $all_types_contain = true;
1265
1266
            foreach ($container_type_part->properties as $key => $container_property_type) {
1267
                if (!isset($input_type_part->properties[$key])) {
0 ignored issues
show
Bug introduced by
The property properties 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...
1268
                    if (!$container_property_type->possibly_undefined) {
1269
                        $all_types_contain = false;
1270
                    }
1271
1272
                    continue;
1273
                }
1274
1275
                $input_property_type = $input_type_part->properties[$key];
1276
1277
                $property_type_comparison = new TypeComparisonResult();
1278
1279
                if (!$input_property_type->isEmpty()
1280
                    && !self::isContainedBy(
1281
                        $codebase,
1282
                        $input_property_type,
1283
                        $container_property_type,
1284
                        $input_property_type->ignore_nullable_issues,
1285
                        $input_property_type->ignore_falsable_issues,
1286
                        $property_type_comparison,
1287
                        $allow_interface_equality
1288
                    )
1289
                    && !$property_type_comparison->type_coerced_from_scalar
1290
                ) {
1291
                    $inverse_property_type_comparison = new TypeComparisonResult();
1292
1293
                    if ($atomic_comparison_result) {
1294
                        if (self::isContainedBy(
1295
                            $codebase,
1296
                            $container_property_type,
1297
                            $input_property_type,
1298
                            false,
1299
                            false,
1300
                            $inverse_property_type_comparison,
1301
                            $allow_interface_equality
1302
                        )
1303
                        || $inverse_property_type_comparison->type_coerced_from_scalar
1304
                        ) {
1305
                            $atomic_comparison_result->type_coerced = true;
1306
                        }
1307
                    }
1308
1309
                    $all_types_contain = false;
1310
                }
1311
            }
1312
1313
            if ($all_types_contain) {
1314
                if ($atomic_comparison_result) {
1315
                    $atomic_comparison_result->to_string_cast = false;
1316
                }
1317
1318
                return true;
1319
            }
1320
1321
            return false;
1322
        }
1323
1324
        if ($container_type_part instanceof TIterable) {
1325
            if ($input_type_part instanceof TArray
1326
                || $input_type_part instanceof ObjectLike
1327
                || $input_type_part instanceof TList
1328
            ) {
1329
                if ($input_type_part instanceof ObjectLike) {
1330
                    $input_type_part = $input_type_part->getGenericArrayType();
1331
                } elseif ($input_type_part instanceof TList) {
1332
                    $input_type_part = new TArray([Type::getInt(), $input_type_part->type_param]);
1333
                }
1334
1335
                $all_types_contain = true;
1336
1337
                foreach ($input_type_part->type_params as $i => $input_param) {
1338
                    $container_param_offset = $i - (2 - count($container_type_part->type_params));
1339
1340
                    if ($container_param_offset === -1) {
1341
                        continue;
1342
                    }
1343
1344
                    $container_param = $container_type_part->type_params[$container_param_offset];
1345
1346
                    if ($i === 0
1347
                        && $input_param->hasMixed()
1348
                        && $container_param->hasString()
1349
                        && $container_param->hasInt()
1350
                    ) {
1351
                        continue;
1352
                    }
1353
1354
                    $array_comparison_result = new TypeComparisonResult();
1355
1356
                    if (!$input_param->isEmpty()
1357
                        && !self::isContainedBy(
1358
                            $codebase,
1359
                            $input_param,
1360
                            $container_param,
1361
                            $input_param->ignore_nullable_issues,
1362
                            $input_param->ignore_falsable_issues,
1363
                            $array_comparison_result,
1364
                            $allow_interface_equality
1365
                        )
1366
                        && !$array_comparison_result->type_coerced_from_scalar
1367
                    ) {
1368
                        if ($atomic_comparison_result && $array_comparison_result->type_coerced_from_mixed) {
1369
                            $atomic_comparison_result->type_coerced_from_mixed = true;
1370
                        }
1371
                        $all_types_contain = false;
1372
                    }
1373
                }
1374
1375
                if ($all_types_contain) {
1376
                    if ($atomic_comparison_result) {
1377
                        $atomic_comparison_result->to_string_cast = false;
1378
                    }
1379
1380
                    return true;
1381
                }
1382
1383
                return false;
1384
            }
1385
1386
            if ($input_type_part->hasTraversableInterface($codebase)) {
1387
                return true;
1388
            }
1389
        }
1390
1391
        if ($container_type_part instanceof TScalar && $input_type_part instanceof Scalar) {
1392
            return true;
1393
        }
1394
1395
        if (get_class($container_type_part) === TInt::class && $input_type_part instanceof TLiteralInt) {
1396
            return true;
1397
        }
1398
1399
        if (get_class($container_type_part) === TFloat::class && $input_type_part instanceof TLiteralFloat) {
1400
            return true;
1401
        }
1402
1403
        if ($container_type_part instanceof TNonEmptyString
1404
            && $input_type_part instanceof TLiteralString
1405
            && $input_type_part->value === ''
1406
        ) {
1407
            return false;
1408
        }
1409
1410
        if ((get_class($container_type_part) === TString::class
1411
                || get_class($container_type_part) === TNonEmptyString::class
1412
                || get_class($container_type_part) === TSingleLetter::class)
1413
            && $input_type_part instanceof TLiteralString
1414
        ) {
1415
            return true;
1416
        }
1417
1418
        if (get_class($input_type_part) === TInt::class && $container_type_part instanceof TLiteralInt) {
1419
            if ($atomic_comparison_result) {
1420
                $atomic_comparison_result->type_coerced = true;
1421
                $atomic_comparison_result->type_coerced_from_scalar = true;
1422
            }
1423
1424
            return false;
1425
        }
1426
1427
        if (get_class($input_type_part) === TFloat::class && $container_type_part instanceof TLiteralFloat) {
1428
            if ($atomic_comparison_result) {
1429
                $atomic_comparison_result->type_coerced = true;
1430
                $atomic_comparison_result->type_coerced_from_scalar = true;
1431
            }
1432
1433
            return false;
1434
        }
1435
1436
        if ((get_class($input_type_part) === TString::class
1437
                || get_class($input_type_part) === TSingleLetter::class
1438
                || get_class($input_type_part) === TNonEmptyString::class)
1439
            && $container_type_part instanceof TLiteralString
1440
        ) {
1441
            if ($atomic_comparison_result) {
1442
                $atomic_comparison_result->type_coerced = true;
1443
                $atomic_comparison_result->type_coerced_from_scalar = true;
1444
            }
1445
1446
            return false;
1447
        }
1448
1449
        if (($input_type_part instanceof Type\Atomic\TLowercaseString
1450
                || $input_type_part instanceof Type\Atomic\TNonEmptyLowercaseString)
1451
            && $container_type_part instanceof TLiteralString
1452
            && strtolower($container_type_part->value) === $container_type_part->value
1453
        ) {
1454
            if ($atomic_comparison_result
1455
                && ($container_type_part->value || $input_type_part instanceof Type\Atomic\TLowercaseString)
1456
            ) {
1457
                $atomic_comparison_result->type_coerced = true;
1458
                $atomic_comparison_result->type_coerced_from_scalar = true;
1459
            }
1460
1461
            return false;
1462
        }
1463
1464
        if (($container_type_part instanceof TClassString || $container_type_part instanceof TLiteralClassString)
1465
            && ($input_type_part instanceof TClassString || $input_type_part instanceof TLiteralClassString)
1466
        ) {
1467
            if ($container_type_part instanceof TLiteralClassString
1468
                && $input_type_part instanceof TLiteralClassString
1469
            ) {
1470
                return $container_type_part->value === $input_type_part->value;
1471
            }
1472
1473
            if ($container_type_part instanceof TTemplateParamClass
1474
                && get_class($input_type_part) === TClassString::class
1475
            ) {
1476
                if ($atomic_comparison_result) {
1477
                    $atomic_comparison_result->type_coerced = true;
1478
                }
1479
1480
                return false;
1481
            }
1482
1483
            if ($container_type_part instanceof TClassString
1484
                && $container_type_part->as === 'object'
1485
                && !$container_type_part->as_type
1486
            ) {
1487
                return true;
1488
            }
1489
1490
            if ($input_type_part instanceof TClassString
1491
                && $input_type_part->as === 'object'
1492
                && !$input_type_part->as_type
1493
            ) {
1494
                if ($atomic_comparison_result) {
1495
                    $atomic_comparison_result->type_coerced = true;
1496
                    $atomic_comparison_result->type_coerced_from_scalar = true;
1497
                }
1498
1499
                return false;
1500
            }
1501
1502
            $fake_container_object = $container_type_part instanceof TClassString
1503
                && $container_type_part->as_type
1504
                ? $container_type_part->as_type
1505
                : new TNamedObject(
1506
                    $container_type_part instanceof TClassString
1507
                        ? $container_type_part->as
1508
                        : $container_type_part->value
1509
                );
1510
1511
            $fake_input_object = $input_type_part instanceof TClassString
1512
                && $input_type_part->as_type
1513
                ? $input_type_part->as_type
1514
                : new TNamedObject(
1515
                    $input_type_part instanceof TClassString
1516
                        ? $input_type_part->as
1517
                        : $input_type_part->value
1518
                );
1519
1520
            return self::isAtomicContainedBy(
1521
                $codebase,
1522
                $fake_input_object,
1523
                $fake_container_object,
1524
                $allow_interface_equality,
1525
                $allow_float_int_equality,
1526
                $atomic_comparison_result
1527
            );
1528
        }
1529
1530
        if ($container_type_part instanceof TString && $input_type_part instanceof TTraitString) {
1531
            return true;
1532
        }
1533
1534
        if ($container_type_part instanceof TTraitString
1535
            && (get_class($input_type_part) === TString::class
1536
                || get_class($input_type_part) === TNonEmptyString::class)
1537
        ) {
1538
            if ($atomic_comparison_result) {
1539
                $atomic_comparison_result->type_coerced = true;
1540
            }
1541
1542
            return false;
1543
        }
1544
1545
        if (($input_type_part instanceof TClassString
1546
            || $input_type_part instanceof TLiteralClassString)
1547
            && (get_class($container_type_part) === TString::class
1548
                || get_class($container_type_part) === TSingleLetter::class
1549
                || get_class($container_type_part) === TNonEmptyString::class)
1550
        ) {
1551
            return true;
1552
        }
1553
1554
        if ($input_type_part instanceof TCallableString
1555
            && (get_class($container_type_part) === TString::class
1556
                || get_class($container_type_part) === TSingleLetter::class
1557
                || get_class($container_type_part) === TNonEmptyString::class)
1558
        ) {
1559
            return true;
1560
        }
1561
1562
        if ($container_type_part instanceof TString
1563
            && ($input_type_part instanceof TNumericString
1564
                || $input_type_part instanceof THtmlEscapedString)
1565
        ) {
1566
            if ($container_type_part instanceof TLiteralString) {
1567
                if (\is_numeric($container_type_part->value) && $atomic_comparison_result) {
1568
                    $atomic_comparison_result->type_coerced = true;
1569
                }
1570
1571
                return false;
1572
            }
1573
1574
            return true;
1575
        }
1576
1577
        if ($input_type_part instanceof TString
1578
            && ($container_type_part instanceof TNumericString
1579
                || $container_type_part instanceof THtmlEscapedString)
1580
        ) {
1581
            if ($input_type_part instanceof TLiteralString) {
1582
                return \is_numeric($input_type_part->value);
1583
            }
1584
            if ($atomic_comparison_result) {
1585
                $atomic_comparison_result->type_coerced = true;
1586
            }
1587
1588
            return false;
1589
        }
1590
1591
        if ($container_type_part instanceof TCallableString
1592
            && $input_type_part instanceof TLiteralString
1593
        ) {
1594
            $input_callable = self::getCallableFromAtomic($codebase, $input_type_part);
1595
            $container_callable = self::getCallableFromAtomic($codebase, $container_type_part);
1596
1597
            if ($input_callable && $container_callable) {
1598
                $all_types_contain = true;
1599
1600
                if (self::compareCallable(
1601
                    $codebase,
1602
                    $input_callable,
1603
                    $container_callable,
1604
                    $atomic_comparison_result ?: new TypeComparisonResult(),
1605
                    $all_types_contain
1606
                ) === false
1607
                ) {
1608
                    return false;
1609
                }
1610
1611
                if (!$all_types_contain) {
1612
                    return false;
1613
                }
1614
            }
1615
1616
            return true;
1617
        }
1618
1619
        if (($container_type_part instanceof TClassString
1620
                || $container_type_part instanceof TLiteralClassString
1621
                || $container_type_part instanceof TCallableString)
1622
            && $input_type_part instanceof TString
1623
        ) {
1624
            if ($atomic_comparison_result) {
1625
                $atomic_comparison_result->type_coerced = true;
1626
            }
1627
1628
            return false;
1629
        }
1630
1631
        if (($container_type_part instanceof TString || $container_type_part instanceof TScalar)
1632
            && $input_type_part instanceof TNamedObject
1633
        ) {
1634
            // check whether the object has a __toString method
1635
            if ($codebase->classOrInterfaceExists($input_type_part->value)
1636
                && $codebase->methods->methodExists(
1637
                    new \Psalm\Internal\MethodIdentifier(
1638
                        $input_type_part->value,
1639
                        '__tostring'
1640
                    )
1641
                )
1642
            ) {
1643
                if ($atomic_comparison_result) {
1644
                    $atomic_comparison_result->to_string_cast = true;
1645
                }
1646
1647
                return true;
1648
            }
1649
1650
            // PHP 5.6 doesn't support this natively, so this introduces a bug *just* when checking PHP 5.6 code
1651
            if ($input_type_part->value === 'ReflectionType') {
1652
                if ($atomic_comparison_result) {
1653
                    $atomic_comparison_result->to_string_cast = true;
1654
                }
1655
1656
                return true;
1657
            }
1658
        }
1659
1660
        if (($container_type_part instanceof TString || $container_type_part instanceof TScalar)
1661
            && $input_type_part instanceof TObjectWithProperties
1662
            && isset($input_type_part->methods['__toString'])
1663
        ) {
1664
            if ($atomic_comparison_result) {
1665
                $atomic_comparison_result->to_string_cast = true;
1666
            }
1667
1668
            return true;
1669
        }
1670
1671
        if ($container_type_part instanceof Type\Atomic\TFn && $input_type_part instanceof TCallable) {
1672
            if ($atomic_comparison_result) {
1673
                $atomic_comparison_result->type_coerced = true;
1674
            }
1675
1676
            return false;
1677
        }
1678
1679
        if ($container_type_part instanceof TCallable &&
1680
            (
1681
                $input_type_part instanceof TLiteralString
1682
                || $input_type_part instanceof TCallableString
1683
                || $input_type_part instanceof TArray
1684
                || $input_type_part instanceof ObjectLike
1685
                || $input_type_part instanceof TList
1686
                || (
1687
                    $input_type_part instanceof TNamedObject &&
1688
                    $codebase->classOrInterfaceExists($input_type_part->value) &&
1689
                    $codebase->methodExists($input_type_part->value . '::__invoke')
1690
                )
1691
            )
1692
        ) {
1693
            if ($input_type_part instanceof TList) {
1694
                if ($input_type_part->type_param->isMixed()
1695
                    || $input_type_part->type_param->hasScalar()
1696
                ) {
1697
                    if ($atomic_comparison_result) {
1698
                        $atomic_comparison_result->type_coerced_from_mixed = true;
1699
                        $atomic_comparison_result->type_coerced = true;
1700
                    }
1701
1702
                    return false;
1703
                }
1704
1705
                if (!$input_type_part->type_param->hasString()) {
1706
                    return false;
1707
                }
1708
1709
                if (!$input_type_part instanceof Type\Atomic\TCallableList) {
1710
                    if ($atomic_comparison_result) {
1711
                        $atomic_comparison_result->type_coerced_from_mixed = true;
1712
                        $atomic_comparison_result->type_coerced = true;
1713
                    }
1714
1715
                    return false;
1716
                }
1717
            }
1718
1719
            if ($input_type_part instanceof TArray) {
1720
                if ($input_type_part->type_params[1]->isMixed()
1721
                    || $input_type_part->type_params[1]->hasScalar()
1722
                ) {
1723
                    if ($atomic_comparison_result) {
1724
                        $atomic_comparison_result->type_coerced_from_mixed = true;
1725
                        $atomic_comparison_result->type_coerced = true;
1726
                    }
1727
1728
                    return false;
1729
                }
1730
1731
                if (!$input_type_part->type_params[1]->hasString()) {
1732
                    return false;
1733
                }
1734
1735
                if (!$input_type_part instanceof Type\Atomic\TCallableArray) {
1736
                    if ($atomic_comparison_result) {
1737
                        $atomic_comparison_result->type_coerced_from_mixed = true;
1738
                        $atomic_comparison_result->type_coerced = true;
1739
                    }
1740
1741
                    return false;
1742
                }
1743
            } elseif ($input_type_part instanceof ObjectLike) {
1744
                $method_id = self::getCallableMethodIdFromObjectLike($input_type_part);
1745
1746
                if ($method_id === 'not-callable') {
1747
                    return false;
1748
                }
1749
1750
                if (!$method_id) {
1751
                    return true;
1752
                }
1753
1754
                try {
1755
                    $method_id = $codebase->methods->getDeclaringMethodId($method_id);
0 ignored issues
show
Bug introduced by
It seems like $method_id defined by $codebase->methods->getD...ingMethodId($method_id) on line 1755 can also be of type string; however, Psalm\Internal\Codebase\...:getDeclaringMethodId() does only seem to accept object<Psalm\Internal\MethodIdentifier>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1756
1757
                    if (!$method_id) {
1758
                        return false;
1759
                    }
1760
1761
                    $codebase->methods->getStorage($method_id);
1762
                } catch (\Exception $e) {
1763
                    return false;
1764
                }
1765
            }
1766
1767
            $input_callable = self::getCallableFromAtomic($codebase, $input_type_part, $container_type_part);
1768
1769
            if ($input_callable) {
1770
                $all_types_contain = true;
1771
1772
                if (self::compareCallable(
1773
                    $codebase,
1774
                    $input_callable,
1775
                    $container_type_part,
1776
                    $atomic_comparison_result ?: new TypeComparisonResult(),
1777
                    $all_types_contain
1778
                ) === false
1779
                ) {
1780
                    return false;
1781
                }
1782
1783
                if (!$all_types_contain) {
1784
                    return false;
1785
                }
1786
            }
1787
1788
            return true;
1789
        }
1790
1791
        if ($input_type_part instanceof TNumeric) {
1792
            if ($container_type_part->isNumericType()) {
1793
                if ($atomic_comparison_result) {
1794
                    $atomic_comparison_result->scalar_type_match_found = true;
1795
                }
1796
            }
1797
        }
1798
1799
        if ($input_type_part instanceof Scalar) {
1800
            if ($container_type_part instanceof Scalar
1801
                && !$container_type_part instanceof TLiteralInt
1802
                && !$container_type_part instanceof TLiteralString
1803
                && !$container_type_part instanceof TLiteralFloat
1804
            ) {
1805
                if ($atomic_comparison_result) {
1806
                    $atomic_comparison_result->scalar_type_match_found = true;
1807
                }
1808
            }
1809
        } elseif ($container_type_part instanceof TObject
1810
            && $input_type_part instanceof TNamedObject
1811
        ) {
1812
            if ($container_type_part instanceof TObjectWithProperties
1813
                && $input_type_part->value !== 'stdClass'
1814
            ) {
1815
                $all_types_contain = true;
1816
1817
                foreach ($container_type_part->properties as $property_name => $container_property_type) {
1818
                    if (!is_string($property_name)) {
1819
                        continue;
1820
                    }
1821
1822
                    if (!$codebase->properties->propertyExists(
1823
                        $input_type_part . '::$' . $property_name,
1824
                        true
1825
                    )) {
1826
                        $all_types_contain = false;
1827
1828
                        continue;
1829
                    }
1830
1831
                    $property_declaring_class = (string) $codebase->properties->getDeclaringClassForProperty(
1832
                        $input_type_part . '::$' . $property_name,
1833
                        true
1834
                    );
1835
1836
                    $class_storage = $codebase->classlike_storage_provider->get($property_declaring_class);
1837
1838
                    $input_property_storage = $class_storage->properties[$property_name];
1839
1840
                    $input_property_type = $input_property_storage->type ?: Type::getMixed();
1841
1842
                    $property_type_comparison = new TypeComparisonResult();
1843
1844
                    if (!$input_property_type->isEmpty()
1845
                        && !self::isContainedBy(
1846
                            $codebase,
1847
                            $input_property_type,
1848
                            $container_property_type,
1849
                            false,
1850
                            false,
1851
                            $property_type_comparison,
1852
                            $allow_interface_equality
1853
                        )
1854
                        && !$property_type_comparison->type_coerced_from_scalar
1855
                    ) {
1856
                        $inverse_property_type_comparison = new TypeComparisonResult();
1857
1858
                        if (self::isContainedBy(
1859
                            $codebase,
1860
                            $container_property_type,
1861
                            $input_property_type,
1862
                            false,
1863
                            false,
1864
                            $inverse_property_type_comparison,
1865
                            $allow_interface_equality
1866
                        )
1867
                        || $inverse_property_type_comparison->type_coerced_from_scalar
1868
                        ) {
1869
                            if ($atomic_comparison_result) {
1870
                                $atomic_comparison_result->type_coerced = true;
1871
                            }
1872
                        }
1873
1874
                        $all_types_contain = false;
1875
                    }
1876
                }
1877
1878
                if ($all_types_contain === true) {
1879
                    return true;
1880
                }
1881
1882
                return false;
1883
            }
1884
1885
            return true;
1886
        } elseif ($atomic_comparison_result) {
1887
            if ($input_type_part instanceof TObject && $container_type_part instanceof TNamedObject) {
1888
                $atomic_comparison_result->type_coerced = true;
1889
            } elseif ($container_type_part instanceof TNamedObject
1890
                && $input_type_part instanceof TNamedObject
1891
            ) {
1892
                if ($container_type_part->was_static
1893
                    && !$input_type_part->was_static
1894
                ) {
1895
                    $atomic_comparison_result->type_coerced = true;
1896
                } elseif ($codebase->classOrInterfaceExists($input_type_part->value)
1897
                    && (
1898
                        (
1899
                            $codebase->classExists($container_type_part->value)
1900
                            && $codebase->classExtendsOrImplements(
1901
                                $container_type_part->value,
1902
                                $input_type_part->value
1903
                            )
1904
                        )
1905
                        ||
1906
                        (
1907
                            $codebase->interfaceExists($container_type_part->value)
1908
                            && $codebase->interfaceExtends(
1909
                                $container_type_part->value,
1910
                                $input_type_part->value
1911
                            )
1912
                        )
1913
                    )
1914
                ) {
1915
                    $atomic_comparison_result->type_coerced = true;
1916
                }
1917
            }
1918
        }
1919
1920
        return false;
1921
    }
1922
1923
    /**
1924
     * @return ?TCallable
0 ignored issues
show
Documentation introduced by
The doc-type ?TCallable could not be parsed: Unknown type name "?TCallable" 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...
1925
     */
1926
    public static function getCallableFromAtomic(
1927
        Codebase $codebase,
1928
        Type\Atomic $input_type_part,
1929
        ?TCallable $container_type_part = null,
1930
        ?StatementsAnalyzer $statements_analyzer = null
1931
    ) : ?TCallable {
1932
        if ($input_type_part instanceof TLiteralString && $input_type_part->value) {
1933
            try {
1934
                $function_storage = $codebase->functions->getStorage(
1935
                    $statements_analyzer,
1936
                    strtolower($input_type_part->value)
1937
                );
1938
1939
                return new TCallable(
1940
                    'callable',
1941
                    $function_storage->params,
1942
                    $function_storage->return_type,
1943
                    $function_storage->pure
1944
                );
1945
            } catch (\UnexpectedValueException $e) {
1946
                if (InternalCallMapHandler::inCallMap($input_type_part->value)) {
1947
                    $args = [];
1948
1949
                    $nodes = new \Psalm\Internal\Provider\NodeDataProvider();
1950
1951
                    if ($container_type_part && $container_type_part->params) {
1952
                        foreach ($container_type_part->params as $i => $param) {
1953
                            $arg = new \PhpParser\Node\Arg(
1954
                                new \PhpParser\Node\Expr\Variable('_' . $i)
1955
                            );
1956
1957
                            if ($param->type) {
1958
                                $nodes->setType($arg->value, $param->type);
1959
                            }
1960
1961
                            $args[] = $arg;
1962
                        }
1963
                    }
1964
1965
                    $matching_callable = \Psalm\Internal\Codebase\InternalCallMapHandler::getCallableFromCallMapById(
1966
                        $codebase,
1967
                        $input_type_part->value,
1968
                        $args,
1969
                        $nodes
1970
                    );
1971
1972
                    $must_use = false;
1973
1974
                    $matching_callable->is_pure = $codebase->functions->isCallMapFunctionPure(
1975
                        $codebase,
1976
                        $statements_analyzer ? $statements_analyzer->node_data : null,
1977
                        $input_type_part->value,
1978
                        null,
1979
                        $must_use
1980
                    );
1981
1982
                    return $matching_callable;
1983
                }
1984
            }
1985
        } elseif ($input_type_part instanceof ObjectLike) {
1986
            $method_id = self::getCallableMethodIdFromObjectLike($input_type_part);
1987
            if ($method_id && $method_id !== 'not-callable') {
1988
                try {
1989
                    $method_storage = $codebase->methods->getStorage($method_id);
0 ignored issues
show
Bug introduced by
It seems like $method_id defined by self::getCallableMethodI...tLike($input_type_part) on line 1986 can also be of type string; however, Psalm\Internal\Codebase\Methods::getStorage() does only seem to accept object<Psalm\Internal\MethodIdentifier>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1990
                    $method_fqcln = $method_id->fq_class_name;
1991
1992
                    $converted_return_type = null;
1993
1994
                    if ($method_storage->return_type) {
1995
                        $converted_return_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
1996
                            $codebase,
1997
                            $method_storage->return_type,
1998
                            $method_fqcln,
1999
                            $method_fqcln,
2000
                            null
2001
                        );
2002
                    }
2003
2004
                    return new TCallable(
2005
                        'callable',
2006
                        $method_storage->params,
2007
                        $converted_return_type
2008
                    );
2009
                } catch (\UnexpectedValueException $e) {
2010
                    // do nothing
2011
                }
2012
            }
2013
        } elseif ($input_type_part instanceof TNamedObject
2014
            && $codebase->classExists($input_type_part->value)
2015
        ) {
2016
            $invoke_id = new \Psalm\Internal\MethodIdentifier(
2017
                $input_type_part->value,
2018
                '__invoke'
2019
            );
2020
2021
            if ($codebase->methods->methodExists($invoke_id)) {
2022
                return new TCallable(
2023
                    'callable',
2024
                    $codebase->methods->getMethodParams($invoke_id),
2025
                    $codebase->methods->getMethodReturnType(
2026
                        $invoke_id,
2027
                        $input_type_part->value
2028
                    )
2029
                );
2030
            }
2031
        }
2032
2033
        return null;
2034
    }
2035
2036
    /** @return null|'not-callable'|\Psalm\Internal\MethodIdentifier */
0 ignored issues
show
Documentation introduced by
The doc-type null|'not-callable'|\Psa...ternal\MethodIdentifier could not be parsed: Unknown type name "'not-callable'" at position 5. (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...
2037
    public static function getCallableMethodIdFromObjectLike(
2038
        ObjectLike $input_type_part,
2039
        Codebase $codebase = null,
2040
        string $calling_method_id = null,
2041
        string $file_name = null
2042
    ) {
2043
        if (!isset($input_type_part->properties[0])
2044
            || !isset($input_type_part->properties[1])
2045
        ) {
2046
            return 'not-callable';
2047
        }
2048
2049
        $lhs = $input_type_part->properties[0];
2050
        $rhs = $input_type_part->properties[1];
2051
2052
        $rhs_low_info = $rhs->hasMixed() || $rhs->hasScalar();
2053
2054
        if ($rhs_low_info || !$rhs->isSingleStringLiteral()) {
2055
            if (!$rhs_low_info && !$rhs->hasString()) {
2056
                return 'not-callable';
2057
            }
2058
2059
            if ($codebase && ($calling_method_id || $file_name)) {
2060
                foreach ($lhs->getAtomicTypes() as $lhs_atomic_type) {
2061
                    if ($lhs_atomic_type instanceof TNamedObject) {
2062
                        $codebase->analyzer->addMixedMemberName(
2063
                            strtolower($lhs_atomic_type->value) . '::',
2064
                            $calling_method_id ?: $file_name
2065
                        );
2066
                    }
2067
                }
2068
            }
2069
2070
            return null;
2071
        }
2072
2073
        $method_name = $rhs->getSingleStringLiteral()->value;
2074
2075
        $class_name = null;
2076
2077
        if ($lhs->isSingleStringLiteral()) {
2078
            $class_name = $lhs->getSingleStringLiteral()->value;
2079
            if ($class_name[0] === '\\') {
2080
                $class_name = \substr($class_name, 1);
2081
            }
2082
        } elseif ($lhs->isSingle()) {
2083
            foreach ($lhs->getAtomicTypes() as $lhs_atomic_type) {
2084
                if ($lhs_atomic_type instanceof TNamedObject) {
2085
                    $class_name = $lhs_atomic_type->value;
2086
                }
2087
            }
2088
        }
2089
2090
        if ($class_name === 'self'
2091
            || $class_name === 'static'
2092
            || $class_name === 'parent'
2093
        ) {
2094
            return null;
2095
        }
2096
2097
        if (!$class_name) {
2098
            if ($codebase && ($calling_method_id || $file_name)) {
2099
                $codebase->analyzer->addMixedMemberName(
2100
                    strtolower($method_name),
2101
                    $calling_method_id ?: $file_name
2102
                );
2103
            }
2104
2105
            return null;
2106
        }
2107
2108
        return new \Psalm\Internal\MethodIdentifier(
2109
            $class_name,
2110
            strtolower($method_name)
2111
        );
2112
    }
2113
2114
    private static function isMatchingTypeContainedBy(
2115
        Codebase $codebase,
2116
        Type\Atomic $input_type_part,
2117
        Type\Atomic $container_type_part,
2118
        TypeComparisonResult $atomic_comparison_result,
2119
        bool $allow_interface_equality
2120
    ) : bool {
2121
        $all_types_contain = true;
2122
2123
        if ($container_type_part instanceof TIterable
2124
            && !$container_type_part->extra_types
2125
            && !$input_type_part instanceof TIterable
2126
        ) {
2127
            $container_type_part = new TGenericObject(
2128
                'Traversable',
2129
                $container_type_part->type_params
2130
            );
2131
        }
2132
2133
        if ($container_type_part instanceof TGenericObject || $container_type_part instanceof TIterable) {
2134
            if (!$input_type_part instanceof TGenericObject && !$input_type_part instanceof TIterable) {
2135
                if ($input_type_part instanceof TNamedObject
2136
                    && $codebase->classExists($input_type_part->value)
2137
                ) {
2138
                    $class_storage = $codebase->classlike_storage_provider->get($input_type_part->value);
2139
2140
                    $container_class = $container_type_part->value;
2141
2142
                    // attempt to transform it
2143
                    if (isset($class_storage->template_type_extends[$container_class])) {
2144
                        $extends_list = $class_storage->template_type_extends[$container_class];
2145
2146
                        $generic_params = [];
2147
2148
                        foreach ($extends_list as $key => $value) {
2149
                            if (is_string($key)) {
2150
                                $generic_params[] = $value;
2151
                            }
2152
                        }
2153
2154
                        if (!$generic_params) {
2155
                            return false;
2156
                        }
2157
2158
                        $input_type_part = new TGenericObject(
2159
                            $input_type_part->value,
2160
                            $generic_params
2161
                        );
2162
                    }
2163
                }
2164
2165
                if (!$input_type_part instanceof TGenericObject) {
2166
                    if ($input_type_part instanceof TNamedObject) {
2167
                        $input_type_part = new TGenericObject(
2168
                            $input_type_part->value,
2169
                            array_fill(0, count($container_type_part->type_params), Type::getMixed())
2170
                        );
2171
                    } else {
2172
                        $atomic_comparison_result->type_coerced = true;
2173
                        $atomic_comparison_result->type_coerced_from_mixed = true;
2174
2175
                        return false;
2176
                    }
2177
                }
2178
            }
2179
2180
            $container_type_params_covariant = [];
2181
2182
            $input_type_params = \Psalm\Internal\Type\UnionTemplateHandler::getMappedGenericTypeParams(
2183
                $codebase,
2184
                $input_type_part,
2185
                $container_type_part,
2186
                $container_type_params_covariant
2187
            );
2188
2189
            foreach ($input_type_params as $i => $input_param) {
2190
                if (!isset($container_type_part->type_params[$i])) {
2191
                    break;
2192
                }
2193
2194
                $container_param = $container_type_part->type_params[$i];
2195
2196
                if ($input_param->isEmpty()) {
2197
                    if (!$atomic_comparison_result->replacement_atomic_type) {
2198
                        $atomic_comparison_result->replacement_atomic_type = clone $input_type_part;
2199
                    }
2200
2201
                    if ($atomic_comparison_result->replacement_atomic_type instanceof TGenericObject) {
2202
                        /** @psalm-suppress PropertyTypeCoercion */
2203
                        $atomic_comparison_result->replacement_atomic_type->type_params[$i]
2204
                            = clone $container_param;
2205
                    }
2206
2207
                    continue;
2208
                }
2209
2210
                $param_comparison_result = new TypeComparisonResult();
2211
2212
                if (!self::isContainedBy(
2213
                    $codebase,
2214
                    $input_param,
2215
                    $container_param,
2216
                    $input_param->ignore_nullable_issues,
2217
                    $input_param->ignore_falsable_issues,
2218
                    $param_comparison_result,
2219
                    $allow_interface_equality
2220
                )) {
2221
                    if ($input_type_part->value === 'Generator'
2222
                        && $i === 2
2223
                        && $param_comparison_result->type_coerced_from_mixed
2224
                    ) {
2225
                        continue;
2226
                    }
2227
2228
                    $atomic_comparison_result->type_coerced
2229
                        = $param_comparison_result->type_coerced === true
2230
                            && $atomic_comparison_result->type_coerced !== false;
2231
2232
                    $atomic_comparison_result->type_coerced_from_mixed
2233
                        = $param_comparison_result->type_coerced_from_mixed === true
2234
                            && $atomic_comparison_result->type_coerced_from_mixed !== false;
2235
2236
                    $atomic_comparison_result->type_coerced_from_as_mixed
2237
                        = $param_comparison_result->type_coerced_from_as_mixed === true
2238
                            && $atomic_comparison_result->type_coerced_from_as_mixed !== false;
2239
2240
                    $atomic_comparison_result->to_string_cast
2241
                        = $param_comparison_result->to_string_cast === true
2242
                            && $atomic_comparison_result->to_string_cast !== false;
2243
2244
                    $atomic_comparison_result->type_coerced_from_scalar
2245
                        = $param_comparison_result->type_coerced_from_scalar === true
2246
                            && $atomic_comparison_result->type_coerced_from_scalar !== false;
2247
2248
                    $atomic_comparison_result->scalar_type_match_found
2249
                        = $param_comparison_result->scalar_type_match_found === true
2250
                            && $atomic_comparison_result->scalar_type_match_found !== false;
2251
2252
                    if (!$param_comparison_result->type_coerced_from_as_mixed) {
2253
                        $all_types_contain = false;
2254
                    }
2255
                } elseif (!$input_type_part instanceof TIterable
2256
                    && !$container_type_part instanceof TIterable
2257
                    && !$container_param->hasTemplate()
2258
                    && !$input_param->hasTemplate()
2259
                ) {
2260
                    if ($input_param->hasEmptyArray()
2261
                        || $input_param->hasLiteralValue()
2262
                    ) {
2263
                        if (!$atomic_comparison_result->replacement_atomic_type) {
2264
                            $atomic_comparison_result->replacement_atomic_type = clone $input_type_part;
2265
                        }
2266
2267
                        if ($atomic_comparison_result->replacement_atomic_type instanceof TGenericObject) {
2268
                            /** @psalm-suppress PropertyTypeCoercion */
2269
                            $atomic_comparison_result->replacement_atomic_type->type_params[$i]
2270
                                = clone $container_param;
2271
                        }
2272
                    } else {
2273
                        if (!($container_type_params_covariant[$i] ?? false)
2274
                            && !$container_param->had_template
2275
                        ) {
2276
                            // Make sure types are basically the same
2277
                            if (!self::isContainedBy(
2278
                                $codebase,
2279
                                $container_param,
2280
                                $input_param,
2281
                                $container_param->ignore_nullable_issues,
2282
                                $container_param->ignore_falsable_issues,
2283
                                $param_comparison_result,
2284
                                $allow_interface_equality
2285
                            ) || $param_comparison_result->type_coerced
2286
                            ) {
2287
                                if ($container_param->hasFormerStaticObject()
2288
                                    && $input_param->isFormerStaticObject()
2289
                                    && self::isContainedBy(
2290
                                        $codebase,
2291
                                        $input_param,
2292
                                        $container_param,
2293
                                        $container_param->ignore_nullable_issues,
2294
                                        $container_param->ignore_falsable_issues,
2295
                                        $param_comparison_result,
2296
                                        $allow_interface_equality
2297
                                    )
2298
                                ) {
2299
                                    // do nothing
2300
                                } else {
2301
                                    if ($container_param->hasMixed() || $container_param->isArrayKey()) {
2302
                                        $atomic_comparison_result->type_coerced_from_mixed = true;
2303
                                    } else {
2304
                                        $all_types_contain = false;
2305
                                    }
2306
2307
                                    $atomic_comparison_result->type_coerced = false;
2308
                                }
2309
                            }
2310
                        }
2311
                    }
2312
                }
2313
            }
2314
        }
2315
2316
        if ($container_type_part instanceof Type\Atomic\TFn) {
2317
            if (!$input_type_part instanceof Type\Atomic\TFn) {
2318
                $atomic_comparison_result->type_coerced = true;
2319
                $atomic_comparison_result->type_coerced_from_mixed = true;
2320
2321
                return false;
2322
            }
2323
2324
            if (self::compareCallable(
2325
                $codebase,
2326
                $input_type_part,
2327
                $container_type_part,
2328
                $atomic_comparison_result,
2329
                $all_types_contain
2330
            ) === false
2331
            ) {
2332
                return false;
2333
            }
2334
        }
2335
2336
        if ($container_type_part instanceof Type\Atomic\TCallable
2337
            && $input_type_part instanceof Type\Atomic\TCallable
2338
        ) {
2339
            if (self::compareCallable(
2340
                $codebase,
2341
                $input_type_part,
2342
                $container_type_part,
2343
                $atomic_comparison_result,
2344
                $all_types_contain
2345
            ) === false
2346
            ) {
2347
                return false;
2348
            }
2349
        }
2350
2351
        if ($container_type_part instanceof TList
2352
            && $input_type_part instanceof ObjectLike
2353
        ) {
2354
            if ($input_type_part->is_list) {
2355
                $input_type_part = $input_type_part->getList();
2356
            } else {
2357
                return false;
2358
            }
2359
        }
2360
2361
        if ($container_type_part instanceof TList
2362
            && $input_type_part instanceof TClassStringMap
2363
        ) {
2364
            return false;
2365
        }
2366
2367
        if ($container_type_part instanceof TList
2368
            && $input_type_part instanceof TArray
2369
            && $input_type_part->type_params[1]->isEmpty()
2370
        ) {
2371
            return !$container_type_part instanceof TNonEmptyList;
2372
        }
2373
2374
        if ($input_type_part instanceof TList
2375
            && $container_type_part instanceof TList
2376
        ) {
2377
            if (!self::isContainedBy(
2378
                $codebase,
2379
                $input_type_part->type_param,
2380
                $container_type_part->type_param,
2381
                $input_type_part->type_param->ignore_nullable_issues,
2382
                $input_type_part->type_param->ignore_falsable_issues,
2383
                $atomic_comparison_result,
2384
                $allow_interface_equality
2385
            )) {
2386
                return false;
2387
            }
2388
2389
            return $input_type_part instanceof TNonEmptyList
2390
                || !$container_type_part instanceof TNonEmptyList;
2391
        }
2392
2393
        $prior_input_type_part = $input_type_part;
2394
2395
        if (($input_type_part instanceof TArray
2396
                || $input_type_part instanceof ObjectLike
2397
                || $input_type_part instanceof TList
2398
                || $input_type_part instanceof TClassStringMap)
2399
            && ($container_type_part instanceof TArray
2400
                || $container_type_part instanceof ObjectLike
2401
                || $container_type_part instanceof TList
2402
                || $container_type_part instanceof TClassStringMap)
2403
        ) {
2404
            if ($container_type_part instanceof ObjectLike) {
2405
                $generic_container_type_part = $container_type_part->getGenericArrayType();
2406
2407
                $container_params_can_be_undefined = (bool) array_reduce(
2408
                    $container_type_part->properties,
2409
                    /**
2410
                     * @param bool $carry
2411
                     *
2412
                     * @return bool
2413
                     */
2414
                    function ($carry, Type\Union $item) {
2415
                        return $carry || $item->possibly_undefined;
2416
                    },
2417
                    false
2418
                );
2419
2420
                if ($input_type_part instanceof TArray
2421
                    && !$input_type_part->type_params[0]->hasMixed()
2422
                    && !($input_type_part->type_params[1]->isEmpty()
2423
                        && $container_params_can_be_undefined)
2424
                ) {
2425
                    $all_types_contain = false;
2426
                    $atomic_comparison_result->type_coerced = true;
2427
                }
2428
2429
                $container_type_part = $generic_container_type_part;
2430
            }
2431
2432
            if ($input_type_part instanceof ObjectLike) {
2433
                $input_type_part = $input_type_part->getGenericArrayType();
2434
            }
2435
2436
            if ($input_type_part instanceof TClassStringMap) {
2437
                $input_type_part = new TArray([
2438
                    $input_type_part->getStandinKeyParam(),
2439
                    clone $input_type_part->value_param
2440
                ]);
2441
            }
2442
2443
            if ($container_type_part instanceof TClassStringMap) {
2444
                $container_type_part = new TArray([
2445
                    $container_type_part->getStandinKeyParam(),
2446
                    clone $container_type_part->value_param
2447
                ]);
2448
            }
2449
2450
            if ($container_type_part instanceof TList) {
2451
                $all_types_contain = false;
2452
                $atomic_comparison_result->type_coerced = true;
2453
2454
                $container_type_part = new TArray([Type::getInt(), clone $container_type_part->type_param]);
2455
            }
2456
2457
            if ($input_type_part instanceof TList) {
2458
                if ($input_type_part instanceof TNonEmptyList) {
2459
                    $input_type_part = new TNonEmptyArray([Type::getInt(), clone $input_type_part->type_param]);
2460
                } else {
2461
                    $input_type_part = new TArray([Type::getInt(), clone $input_type_part->type_param]);
2462
                }
2463
            }
2464
2465
            foreach ($input_type_part->type_params as $i => $input_param) {
2466
                if ($i > 1) {
2467
                    break;
2468
                }
2469
2470
                $container_param = $container_type_part->type_params[$i];
2471
2472
                if ($i === 0
2473
                    && $input_param->hasMixed()
2474
                    && $container_param->hasString()
2475
                    && $container_param->hasInt()
2476
                ) {
2477
                    continue;
2478
                }
2479
2480
                if ($input_param->isEmpty()
2481
                    && $container_type_part instanceof Type\Atomic\TNonEmptyArray
2482
                ) {
2483
                    return false;
2484
                }
2485
2486
                $param_comparison_result = new TypeComparisonResult();
2487
2488
                if (!$input_param->isEmpty() &&
2489
                    !self::isContainedBy(
2490
                        $codebase,
2491
                        $input_param,
2492
                        $container_param,
2493
                        $input_param->ignore_nullable_issues,
2494
                        $input_param->ignore_falsable_issues,
2495
                        $param_comparison_result,
2496
                        $allow_interface_equality
2497
                    )
2498
                ) {
2499
                    $atomic_comparison_result->type_coerced
2500
                        = $param_comparison_result->type_coerced === true
2501
                            && $atomic_comparison_result->type_coerced !== false;
2502
2503
                    $atomic_comparison_result->type_coerced_from_mixed
2504
                        = $param_comparison_result->type_coerced_from_mixed === true
2505
                            && $atomic_comparison_result->type_coerced_from_mixed !== false;
2506
2507
                    $atomic_comparison_result->type_coerced_from_as_mixed
2508
                        = $param_comparison_result->type_coerced_from_as_mixed === true
2509
                            && $atomic_comparison_result->type_coerced_from_as_mixed !== false;
2510
2511
                    $atomic_comparison_result->to_string_cast
2512
                        = $param_comparison_result->to_string_cast === true
2513
                            && $atomic_comparison_result->to_string_cast !== false;
2514
2515
                    $atomic_comparison_result->type_coerced_from_scalar
2516
                        = $param_comparison_result->type_coerced_from_scalar === true
2517
                            && $atomic_comparison_result->type_coerced_from_scalar !== false;
2518
2519
                    $atomic_comparison_result->scalar_type_match_found
2520
                        = $param_comparison_result->scalar_type_match_found === true
2521
                            && $atomic_comparison_result->scalar_type_match_found !== false;
2522
2523
                    if (!$param_comparison_result->type_coerced_from_as_mixed) {
2524
                        $all_types_contain = false;
2525
                    }
2526
                }
2527
            }
2528
        }
2529
2530
        if ($container_type_part instanceof TNamedObject
2531
            && $input_type_part instanceof TNamedObject
2532
            && $container_type_part->was_static
2533
            && !$input_type_part->was_static
2534
        ) {
2535
            $all_types_contain = false;
2536
            $atomic_comparison_result->type_coerced = true;
2537
        }
2538
2539
        if ($container_type_part instanceof Type\Atomic\TNonEmptyArray
2540
            && !$input_type_part instanceof Type\Atomic\TNonEmptyArray
2541
            && !($prior_input_type_part instanceof ObjectLike
2542
                && ($prior_input_type_part->sealed
2543
                    || $prior_input_type_part->previous_value_type
2544
                    || \array_filter(
2545
                        $prior_input_type_part->properties,
2546
                        function ($prop_type) {
2547
                            return !$prop_type->possibly_undefined;
2548
                        }
2549
                    )
2550
                )
2551
            )
2552
        ) {
2553
            if ($all_types_contain) {
2554
                $atomic_comparison_result->type_coerced = true;
2555
            }
2556
2557
            return false;
2558
        }
2559
2560
        if ($all_types_contain) {
2561
            $atomic_comparison_result->to_string_cast = false;
2562
2563
            return true;
2564
        }
2565
2566
        return false;
2567
    }
2568
2569
    /**
2570
     * @param  TCallable|Type\Atomic\TFn   $input_type_part
2571
     * @param  TCallable|Type\Atomic\TFn   $container_type_part
2572
     * @param  bool   &$all_types_contain
2573
     *
2574
     * @return null|false
2575
     */
2576
    private static function compareCallable(
2577
        Codebase $codebase,
2578
        $input_type_part,
2579
        $container_type_part,
2580
        TypeComparisonResult $atomic_comparison_result,
2581
        bool &$all_types_contain
2582
    ) {
2583
        if ($container_type_part->params !== null && $input_type_part->params === null) {
2584
            $atomic_comparison_result->type_coerced = true;
2585
            $atomic_comparison_result->type_coerced_from_mixed = true;
2586
2587
            return false;
2588
        }
2589
2590
        if ($input_type_part->params !== null && $container_type_part->params !== null) {
2591
            foreach ($input_type_part->params as $i => $input_param) {
2592
                $container_param = null;
2593
2594
                if (isset($container_type_part->params[$i])) {
2595
                    $container_param = $container_type_part->params[$i];
2596
                } elseif ($container_type_part->params) {
2597
                    $last_param = end($container_type_part->params);
2598
2599
                    if ($last_param->is_variadic) {
2600
                        $container_param = $last_param;
2601
                    }
2602
                }
2603
2604
                if (!$container_param) {
2605
                    if ($input_param->is_optional) {
2606
                        break;
2607
                    }
2608
2609
                    return false;
2610
                }
2611
2612
                if ($container_param->type
2613
                    && !$container_param->type->hasMixed()
2614
                    && !self::isContainedBy(
2615
                        $codebase,
2616
                        $container_param->type,
2617
                        $input_param->type ?: Type::getMixed(),
2618
                        false,
2619
                        false,
2620
                        $atomic_comparison_result
2621
                    )
2622
                ) {
2623
                    $all_types_contain = false;
2624
                }
2625
            }
2626
        }
2627
2628
        if (isset($container_type_part->return_type)) {
2629
            if (!isset($input_type_part->return_type)) {
2630
                $atomic_comparison_result->type_coerced = true;
2631
                $atomic_comparison_result->type_coerced_from_mixed = true;
2632
2633
                $all_types_contain = false;
2634
            } else {
2635
                $input_return = $input_type_part->return_type;
2636
2637
                if ($input_return->isVoid() && $container_type_part->return_type->isNullable()) {
2638
                    return;
2639
                }
2640
2641
                if (!$container_type_part->return_type->isVoid()
2642
                    && !self::isContainedBy(
2643
                        $codebase,
2644
                        $input_return,
2645
                        $container_type_part->return_type,
2646
                        false,
2647
                        false,
2648
                        $atomic_comparison_result
2649
                    )
2650
                ) {
2651
                    $all_types_contain = false;
2652
                }
2653
            }
2654
        }
2655
    }
2656
2657
    /**
2658
     * Takes two arrays of types and merges them
2659
     *
2660
     * @param  array<string, Type\Union>  $new_types
2661
     * @param  array<string, Type\Union>  $existing_types
2662
     *
2663
     * @return array<string, Type\Union>
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...
2664
     */
2665
    public static function combineKeyedTypes(array $new_types, array $existing_types)
2666
    {
2667
        $keys = array_merge(array_keys($new_types), array_keys($existing_types));
2668
        $keys = array_unique($keys);
2669
2670
        $result_types = [];
2671
2672
        if (empty($new_types)) {
2673
            return $existing_types;
2674
        }
2675
2676
        if (empty($existing_types)) {
2677
            return $new_types;
2678
        }
2679
2680
        foreach ($keys as $key) {
2681
            if (!isset($existing_types[$key])) {
2682
                $result_types[$key] = $new_types[$key];
2683
                continue;
2684
            }
2685
2686
            if (!isset($new_types[$key])) {
2687
                $result_types[$key] = $existing_types[$key];
2688
                continue;
2689
            }
2690
2691
            $existing_var_types = $existing_types[$key];
2692
            $new_var_types = $new_types[$key];
2693
2694
            if ($new_var_types->getId() === $existing_var_types->getId()) {
2695
                $result_types[$key] = $new_var_types;
2696
            } else {
2697
                $result_types[$key] = Type::combineUnionTypes($new_var_types, $existing_var_types);
2698
            }
2699
        }
2700
2701
        return $result_types;
2702
    }
2703
2704
    /**
2705
     * @return Type\Union
2706
     */
2707
    public static function simplifyUnionType(Codebase $codebase, Type\Union $union)
2708
    {
2709
        $union_type_count = count($union->getAtomicTypes());
2710
2711
        if ($union_type_count === 1 || ($union_type_count === 2 && $union->isNullable())) {
2712
            return $union;
2713
        }
2714
2715
        $from_docblock = $union->from_docblock;
2716
        $ignore_nullable_issues = $union->ignore_nullable_issues;
2717
        $ignore_falsable_issues = $union->ignore_falsable_issues;
2718
        $possibly_undefined = $union->possibly_undefined;
2719
2720
        $unique_types = [];
2721
2722
        $inverse_contains = [];
2723
2724
        foreach ($union->getAtomicTypes() as $type_part) {
2725
            $is_contained_by_other = false;
2726
2727
            // don't try to simplify intersection types
2728
            if (($type_part instanceof TNamedObject
2729
                    || $type_part instanceof TTemplateParam
2730
                    || $type_part instanceof TIterable)
2731
                && $type_part->extra_types
2732
            ) {
2733
                return $union;
2734
            }
2735
2736
            foreach ($union->getAtomicTypes() as $container_type_part) {
2737
                $string_container_part = $container_type_part->getId();
2738
                $string_input_part = $type_part->getId();
2739
2740
                $atomic_comparison_result = new TypeComparisonResult();
2741
2742
                if ($type_part !== $container_type_part &&
2743
                    !(
2744
                        $container_type_part instanceof TInt
2745
                        || $container_type_part instanceof TFloat
2746
                        || $container_type_part instanceof TCallable
2747
                        || ($container_type_part instanceof TString && $type_part instanceof TCallable)
2748
                        || ($container_type_part instanceof TArray && $type_part instanceof TCallable)
2749
                    ) &&
2750
                    !isset($inverse_contains[$string_input_part][$string_container_part]) &&
2751
                    TypeAnalyzer::isAtomicContainedBy(
2752
                        $codebase,
2753
                        $type_part,
2754
                        $container_type_part,
2755
                        false,
2756
                        false,
2757
                        $atomic_comparison_result
2758
                    ) &&
2759
                    !$atomic_comparison_result->to_string_cast
2760
                ) {
2761
                    $inverse_contains[$string_container_part][$string_input_part] = true;
2762
2763
                    $is_contained_by_other = true;
2764
                    break;
2765
                }
2766
            }
2767
2768
            if (!$is_contained_by_other) {
2769
                $unique_types[] = $type_part;
2770
            }
2771
        }
2772
2773
        if (!$unique_types) {
2774
            throw new \UnexpectedValueException('There must be more than one unique type');
2775
        }
2776
2777
        $unique_type = new Type\Union($unique_types);
2778
2779
        $unique_type->from_docblock = $from_docblock;
2780
        $unique_type->ignore_nullable_issues = $ignore_nullable_issues;
2781
        $unique_type->ignore_falsable_issues = $ignore_falsable_issues;
2782
        $unique_type->possibly_undefined = $possibly_undefined;
2783
2784
        return $unique_type;
2785
    }
2786
}
2787