findMatchingAtomicTypesForTemplate()   F
last analyzed

Complexity

Conditions 37
Paths 3419

Size

Total Lines 159

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 37
nc 3419
nop 5
dl 0
loc 159
rs 0
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
3
namespace Psalm\Internal\Type;
4
5
use Psalm\Codebase;
6
use Psalm\Internal\Analyzer\StatementsAnalyzer;
7
use Psalm\Internal\Analyzer\TypeAnalyzer;
8
use Psalm\Type\Union;
9
use Psalm\Type\Atomic;
10
use function array_merge;
11
use function array_values;
12
use function count;
13
use function is_string;
14
use function strpos;
15
use function substr;
16
17
class UnionTemplateHandler
18
{
19
    public static function replaceTemplateTypesWithStandins(
20
        Union $union_type,
21
        TemplateResult $template_result,
22
        ?Codebase $codebase,
23
        ?StatementsAnalyzer $statements_analyzer,
24
        ?Union $input_type,
25
        ?int $input_arg_offset = null,
26
        ?string $calling_class = null,
27
        ?string $calling_function = null,
28
        bool $replace = true,
29
        bool $add_upper_bound = false,
30
        int $depth = 0
31
    ) : Union {
32
        $atomic_types = [];
33
34
        $original_atomic_types = $union_type->getAtomicTypes();
35
36
        $had_template = false;
37
38
        foreach ($original_atomic_types as $key => $atomic_type) {
39
            $atomic_types = array_merge(
40
                $atomic_types,
41
                self::handleAtomicStandin(
42
                    $atomic_type,
43
                    $key,
44
                    $template_result,
45
                    $codebase,
46
                    $statements_analyzer,
47
                    $input_type,
48
                    $input_arg_offset,
49
                    $calling_class,
50
                    $calling_function,
51
                    $replace,
52
                    $add_upper_bound,
53
                    $depth,
54
                    count($original_atomic_types) === 1,
55
                    $union_type->isNullable(),
56
                    $had_template
57
                )
58
            );
59
        }
60
61
        if ($replace) {
62
            if (array_values($original_atomic_types) === $atomic_types) {
63
                return $union_type;
64
            }
65
66
            if (!$atomic_types) {
67
                throw new \UnexpectedValueException('Cannot remove all keys');
68
            }
69
70
            $new_union_type = new Union($atomic_types);
71
            $new_union_type->ignore_nullable_issues = $union_type->ignore_nullable_issues;
72
            $new_union_type->ignore_falsable_issues = $union_type->ignore_falsable_issues;
73
            $new_union_type->possibly_undefined = $union_type->possibly_undefined;
74
75
            if ($had_template) {
76
                $new_union_type->had_template = true;
77
            }
78
79
            return $new_union_type;
80
        }
81
82
        return $union_type;
83
    }
84
85
    /**
86
     * @return list<Atomic>
0 ignored issues
show
Documentation introduced by
The doc-type list<Atomic> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (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...
87
     */
88
    private static function handleAtomicStandin(
89
        Atomic $atomic_type,
90
        string $key,
91
        TemplateResult $template_result,
92
        ?Codebase $codebase,
93
        ?StatementsAnalyzer $statements_analyzer,
94
        ?Union $input_type,
95
        ?int $input_arg_offset,
96
        ?string $calling_class,
97
        ?string $calling_function,
98
        bool $replace,
99
        bool $add_upper_bound,
100
        int $depth,
101
        bool $was_single,
102
        bool $was_nullable,
103
        bool &$had_template
104
    ) : array {
105
        if ($bracket_pos = strpos($key, '<')) {
106
            $key = substr($key, 0, $bracket_pos);
107
        }
108
109
        if ($atomic_type instanceof Atomic\TTemplateParam
110
            && ($param_name_key = strpos($key, '&') ? $key : $atomic_type->param_name)
111
            && isset($template_result->template_types[$param_name_key][$atomic_type->defining_class])
112
        ) {
113
            $a = self::handleTemplateParamStandin(
114
                $atomic_type,
115
                $key,
116
                $input_type,
117
                $input_arg_offset,
118
                $calling_class,
119
                $calling_function,
120
                $template_result,
121
                $codebase,
122
                $statements_analyzer,
123
                $replace,
124
                $add_upper_bound,
125
                $depth,
126
                $was_nullable,
127
                $had_template
128
            );
129
130
            return $a;
131
        }
132
133
        if ($atomic_type instanceof Atomic\TTemplateParamClass
134
            && isset($template_result->template_types[$atomic_type->param_name])
135
        ) {
136
            if ($replace) {
137
                return self::handleTemplateParamClassStandin(
138
                    $atomic_type,
139
                    $input_type,
140
                    $input_arg_offset,
141
                    $template_result,
142
                    $depth,
143
                    $was_single
144
                );
145
            }
146
        }
147
148
        if ($atomic_type instanceof Atomic\TTemplateIndexedAccess) {
149
            if ($replace) {
150
                $atomic_types = [];
151
152
                $include_first = true;
153
154
                if (isset($template_result->template_types[$atomic_type->array_param_name][$atomic_type->defining_class])
155
                    && !empty($template_result->upper_bounds[$atomic_type->offset_param_name])
156
                ) {
157
                    $array_template_type
158
                        = $template_result->template_types[$atomic_type->array_param_name][$atomic_type->defining_class][0];
159
                    $offset_template_type
160
                        = array_values(
161
                            $template_result->upper_bounds[$atomic_type->offset_param_name]
162
                        )[0][0];
163
164
                    if ($array_template_type->isSingle()
165
                        && $offset_template_type->isSingle()
166
                        && !$array_template_type->isMixed()
167
                        && !$offset_template_type->isMixed()
168
                    ) {
169
                        $array_template_type = array_values($array_template_type->getAtomicTypes())[0];
170
                        $offset_template_type = array_values($offset_template_type->getAtomicTypes())[0];
171
172
                        if ($array_template_type instanceof Atomic\ObjectLike
173
                            && ($offset_template_type instanceof Atomic\TLiteralString
174
                                || $offset_template_type instanceof Atomic\TLiteralInt)
175
                            && isset($array_template_type->properties[$offset_template_type->value])
176
                        ) {
177
                            $include_first = false;
178
179
                            $replacement_type
180
                                = clone $array_template_type->properties[$offset_template_type->value];
181
182
                            foreach ($replacement_type->getAtomicTypes() as $replacement_atomic_type) {
183
                                $atomic_types[] = $replacement_atomic_type;
184
                            }
185
                        }
186
                    }
187
                }
188
189
                if ($include_first) {
190
                    $atomic_types[] = $atomic_type;
191
                }
192
193
                return $atomic_types;
194
            }
195
196
            return [$atomic_type];
197
        }
198
199
        if ($atomic_type instanceof Atomic\TTemplateKeyOf) {
200
            if ($replace) {
201
                $atomic_types = [];
202
203
                $include_first = true;
204
205
                if (isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class])) {
206
                    $template_type
207
                        = $template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class][0];
208
209
                    if ($template_type->isSingle()) {
210
                        $template_type = array_values($template_type->getAtomicTypes())[0];
211
212
                        if ($template_type instanceof Atomic\ObjectLike
213
                            || $template_type instanceof Atomic\TArray
214
                            || $template_type instanceof Atomic\TList
215
                        ) {
216
                            if ($template_type instanceof Atomic\ObjectLike) {
217
                                $key_type = $template_type->getGenericKeyType();
218
                            } elseif ($template_type instanceof Atomic\TList) {
219
                                $key_type = \Psalm\Type::getInt();
220
                            } else {
221
                                $key_type = clone $template_type->type_params[0];
222
                            }
223
224
                            $include_first = false;
225
226
                            foreach ($key_type->getAtomicTypes() as $key_atomic_type) {
227
                                $atomic_types[] = $key_atomic_type;
228
                            }
229
                        }
230
                    }
231
                }
232
233
                if ($include_first) {
234
                    $atomic_types[] = $atomic_type;
235
                }
236
237
                return $atomic_types;
238
            }
239
240
            return [$atomic_type];
241
        }
242
243
        $matching_atomic_types = [];
244
245
        if ($input_type && $codebase && !$input_type->hasMixed()) {
246
            $matching_atomic_types = self::findMatchingAtomicTypesForTemplate(
247
                $input_type,
248
                $atomic_type,
249
                $key,
250
                $codebase,
251
                $statements_analyzer
252
            );
253
        }
254
255
        if (!$matching_atomic_types) {
256
            $atomic_type = $atomic_type->replaceTemplateTypesWithStandins(
257
                $template_result,
258
                $codebase,
259
                $statements_analyzer,
260
                null,
261
                $input_arg_offset,
262
                $calling_class,
263
                $calling_function,
264
                $replace,
265
                $add_upper_bound,
266
                $depth + 1
267
            );
268
269
            return [$atomic_type];
270
        }
271
272
        $atomic_types = [];
273
274
        foreach ($matching_atomic_types as $matching_atomic_type) {
275
            $atomic_types[] = $atomic_type->replaceTemplateTypesWithStandins(
276
                $template_result,
277
                $codebase,
278
                $statements_analyzer,
279
                $matching_atomic_type,
280
                $input_arg_offset,
281
                $calling_class,
282
                $calling_function,
283
                $replace,
284
                $add_upper_bound,
285
                $depth + 1
286
            );
287
        }
288
289
        return $atomic_types;
290
    }
291
292
    /**
293
     * @return list<Atomic>
0 ignored issues
show
Documentation introduced by
The doc-type list<Atomic> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (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...
294
     */
295
    private static function findMatchingAtomicTypesForTemplate(
296
        Union $input_type,
297
        Atomic $base_type,
298
        string $key,
299
        Codebase $codebase,
300
        ?StatementsAnalyzer $statements_analyzer
301
    ) : array {
302
        $matching_atomic_types = [];
303
304
        foreach ($input_type->getAtomicTypes() as $input_key => $atomic_input_type) {
305
            if ($bracket_pos = strpos($input_key, '<')) {
306
                $input_key = substr($input_key, 0, $bracket_pos);
307
            }
308
309
            if ($input_key === $key) {
310
                $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
311
                continue;
312
            }
313
314
            if ($atomic_input_type instanceof Atomic\TFn && $base_type instanceof Atomic\TFn) {
315
                $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
316
                continue;
317
            }
318
319
            if ($atomic_input_type instanceof Atomic\TCallable
320
                && $base_type instanceof Atomic\TCallable
321
            ) {
322
                $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
323
                continue;
324
            }
325
326
            if ($atomic_input_type instanceof Atomic\TFn && $base_type instanceof Atomic\TCallable) {
327
                $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
328
                continue;
329
            }
330
331
            if (($atomic_input_type instanceof Atomic\TArray
332
                    || $atomic_input_type instanceof Atomic\ObjectLike
333
                    || $atomic_input_type instanceof Atomic\TList)
334
                && $key === 'iterable'
335
            ) {
336
                $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
337
                continue;
338
            }
339
340
            if (strpos($input_key, $key . '&') === 0) {
341
                $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
342
                continue;
343
            }
344
345
            if ($atomic_input_type instanceof Atomic\TLiteralClassString
346
                && $base_type instanceof Atomic\TClassString
347
                && $base_type->as_type
348
            ) {
349
                try {
350
                    $classlike_storage =
351
                        $codebase->classlike_storage_provider->get($atomic_input_type->value);
352
353
                    if (isset($classlike_storage->template_type_extends[$base_type->as_type->value])) {
354
                        $extends_list = $classlike_storage->template_type_extends[$base_type->as_type->value];
355
356
                        $new_generic_params = [];
357
358
                        foreach ($extends_list as $extends_key => $value) {
359
                            if (is_string($extends_key)) {
360
                                $new_generic_params[] = $value;
361
                            }
362
                        }
363
364
                        if ($new_generic_params) {
365
                            $atomic_input_type = new Atomic\TClassString(
366
                                $base_type->as_type->value,
367
                                new Atomic\TGenericObject(
368
                                    $base_type->as_type->value,
369
                                    $new_generic_params
370
                                )
371
                            );
372
                        }
373
374
                        $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
375
                        continue;
376
                    }
377
                } catch (\InvalidArgumentException $e) {
378
                    // do nothing
379
                }
380
            }
381
382
            if ($base_type instanceof Atomic\TCallable) {
383
                $matching_atomic_type = TypeAnalyzer::getCallableFromAtomic(
384
                    $codebase,
385
                    $atomic_input_type,
386
                    null,
387
                    $statements_analyzer
388
                );
389
390
                if ($matching_atomic_type) {
391
                    $matching_atomic_types[$matching_atomic_type->getId()] = $matching_atomic_type;
392
                    continue;
393
                }
394
            }
395
396
            if ($atomic_input_type instanceof Atomic\TNamedObject
397
                && ($base_type instanceof Atomic\TNamedObject
398
                    || $base_type instanceof Atomic\TIterable)
399
            ) {
400
                if ($base_type instanceof Atomic\TIterable) {
401
                    if ($atomic_input_type->value === 'Traversable') {
402
                        $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
403
                        continue;
404
                    }
405
406
                    $base_type = new Atomic\TGenericObject(
407
                        'Traversable',
408
                        $base_type->type_params
409
                    );
410
                }
411
412
                try {
413
                    $classlike_storage =
414
                        $codebase->classlike_storage_provider->get($atomic_input_type->value);
415
416
                    if ($atomic_input_type instanceof Atomic\TGenericObject
417
                        && isset($classlike_storage->template_type_extends[$base_type->value])
418
                    ) {
419
                        $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
420
                        continue;
421
                    }
422
423
                    if (isset($classlike_storage->template_type_extends[$base_type->value])) {
424
                        $extends_list = $classlike_storage->template_type_extends[$base_type->value];
425
426
                        $new_generic_params = [];
427
428
                        foreach ($extends_list as $extends_key => $value) {
429
                            if (is_string($extends_key)) {
430
                                $new_generic_params[] = $value;
431
                            }
432
                        }
433
434
                        if (!$new_generic_params) {
435
                            $atomic_input_type = new Atomic\TNamedObject($atomic_input_type->value);
436
                        } else {
437
                            $atomic_input_type = new Atomic\TGenericObject(
438
                                $atomic_input_type->value,
439
                                $new_generic_params
440
                            );
441
                        }
442
443
                        $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type;
444
                        continue;
445
                    }
446
                } catch (\InvalidArgumentException $e) {
447
                    // do nothing
448
                }
449
            }
450
        }
451
452
        return array_values($matching_atomic_types);
453
    }
454
455
    /**
456
     * @return list<Atomic>
0 ignored issues
show
Documentation introduced by
The doc-type list<Atomic> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (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...
457
     */
458
    private static function handleTemplateParamStandin(
459
        Atomic\TTemplateParam $atomic_type,
460
        string $key,
461
        ?Union $input_type,
462
        ?int $input_arg_offset,
463
        ?string $calling_class,
464
        ?string $calling_function,
465
        TemplateResult $template_result,
466
        ?Codebase $codebase,
467
        ?StatementsAnalyzer $statements_analyzer,
468
        bool $replace,
469
        bool $add_upper_bound,
470
        int $depth,
471
        bool $was_nullable,
472
        bool &$had_template
473
    ) : array {
474
        $template_type = $template_result->template_types
475
            [$atomic_type->param_name]
476
            [$atomic_type->defining_class]
477
            [0];
478
479
        if ($template_type->getId() === $key) {
480
            return array_values($template_type->getAtomicTypes());
481
        }
482
483
        $replacement_type = $template_type;
484
485
        $param_name_key = $atomic_type->param_name;
486
487
        if (strpos($key, '&')) {
488
            $param_name_key = $key;
489
        }
490
491
        if ($replace) {
492
            $atomic_types = [];
493
494
            if ($replacement_type->hasMixed()
495
                && !$atomic_type->as->hasMixed()
496
            ) {
497
                foreach ($atomic_type->as->getAtomicTypes() as $as_atomic_type) {
498
                    $atomic_types[] = clone $as_atomic_type;
499
                }
500
            } else {
501
                if ($codebase) {
502
                    $replacement_type = TypeExpander::expandUnion(
503
                        $codebase,
504
                        $replacement_type,
505
                        $calling_class,
506
                        $calling_class,
507
                        null
508
                    );
509
                }
510
511
                if ($depth < 10) {
512
                    $replacement_type = self::replaceTemplateTypesWithStandins(
513
                        $replacement_type,
514
                        $template_result,
515
                        $codebase,
516
                        $statements_analyzer,
517
                        $input_type,
518
                        $input_arg_offset,
519
                        $calling_class,
520
                        $calling_function,
521
                        $replace,
522
                        $add_upper_bound,
523
                        $depth + 1
524
                    );
525
                }
526
527
                foreach ($replacement_type->getAtomicTypes() as $replacement_atomic_type) {
528
                    $replacements_found = false;
529
530
                    // @codingStandardsIgnoreStart
531
                    if ($replacement_atomic_type instanceof Atomic\TTemplateKeyOf
532
                        && isset($template_result->template_types[$replacement_atomic_type->param_name][$replacement_atomic_type->defining_class][0])
533
                    ) {
534
                        $keyed_template = $template_result->template_types[$replacement_atomic_type->param_name][$replacement_atomic_type->defining_class][0];
535
536
                        if ($keyed_template->isSingle()) {
537
                            $keyed_template = array_values($keyed_template->getAtomicTypes())[0];
538
                        }
539
540
                        if ($keyed_template instanceof Atomic\ObjectLike
541
                            || $keyed_template instanceof Atomic\TArray
542
                            || $keyed_template instanceof Atomic\TList
543
                        ) {
544
                            if ($keyed_template instanceof Atomic\ObjectLike) {
545
                                $key_type = $keyed_template->getGenericKeyType();
546
                            } elseif ($keyed_template instanceof Atomic\TList) {
547
                                $key_type = \Psalm\Type::getInt();
548
                            } else {
549
                                $key_type = $keyed_template->type_params[0];
550
                            }
551
552
                            $replacements_found = true;
553
554
                            foreach ($key_type->getAtomicTypes() as $key_type_atomic) {
555
                                $atomic_types[] = clone $key_type_atomic;
556
                            }
557
558
                            $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class][0]
559
                                = clone $key_type;
560
                        }
561
                    }
562
563
                    if ($replacement_atomic_type instanceof Atomic\TTemplateParam
564
                        && $replacement_atomic_type->defining_class !== $calling_class
565
                        && $replacement_atomic_type->defining_class !== 'fn-' . $calling_function
566
                    ) {
567
                        foreach ($replacement_atomic_type->as->getAtomicTypes() as $nested_type_atomic) {
568
                            $replacements_found = true;
569
                            $atomic_types[] = clone $nested_type_atomic;
570
                        }
571
                    }
572
                    // @codingStandardsIgnoreEnd
573
574
                    if (!$replacements_found) {
575
                        $atomic_types[] = clone $replacement_atomic_type;
576
                    }
577
578
                    $had_template = true;
579
                }
580
            }
581
582
            $matching_input_keys = [];
583
584
            if ($codebase) {
585
                $atomic_type->as = TypeExpander::expandUnion(
586
                    $codebase,
587
                    $atomic_type->as,
588
                    $calling_class,
589
                    $calling_class,
590
                    null
591
                );
592
            }
593
594
            $atomic_type->as = self::replaceTemplateTypesWithStandins(
595
                $atomic_type->as,
596
                $template_result,
597
                $codebase,
598
                $statements_analyzer,
599
                $input_type,
600
                $input_arg_offset,
601
                $calling_class,
602
                $calling_function,
603
                $replace,
604
                $add_upper_bound,
605
                $depth + 1
606
            );
607
608
            if ($input_type
609
                && (
610
                    $atomic_type->as->isMixed()
611
                    || !$codebase
612
                    || TypeAnalyzer::canBeContainedBy(
613
                        $codebase,
614
                        $input_type,
615
                        $atomic_type->as,
616
                        false,
617
                        false,
618
                        $matching_input_keys
619
                    )
620
                )
621
            ) {
622
                $generic_param = clone $input_type;
623
624
                if ($matching_input_keys) {
625
                    $generic_param_keys = \array_keys($generic_param->getAtomicTypes());
626
627
                    foreach ($generic_param_keys as $atomic_key) {
628
                        if (!isset($matching_input_keys[$atomic_key])) {
629
                            $generic_param->removeType($atomic_key);
630
                        }
631
                    }
632
                }
633
634
                if ($was_nullable && $generic_param->isNullable() && !$generic_param->isNull()) {
635
                    $generic_param->removeType('null');
636
                }
637
638
                if ($add_upper_bound) {
639
                    return array_values($generic_param->getAtomicTypes());
640
                }
641
642
                $generic_param->setFromDocblock();
643
644
                if (isset(
645
                    $template_result->upper_bounds[$param_name_key][$atomic_type->defining_class][0]
646
                )) {
647
                    $existing_generic_param = $template_result->upper_bounds
648
                        [$param_name_key]
649
                        [$atomic_type->defining_class];
650
651
                    $existing_depth = $existing_generic_param[1] ?? -1;
652
                    $existing_arg_offset = $existing_generic_param[2] ?? $input_arg_offset;
653
654
                    if ($existing_depth > $depth && $input_arg_offset === $existing_arg_offset) {
655
                        return $atomic_types ?: [$atomic_type];
656
                    }
657
658
                    if ($existing_depth === $depth || $input_arg_offset !== $existing_arg_offset) {
659
                        $generic_param = \Psalm\Type::combineUnionTypes(
660
                            $template_result->upper_bounds
661
                                [$param_name_key]
662
                                [$atomic_type->defining_class]
663
                                [0],
664
                            $generic_param,
665
                            $codebase
666
                        );
667
                    }
668
                }
669
670
                $template_result->upper_bounds[$param_name_key][$atomic_type->defining_class] = [
671
                    $generic_param,
672
                    $depth,
673
                    $input_arg_offset
674
                ];
675
            }
676
677
            return $atomic_types;
678
        }
679
680
        if ($add_upper_bound && $input_type) {
681
            $matching_input_keys = [];
682
683
            if ($codebase
684
                && TypeAnalyzer::canBeContainedBy(
685
                    $codebase,
686
                    $input_type,
687
                    $replacement_type,
688
                    false,
689
                    false,
690
                    $matching_input_keys
691
                )
692
            ) {
693
                $generic_param = clone $input_type;
694
695
                if ($matching_input_keys) {
696
                    $generic_param_keys = \array_keys($generic_param->getAtomicTypes());
697
698
                    foreach ($generic_param_keys as $atomic_key) {
699
                        if (!isset($matching_input_keys[$atomic_key])) {
700
                            $generic_param->removeType($atomic_key);
701
                        }
702
                    }
703
                }
704
705
                if (isset($template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0])) {
706
                    $intersection_type = \Psalm\Type::intersectUnionTypes(
707
                        $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0],
708
                        $generic_param,
709
                        $codebase
710
                    );
711
712
                    if ($intersection_type) {
713
                        $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0]
714
                            = $intersection_type;
715
                    } else {
716
                        $template_result->lower_bounds_unintersectable_types[]
717
                            = $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0];
718
                        $template_result->lower_bounds_unintersectable_types[] = $generic_param;
719
720
                        $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0]
721
                            = \Psalm\Type::getMixed();
722
                    }
723
                } else {
724
                    $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0]
725
                        = $generic_param;
726
                }
727
            }
728
        }
729
730
        return [$atomic_type];
731
    }
732
733
    /**
734
     * @return list<Atomic>
0 ignored issues
show
Documentation introduced by
The doc-type list<Atomic> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (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...
735
     */
736
    public static function handleTemplateParamClassStandin(
737
        Atomic\TTemplateParamClass $atomic_type,
738
        ?Union $input_type,
739
        ?int $input_arg_offset,
740
        TemplateResult $template_result,
741
        int $depth,
742
        bool $was_single
743
    ) : array {
744
        $class_string = new Atomic\TClassString($atomic_type->as, $atomic_type->as_type);
745
746
        $atomic_types = [];
747
748
        $atomic_types[] = $class_string;
749
750
        if ($input_type) {
751
            $valid_input_atomic_types = [];
752
753
            foreach ($input_type->getAtomicTypes() as $input_atomic_type) {
754
                if ($input_atomic_type instanceof Atomic\TLiteralClassString) {
755
                    $valid_input_atomic_types[] = new Atomic\TNamedObject(
756
                        $input_atomic_type->value
757
                    );
758
                } elseif ($input_atomic_type instanceof Atomic\TTemplateParamClass) {
759
                    $valid_input_atomic_types[] = new Atomic\TTemplateParam(
760
                        $input_atomic_type->param_name,
761
                        $input_atomic_type->as_type
762
                            ? new Union([$input_atomic_type->as_type])
763
                            : ($input_atomic_type->as === 'object'
764
                                ? \Psalm\Type::getObject()
765
                                : \Psalm\Type::getMixed()),
766
                        $input_atomic_type->defining_class
767
                    );
768
                } elseif ($input_atomic_type instanceof Atomic\TClassString) {
769
                    if ($input_atomic_type->as_type) {
770
                        $valid_input_atomic_types[] = clone $input_atomic_type->as_type;
771
                    } elseif ($input_atomic_type->as !== 'object') {
772
                        $valid_input_atomic_types[] = new Atomic\TNamedObject(
773
                            $input_atomic_type->as
774
                        );
775
                    } else {
776
                        $valid_input_atomic_types[] = new Atomic\TObject();
777
                    }
778
                }
779
            }
780
781
            $generic_param = null;
782
783
            if ($valid_input_atomic_types) {
784
                $generic_param = new Union($valid_input_atomic_types);
785
                $generic_param->setFromDocblock();
786
            } elseif ($was_single) {
787
                $generic_param = \Psalm\Type::getMixed();
788
            }
789
790
            if ($generic_param) {
791
                if (isset($template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class])) {
792
                    $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class] = [
793
                        \Psalm\Type::combineUnionTypes(
794
                            $generic_param,
795
                            $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class][0]
796
                        ),
797
                        $depth
798
                    ];
799
                } else {
800
                    $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class] = [
801
                        $generic_param,
802
                        $depth,
803
                        $input_arg_offset
804
                    ];
805
                }
806
            }
807
        }
808
809
        return $atomic_types;
810
    }
811
812
    /**
813
     * @param  array<string, array<string, array{Union, 1?:int}>>  $template_types
814
     *
815
     * @return array{Union, 1?:int}|null
0 ignored issues
show
Documentation introduced by
The doc-type array{Union, could not be parsed: Unknown type name "array{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...
816
     */
817
    public static function getRootTemplateType(
818
        array $template_types,
819
        string $param_name,
820
        string $defining_class,
821
        array $visited_classes = []
822
    ) : ?array {
823
        if (isset($template_types[$param_name][$defining_class])) {
824
            $mapped_type = $template_types[$param_name][$defining_class][0];
825
826
            $mapped_type_atomic_types = array_values($mapped_type->getAtomicTypes());
827
828
            if (count($mapped_type_atomic_types) > 1
829
                || !$mapped_type_atomic_types[0] instanceof Atomic\TTemplateParam
830
                || isset($visited_classes[$defining_class])
831
            ) {
832
                return $template_types[$param_name][$defining_class];
833
            }
834
835
            $first_template = $mapped_type_atomic_types[0];
836
837
            return self::getRootTemplateType(
838
                $template_types,
839
                $first_template->param_name,
840
                $first_template->defining_class,
841
                $visited_classes + [$defining_class => true]
842
            ) ?? $template_types[$param_name][$defining_class];
843
        }
844
845
        return null;
846
    }
847
848
    /**
849
     * @param Atomic\TGenericObject|Atomic\TIterable $input_type_part
850
     * @param Atomic\TGenericObject|Atomic\TIterable $container_type_part
851
     * @return list<Union>
0 ignored issues
show
Documentation introduced by
The doc-type list<Union> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (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...
852
     */
853
    public static function getMappedGenericTypeParams(
854
        Codebase $codebase,
855
        Atomic $input_type_part,
856
        Atomic $container_type_part,
857
        ?array &$container_type_params_covariant = null
858
    ) : array {
859
        $input_type_params = $input_type_part->type_params;
0 ignored issues
show
Bug introduced by
The property type_params does not seem to exist in Psalm\Type\Atomic.

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

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

Loading history...
860
861
        try {
862
            $input_class_storage = $codebase->classlike_storage_provider->get($input_type_part->value);
0 ignored issues
show
Bug introduced by
The property value 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...
863
            $container_class_storage = $codebase->classlike_storage_provider->get($container_type_part->value);
864
            $container_type_params_covariant = $container_class_storage->template_covariants;
865
        } catch (\Throwable $e) {
866
            $input_class_storage = null;
867
            $container_class_storage = null;
0 ignored issues
show
Unused Code introduced by
$container_class_storage is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
868
        }
869
870
        if ($input_type_part->value !== $container_type_part->value
871
            && $input_class_storage
872
        ) {
873
            $input_template_types = $input_class_storage->template_types;
874
            $i = 0;
875
876
            $replacement_templates = [];
877
878
            if ($input_template_types
879
                && (!$input_type_part instanceof Atomic\TGenericObject || !$input_type_part->remapped_params)
880
                && (!$container_type_part instanceof Atomic\TGenericObject || !$container_type_part->remapped_params)
881
            ) {
882
                foreach ($input_template_types as $template_name => $_) {
883
                    if (!isset($input_type_params[$i])) {
884
                        break;
885
                    }
886
887
                    $replacement_templates[$template_name][$input_type_part->value] = [$input_type_params[$i]];
888
889
                    $i++;
890
                }
891
            }
892
893
            $template_extends = $input_class_storage->template_type_extends;
894
895
            if (isset($template_extends[$container_type_part->value])) {
896
                $params = $template_extends[$container_type_part->value];
897
898
                $new_input_params = [];
899
900
                foreach ($params as $key => $extended_input_param_type) {
901
                    if (is_string($key)) {
902
                        $new_input_param = null;
903
904
                        foreach ($extended_input_param_type->getAtomicTypes() as $et) {
905
                            if ($et instanceof Atomic\TTemplateParam) {
906
                                $ets = \Psalm\Internal\Codebase\Methods::getExtendedTemplatedTypes(
907
                                    $et,
908
                                    $template_extends
909
                                );
910
                            } else {
911
                                $ets = [];
912
                            }
913
914
                            if ($ets
915
                                && $ets[0] instanceof Atomic\TTemplateParam
916
                                && isset(
917
                                    $input_class_storage->template_types
918
                                        [$ets[0]->param_name]
919
                                        [$ets[0]->defining_class]
920
                                )
921
                            ) {
922
                                $old_params_offset = (int) \array_search(
923
                                    $ets[0]->param_name,
924
                                    \array_keys($input_class_storage->template_types)
925
                                );
926
927
                                if (!isset($input_type_params[$old_params_offset])) {
928
                                    $candidate_param_type = \Psalm\Type::getMixed();
929
                                } else {
930
                                    $candidate_param_type = $input_type_params[$old_params_offset];
931
                                }
932
                            } else {
933
                                $candidate_param_type = new Union([clone $et]);
934
                            }
935
936
                            $candidate_param_type->from_template_default = true;
937
938
                            if (!$new_input_param) {
939
                                $new_input_param = $candidate_param_type;
940
                            } else {
941
                                $new_input_param = \Psalm\Type::combineUnionTypes(
942
                                    $new_input_param,
943
                                    $candidate_param_type
944
                                );
945
                            }
946
                        }
947
948
                        $new_input_param = clone $new_input_param;
949
                        $new_input_param->replaceTemplateTypesWithArgTypes(
950
                            new TemplateResult([], $replacement_templates),
951
                            $codebase
952
                        );
953
954
                        $new_input_params[] = $new_input_param;
955
                    }
956
                }
957
958
                $input_type_params = $new_input_params;
959
            }
960
        }
961
962
        return $input_type_params;
963
    }
964
}
965