Populator::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 6
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
1
<?php
2
namespace Psalm\Internal\Codebase;
3
4
use function array_keys;
5
use function array_merge;
6
use function count;
7
use function explode;
8
use function is_int;
9
use Psalm\Config;
10
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
11
use Psalm\Internal\Analyzer\TypeAnalyzer;
12
use Psalm\Internal\Provider\ClassLikeStorageProvider;
13
use Psalm\Internal\Provider\FileReferenceProvider;
14
use Psalm\Internal\Provider\FileStorageProvider;
15
use Psalm\Issue\CircularReference;
16
use Psalm\IssueBuffer;
17
use Psalm\Progress\Progress;
18
use Psalm\Storage\ClassLikeStorage;
19
use Psalm\Storage\FileStorage;
20
use Psalm\Type;
21
use function reset;
22
use function strpos;
23
use function strtolower;
24
25
/**
26
 * @internal
27
 *
28
 * Populates file and class information so that analysis can work properly
29
 */
30
class Populator
31
{
32
    /**
33
     * @var ClassLikeStorageProvider
34
     */
35
    private $classlike_storage_provider;
36
37
    /**
38
     * @var FileStorageProvider
39
     */
40
    private $file_storage_provider;
41
42
    /**
43
     * @var array<lowercase-string, list<ClassLikeStorage>>
44
     */
45
    private $invalid_class_storages = [];
46
47
    /**
48
     * @var Progress
49
     */
50
    private $progress;
51
52
    /**
53
     * @var ClassLikes
54
     */
55
    private $classlikes;
56
57
    /**
58
     * @var Config
59
     */
60
    private $config;
61
62
    /**
63
     * @var FileReferenceProvider
64
     */
65
    private $file_reference_provider;
66
67
    public function __construct(
68
        Config $config,
69
        ClassLikeStorageProvider $classlike_storage_provider,
70
        FileStorageProvider $file_storage_provider,
71
        ClassLikes $classlikes,
72
        FileReferenceProvider $file_reference_provider,
73
        Progress $progress
74
    ) {
75
        $this->classlike_storage_provider = $classlike_storage_provider;
76
        $this->file_storage_provider = $file_storage_provider;
77
        $this->classlikes = $classlikes;
78
        $this->progress = $progress;
79
        $this->config = $config;
80
        $this->file_reference_provider = $file_reference_provider;
81
    }
82
83
    /**
84
     * @return void
85
     */
86
    public function populateCodebase()
87
    {
88
        $this->progress->debug('ClassLikeStorage is populating' . "\n");
89
90
        foreach ($this->classlike_storage_provider->getNew() as $class_storage) {
91
            $this->populateClassLikeStorage($class_storage);
92
        }
93
94
        $this->progress->debug('ClassLikeStorage is populated' . "\n");
95
96
        $this->progress->debug('FileStorage is populating' . "\n");
97
98
        $all_file_storage = $this->file_storage_provider->getNew();
99
100
        foreach ($all_file_storage as $file_storage) {
101
            $this->populateFileStorage($file_storage);
102
        }
103
104
        foreach ($this->classlike_storage_provider->getNew() as $class_storage) {
105
            if ($this->config->allow_phpstorm_generics) {
106
                foreach ($class_storage->properties as $property_storage) {
107
                    if ($property_storage->type) {
108
                        $this->convertPhpStormGenericToPsalmGeneric($property_storage->type, true);
109
                    }
110
                }
111
112
                foreach ($class_storage->methods as $method_storage) {
113
                    if ($method_storage->return_type) {
114
                        $this->convertPhpStormGenericToPsalmGeneric($method_storage->return_type);
115
                    }
116
117
                    foreach ($method_storage->params as $param_storage) {
118
                        if ($param_storage->type) {
119
                            $this->convertPhpStormGenericToPsalmGeneric($param_storage->type);
120
                        }
121
                    }
122
                }
123
            }
124
125
            foreach ($class_storage->dependent_classlikes as $dependent_classlike_lc => $_) {
126
                $dependee_storage = $this->classlike_storage_provider->get($dependent_classlike_lc);
127
128
                $class_storage->dependent_classlikes += $dependee_storage->dependent_classlikes;
129
            }
130
        }
131
132
        if ($this->config->allow_phpstorm_generics) {
133
            foreach ($all_file_storage as $file_storage) {
134
                foreach ($file_storage->functions as $function_storage) {
135
                    if ($function_storage->return_type) {
136
                        $this->convertPhpStormGenericToPsalmGeneric($function_storage->return_type);
137
                    }
138
139
                    foreach ($function_storage->params as $param_storage) {
140
                        if ($param_storage->type) {
141
                            $this->convertPhpStormGenericToPsalmGeneric($param_storage->type);
142
                        }
143
                    }
144
                }
145
            }
146
        }
147
148
        $this->progress->debug('FileStorage is populated' . "\n");
149
150
        $this->classlike_storage_provider->populated();
151
        $this->file_storage_provider->populated();
152
    }
153
154
    /**
155
     * @param  ClassLikeStorage $storage
156
     * @param  array            $dependent_classlikes
157
     *
158
     * @return void
159
     */
160
    private function populateClassLikeStorage(ClassLikeStorage $storage, array $dependent_classlikes = [])
161
    {
162
        if ($storage->populated) {
163
            return;
164
        }
165
166
        $fq_classlike_name_lc = strtolower($storage->name);
167
168
        if (isset($dependent_classlikes[$fq_classlike_name_lc])) {
169
            if ($storage->location && IssueBuffer::accepts(
170
                new CircularReference(
171
                    'Circular reference discovered when loading ' . $storage->name,
172
                    $storage->location
173
                )
174
            )) {
175
                // fall through
176
            }
177
178
            return;
179
        }
180
181
        $storage_provider = $this->classlike_storage_provider;
182
183
        $dependent_classlikes[$fq_classlike_name_lc] = true;
184
185
        $this->populateDataFromTraits($storage, $storage_provider, $dependent_classlikes);
186
187
        if ($storage->parent_classes) {
188
            $this->populateDataFromParentClass($storage, $storage_provider, $dependent_classlikes);
189
        }
190
191
        if (!strpos($fq_classlike_name_lc, '\\')
192
            && !isset($storage->methods['__construct'])
193
            && isset($storage->methods[$fq_classlike_name_lc])
194
            && !$storage->is_interface
195
            && !$storage->is_trait
196
        ) {
197
            /** @psalm-suppress PropertyTypeCoercion */
198
            $storage->methods['__construct'] = $storage->methods[$fq_classlike_name_lc];
199
        }
200
201
        $this->populateInterfaceDataFromParentInterfaces($storage, $storage_provider, $dependent_classlikes);
202
203
        $this->populateDataFromImplementedInterfaces($storage, $storage_provider, $dependent_classlikes);
204
205
        if ($storage->location) {
206
            $file_path = $storage->location->file_path;
207
208
            foreach ($storage->parent_interfaces as $parent_interface_lc) {
209
                $this->file_reference_provider->addFileInheritanceToClass($file_path, $parent_interface_lc);
210
            }
211
212
            foreach ($storage->parent_classes as $parent_class_lc => $_) {
213
                $this->file_reference_provider->addFileInheritanceToClass($file_path, $parent_class_lc);
214
            }
215
216
            foreach ($storage->class_implements as $implemented_interface) {
217
                $this->file_reference_provider->addFileInheritanceToClass(
218
                    $file_path,
219
                    strtolower($implemented_interface)
220
                );
221
            }
222
223
            foreach ($storage->used_traits as $used_trait_lc => $_) {
224
                $this->file_reference_provider->addFileInheritanceToClass($file_path, $used_trait_lc);
225
            }
226
        }
227
228
        if ($storage->mutation_free || $storage->external_mutation_free) {
229
            foreach ($storage->methods as $method) {
230
                if (!$method->is_static && !$method->external_mutation_free) {
231
                    $method->mutation_free = $storage->mutation_free;
232
                    $method->external_mutation_free = $storage->external_mutation_free;
233
                }
234
            }
235
236
            if ($storage->mutation_free) {
237
                foreach ($storage->properties as $property) {
238
                    if (!$property->is_static) {
239
                        $property->readonly = true;
240
                    }
241
                }
242
            }
243
        }
244
245
        if ($storage->specialize_instance) {
246
            foreach ($storage->methods as $method) {
247
                if (!$method->is_static) {
248
                    $method->specialize_call = true;
249
                }
250
            }
251
        }
252
253
        if ($storage->internal
254
            && !$storage->is_interface
255
            && !$storage->is_trait
256
        ) {
257
            foreach ($storage->methods as $method) {
258
                $method->internal = true;
259
            }
260
261
            foreach ($storage->properties as $property) {
262
                $property->internal = true;
263
            }
264
        }
265
266
        $this->populateOverriddenMethods($storage);
267
268
        $this->progress->debug('Have populated ' . $storage->name . "\n");
269
270
        $storage->populated = true;
271
272
        if (isset($this->invalid_class_storages[$fq_classlike_name_lc])) {
273
            foreach ($this->invalid_class_storages[$fq_classlike_name_lc] as $dependency) {
274
                $dependency->populated = false;
275
                $this->populateClassLikeStorage($dependency, $dependent_classlikes);
276
            }
277
278
            unset($this->invalid_class_storages[$fq_classlike_name_lc]);
279
        }
280
    }
281
282
    /** @return void */
283
    private function populateOverriddenMethods(
284
        ClassLikeStorage $storage
285
    ) {
286
        foreach ($storage->methods as $method_name => $method_storage) {
287
            if (isset($storage->overridden_method_ids[$method_name])) {
288
                $overridden_method_ids = $storage->overridden_method_ids[$method_name];
289
290
                $candidate_overridden_ids = null;
291
292
                $declaring_class_storages = [];
293
294
                foreach ($overridden_method_ids as $declaring_method_id) {
295
                    $declaring_class = $declaring_method_id->fq_class_name;
296
                    $declaring_class_storage
297
                        = $declaring_class_storages[$declaring_class]
298
                        = $this->classlike_storage_provider->get($declaring_class);
299
300
                    if ($candidate_overridden_ids === null) {
301
                        $candidate_overridden_ids
302
                            = ($declaring_class_storage->overridden_method_ids[$method_name] ?? [])
303
                                + [$declaring_method_id->fq_class_name => $declaring_method_id];
304
                    } else {
305
                        $candidate_overridden_ids = \array_intersect_key(
306
                            $candidate_overridden_ids,
307
                            ($declaring_class_storage->overridden_method_ids[$method_name] ?? [])
308
                                + [$declaring_method_id->fq_class_name => $declaring_method_id]
309
                        );
310
                    }
311
                }
312
313
                foreach ($overridden_method_ids as $declaring_method_id) {
314
                    $declaring_class = $declaring_method_id->fq_class_name;
315
                    $declaring_method_name = $declaring_method_id->method_name;
316
                    ;
317
                    $declaring_class_storage = $declaring_class_storages[$declaring_class];
318
319
                    $declaring_method_storage = $declaring_class_storage->methods[$declaring_method_name];
320
321
                    if ($declaring_method_storage->has_docblock_param_types
322
                        && !$method_storage->has_docblock_param_types
323
                        && !isset($storage->documenting_method_ids[$method_name])
324
                    ) {
325
                        $storage->documenting_method_ids[$method_name] = $declaring_method_id;
326
                    }
327
328
                    // tell the declaring class it's overridden downstream
329
                    $declaring_method_storage->overridden_downstream = true;
330
                    $declaring_method_storage->overridden_somewhere = true;
331
332
                    if ($declaring_method_storage->mutation_free_inferred) {
333
                        $declaring_method_storage->mutation_free = false;
334
                        $declaring_method_storage->external_mutation_free = false;
335
                        $declaring_method_storage->mutation_free_inferred = false;
336
                    }
337
338
                    if ($declaring_method_storage->throws
339
                        && (!$method_storage->throws || $method_storage->inheritdoc)
340
                    ) {
341
                        $method_storage->throws += $declaring_method_storage->throws;
342
                    }
343
344
                    if ((count($overridden_method_ids) === 1
345
                            || $candidate_overridden_ids)
346
                        && $method_storage->signature_return_type
347
                        && !$method_storage->signature_return_type->isVoid()
348
                        && ($method_storage->return_type === $method_storage->signature_return_type
349
                            || $method_storage->inherited_return_type)
350
                    ) {
351
                        if (isset($declaring_class_storage->methods[$method_name])) {
352
                            $declaring_method_storage = $declaring_class_storage->methods[$method_name];
353
354
                            if ($declaring_method_storage->return_type
355
                                && $declaring_method_storage->return_type
356
                                    !== $declaring_method_storage->signature_return_type
357
                            ) {
358
                                if ($declaring_method_storage->signature_return_type
359
                                    && TypeAnalyzer::isSimplyContainedBy(
360
                                        $method_storage->signature_return_type,
361
                                        $declaring_method_storage->signature_return_type
362
                                    )
363
                                ) {
364
                                    $method_storage->return_type = $declaring_method_storage->return_type;
365
                                    $method_storage->inherited_return_type = true;
366
                                } elseif (TypeAnalyzer::isSimplyContainedBy(
367
                                    $declaring_method_storage->return_type,
368
                                    $method_storage->signature_return_type
369
                                )) {
370
                                    $method_storage->return_type = $declaring_method_storage->return_type;
371
                                    $method_storage->inherited_return_type = true;
372
                                }
373
                            }
374
                        }
375
                    }
376
                }
377
            }
378
        }
379
    }
380
381
    /**
382
     * @return void
383
     */
384
    private function populateDataFromTraits(
385
        ClassLikeStorage $storage,
386
        ClassLikeStorageProvider $storage_provider,
387
        array $dependent_classlikes
388
    ) {
389
        foreach ($storage->used_traits as $used_trait_lc => $_) {
390
            try {
391
                $used_trait_lc = strtolower(
392
                    $this->classlikes->getUnAliasedName(
393
                        $used_trait_lc
394
                    )
395
                );
396
                $trait_storage = $storage_provider->get($used_trait_lc);
397
            } catch (\InvalidArgumentException $e) {
398
                continue;
399
            }
400
401
            $this->populateClassLikeStorage($trait_storage, $dependent_classlikes);
402
403
            $this->inheritMethodsFromParent($storage, $trait_storage);
404
            $this->inheritPropertiesFromParent($storage, $trait_storage);
405
406 View Code Duplication
            if ($trait_storage->template_types) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
407
                if (isset($storage->template_type_extends[$trait_storage->name])) {
408
                    foreach ($storage->template_type_extends[$trait_storage->name] as $i => $type) {
409
                        $trait_template_type_names = array_keys($trait_storage->template_types);
410
411
                        $mapped_name = $trait_template_type_names[$i] ?? null;
412
413
                        if ($mapped_name) {
414
                            $storage->template_type_extends[$trait_storage->name][$mapped_name] = $type;
415
                        }
416
                    }
417
418
                    if ($trait_storage->template_type_extends) {
419
                        foreach ($trait_storage->template_type_extends as $t_storage_class => $type_map) {
420
                            foreach ($type_map as $i => $type) {
421
                                if (is_int($i)) {
422
                                    continue;
423
                                }
424
425
                                $storage->template_type_extends[$t_storage_class][$i] = self::extendType(
426
                                    $type,
427
                                    $storage
428
                                );
429
                            }
430
                        }
431
                    }
432
                } else {
433
                    $storage->template_type_extends[$trait_storage->name] = [];
434
435
                    foreach ($trait_storage->template_types as $template_name => $template_type_map) {
436
                        foreach ($template_type_map as $template_type) {
437
                            $default_param = clone $template_type[0];
438
                            $default_param->from_docblock = false;
439
                            $storage->template_type_extends[$trait_storage->name][$template_name]
440
                                = $default_param;
441
                        }
442
                    }
443
                }
444
            } elseif ($trait_storage->template_type_extends) {
445
                $storage->template_type_extends = array_merge(
446
                    $storage->template_type_extends ?: [],
447
                    $trait_storage->template_type_extends
448
                );
449
            }
450
451
            $storage->pseudo_property_get_types += $trait_storage->pseudo_property_get_types;
452
            $storage->pseudo_property_set_types += $trait_storage->pseudo_property_set_types;
453
454
            $storage->pseudo_methods += $trait_storage->pseudo_methods;
455
        }
456
    }
457
458
    private static function extendType(
459
        Type\Union $type,
460
        ClassLikeStorage $storage
461
    ) : Type\Union {
462
        $extended_types = [];
463
464
        foreach ($type->getAtomicTypes() as $atomic_type) {
465
            if ($atomic_type instanceof Type\Atomic\TTemplateParam) {
466
                $referenced_type
467
                    = $storage->template_type_extends[$atomic_type->defining_class][$atomic_type->param_name]
468
                        ?? null;
469
470
                if ($referenced_type) {
471
                    foreach ($referenced_type->getAtomicTypes() as $atomic_referenced_type) {
472
                        if (!$atomic_referenced_type instanceof Type\Atomic\TTemplateParam) {
473
                            $extended_types[] = $atomic_referenced_type;
474
                        } else {
475
                            $extended_types[] = $atomic_type;
476
                        }
477
                    }
478
                } else {
479
                    $extended_types[] = $atomic_type;
480
                }
481
            } else {
482
                $extended_types[] = $atomic_type;
483
            }
484
        }
485
486
        return new Type\Union($extended_types);
487
    }
488
489
    /**
490
     * @return void
491
     */
492
    private function populateDataFromParentClass(
493
        ClassLikeStorage $storage,
494
        ClassLikeStorageProvider $storage_provider,
495
        array $dependent_classlikes
496
    ) {
497
        $parent_storage_class = reset($storage->parent_classes);
498
499
        $parent_storage_class = strtolower(
500
            $this->classlikes->getUnAliasedName(
501
                $parent_storage_class
502
            )
503
        );
504
505
        try {
506
            $parent_storage = $storage_provider->get($parent_storage_class);
507
        } catch (\InvalidArgumentException $e) {
508
            $this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n");
509
510
            $storage->invalid_dependencies[] = $parent_storage_class;
511
512
            $this->invalid_class_storages[strtolower($parent_storage_class)][] = $storage;
513
514
            return;
515
        }
516
517
        $this->populateClassLikeStorage($parent_storage, $dependent_classlikes);
518
519
        $storage->parent_classes = array_merge($storage->parent_classes, $parent_storage->parent_classes);
520
521
        if ($parent_storage->template_types) {
522 View Code Duplication
            if (isset($storage->template_type_extends[$parent_storage->name])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
523
                foreach ($storage->template_type_extends[$parent_storage->name] as $i => $type) {
524
                    $parent_template_type_names = array_keys($parent_storage->template_types);
525
526
                    $mapped_name = $parent_template_type_names[$i] ?? null;
527
528
                    if ($mapped_name) {
529
                        $storage->template_type_extends[$parent_storage->name][$mapped_name] = $type;
530
                    }
531
                }
532
533
                if ($parent_storage->template_type_extends) {
534
                    foreach ($parent_storage->template_type_extends as $t_storage_class => $type_map) {
535
                        foreach ($type_map as $i => $type) {
536
                            if (is_int($i)) {
537
                                continue;
538
                            }
539
540
                            $storage->template_type_extends[$t_storage_class][$i] = self::extendType(
541
                                $type,
542
                                $storage
543
                            );
544
                        }
545
                    }
546
                }
547
            } else {
548
                $storage->template_type_extends[$parent_storage->name] = [];
549
550
                foreach ($parent_storage->template_types as $template_name => $template_type_map) {
551
                    foreach ($template_type_map as $template_type) {
552
                        $default_param = clone $template_type[0];
553
                        $default_param->from_docblock = false;
554
                        $storage->template_type_extends[$parent_storage->name][$template_name]
555
                            = $default_param;
556
                    }
557
                }
558
559
                if ($parent_storage->template_type_extends) {
560
                    $storage->template_type_extends = array_merge(
561
                        $storage->template_type_extends,
562
                        $parent_storage->template_type_extends
563
                    );
564
                }
565
            }
566
        } elseif ($parent_storage->template_type_extends) {
567
            $storage->template_type_extends = array_merge(
568
                $storage->template_type_extends ?: [],
569
                $parent_storage->template_type_extends
570
            );
571
        }
572
573
        $this->inheritMethodsFromParent($storage, $parent_storage);
574
        $this->inheritPropertiesFromParent($storage, $parent_storage);
575
576
        $storage->class_implements = array_merge($storage->class_implements, $parent_storage->class_implements);
577
        $storage->invalid_dependencies = array_merge(
578
            $storage->invalid_dependencies,
579
            $parent_storage->invalid_dependencies
580
        );
581
582
        if ($parent_storage->has_visitor_issues) {
583
            $storage->has_visitor_issues = true;
584
        }
585
586
        $storage->public_class_constants = array_merge(
587
            $parent_storage->public_class_constants,
588
            $storage->public_class_constants
589
        );
590
        $storage->protected_class_constants = array_merge(
591
            $parent_storage->protected_class_constants,
592
            $storage->protected_class_constants
593
        );
594
595
        if ($parent_storage->mixin && !$storage->mixin) {
596
            $storage->mixin_declaring_fqcln = $parent_storage->mixin_declaring_fqcln;
597
            $storage->mixin = $parent_storage->mixin;
598
        }
599
600
        foreach ($parent_storage->public_class_constant_nodes as $name => $_) {
601
            $storage->public_class_constants[$name] = Type::getMixed();
602
        }
603
604
        foreach ($parent_storage->protected_class_constant_nodes as $name => $_) {
605
            $storage->protected_class_constants[$name] = Type::getMixed();
606
        }
607
608
        $storage->pseudo_property_get_types += $parent_storage->pseudo_property_get_types;
609
        $storage->pseudo_property_set_types += $parent_storage->pseudo_property_set_types;
610
611
        $parent_storage->dependent_classlikes[strtolower($storage->name)] = true;
612
613
        $storage->pseudo_methods += $parent_storage->pseudo_methods;
614
    }
615
616
    /**
617
     * @return void
618
     */
619
    private function populateInterfaceDataFromParentInterfaces(
620
        ClassLikeStorage $storage,
621
        ClassLikeStorageProvider $storage_provider,
622
        array $dependent_classlikes
623
    ) {
624
        $parent_interfaces = [];
625
626
        foreach ($storage->parent_interfaces as $parent_interface_lc => $_) {
627
            try {
628
                $parent_interface_lc = strtolower(
629
                    $this->classlikes->getUnAliasedName(
630
                        $parent_interface_lc
631
                    )
632
                );
633
                $parent_interface_storage = $storage_provider->get($parent_interface_lc);
634
            } catch (\InvalidArgumentException $e) {
635
                $this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n");
636
637
                $storage->invalid_dependencies[] = $parent_interface_lc;
638
                continue;
639
            }
640
641
            $this->populateClassLikeStorage($parent_interface_storage, $dependent_classlikes);
642
643
            // copy over any constants
644
            $storage->public_class_constants = array_merge(
645
                $parent_interface_storage->public_class_constants,
646
                $storage->public_class_constants
647
            );
648
649
            $storage->invalid_dependencies = array_merge(
650
                $storage->invalid_dependencies,
651
                $parent_interface_storage->invalid_dependencies
652
            );
653
654
            foreach ($parent_interface_storage->public_class_constant_nodes as $name => $node) {
655
                $storage->public_class_constant_nodes[$name] = $node;
656
            }
657
658
            if ($parent_interface_storage->template_types) {
659
                if (isset($storage->template_type_extends[$parent_interface_storage->name])) {
660
                    foreach ($storage->template_type_extends[$parent_interface_storage->name] as $i => $type) {
661
                        $parent_template_type_names = array_keys($parent_interface_storage->template_types);
662
663
                        $mapped_name = $parent_template_type_names[$i] ?? null;
664
665
                        if ($mapped_name) {
666
                            $storage->template_type_extends[$parent_interface_storage->name][$mapped_name] = $type;
667
                        }
668
                    }
669
670
                    if ($parent_interface_storage->template_type_extends) {
671
                        foreach ($parent_interface_storage->template_type_extends as $t_storage_class => $type_map) {
672
                            foreach ($type_map as $i => $type) {
673
                                if (is_int($i)) {
674
                                    continue;
675
                                }
676
677
                                $storage->template_type_extends[$t_storage_class][$i] = self::extendType(
678
                                    $type,
679
                                    $storage
680
                                );
681
                            }
682
                        }
683
                    }
684
                } else {
685
                    $storage->template_type_extends[$parent_interface_storage->name] = [];
686
687
                    foreach ($parent_interface_storage->template_types as $template_name => $template_type_map) {
688
                        foreach ($template_type_map as $template_type) {
689
                            $default_param = clone $template_type[0];
690
                            $default_param->from_docblock = false;
691
                            $storage->template_type_extends[$parent_interface_storage->name][$template_name]
692
                                = $default_param;
693
                        }
694
                    }
695
                }
696
            }
697
698
            $parent_interface_storage->dependent_classlikes[strtolower($storage->name)] = true;
699
700
            $parent_interfaces = array_merge($parent_interfaces, $parent_interface_storage->parent_interfaces);
701
702
            $this->inheritMethodsFromParent($storage, $parent_interface_storage);
703
704
            $storage->pseudo_methods += $parent_interface_storage->pseudo_methods;
705
        }
706
707
        $storage->parent_interfaces = array_merge($parent_interfaces, $storage->parent_interfaces);
708
    }
709
710
    /**
711
     * @return void
712
     */
713
    private function populateDataFromImplementedInterfaces(
714
        ClassLikeStorage $storage,
715
        ClassLikeStorageProvider $storage_provider,
716
        array $dependent_classlikes
717
    ) {
718
        $extra_interfaces = [];
719
720
        foreach ($storage->class_implements as $implemented_interface_lc => $_) {
721
            try {
722
                $implemented_interface_lc = strtolower(
723
                    $this->classlikes->getUnAliasedName(
724
                        $implemented_interface_lc
725
                    )
726
                );
727
                $implemented_interface_storage = $storage_provider->get($implemented_interface_lc);
728
            } catch (\InvalidArgumentException $e) {
729
                $this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n");
730
731
                $storage->invalid_dependencies[] = $implemented_interface_lc;
732
                continue;
733
            }
734
735
            $this->populateClassLikeStorage($implemented_interface_storage, $dependent_classlikes);
736
737
            // copy over any constants
738
            $storage->public_class_constants = array_merge(
739
                $implemented_interface_storage->public_class_constants,
740
                $storage->public_class_constants
741
            );
742
743
            foreach ($implemented_interface_storage->public_class_constant_nodes as $name => $_) {
744
                $storage->public_class_constants[$name] = Type::getMixed();
745
            }
746
747
            $storage->invalid_dependencies = array_merge(
748
                $storage->invalid_dependencies,
749
                $implemented_interface_storage->invalid_dependencies
750
            );
751
752 View Code Duplication
            if ($implemented_interface_storage->template_types) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
753
                if (isset($storage->template_type_extends[$implemented_interface_storage->name])) {
754
                    foreach ($storage->template_type_extends[$implemented_interface_storage->name] as $i => $type) {
755
                        $parent_template_type_names = array_keys($implemented_interface_storage->template_types);
756
757
                        $mapped_name = $parent_template_type_names[$i] ?? null;
758
759
                        if ($mapped_name) {
760
                            $storage->template_type_extends[$implemented_interface_storage->name][$mapped_name] = $type;
761
                        }
762
                    }
763
764
                    if ($implemented_interface_storage->template_type_extends) {
765
                        foreach ($implemented_interface_storage->template_type_extends as $e_i => $type_map) {
766
                            foreach ($type_map as $i => $type) {
767
                                if (is_int($i)) {
768
                                    continue;
769
                                }
770
771
                                $storage->template_type_extends[$e_i][$i] = self::extendType(
772
                                    $type,
773
                                    $storage
774
                                );
775
                            }
776
                        }
777
                    }
778
                } else {
779
                    $storage->template_type_extends[$implemented_interface_storage->name] = [];
780
781
                    foreach ($implemented_interface_storage->template_types as $template_name => $template_type_map) {
782
                        foreach ($template_type_map as $template_type) {
783
                            $default_param = clone $template_type[0];
784
                            $default_param->from_docblock = false;
785
                            $storage->template_type_extends[$implemented_interface_storage->name][$template_name]
786
                                = $default_param;
787
                        }
788
                    }
789
                }
790
            } elseif ($implemented_interface_storage->template_type_extends) {
791
                $storage->template_type_extends = array_merge(
792
                    $storage->template_type_extends ?: [],
793
                    $implemented_interface_storage->template_type_extends
794
                );
795
            }
796
797
            $extra_interfaces = array_merge($extra_interfaces, $implemented_interface_storage->parent_interfaces);
798
        }
799
800
        $storage->class_implements = array_merge($storage->class_implements, $extra_interfaces);
801
802
        $interface_method_implementers = [];
803
804
        foreach ($storage->class_implements as $implemented_interface_lc => $_) {
805
            try {
806
                $implemented_interface = strtolower(
807
                    $this->classlikes->getUnAliasedName(
808
                        $implemented_interface_lc
809
                    )
810
                );
811
                $implemented_interface_storage = $storage_provider->get($implemented_interface);
812
            } catch (\InvalidArgumentException $e) {
813
                continue;
814
            }
815
816
            $implemented_interface_storage->dependent_classlikes[strtolower($storage->name)] = true;
817
818
            foreach ($implemented_interface_storage->methods as $method_name => $method) {
819
                if ($method->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC) {
820
                    $interface_method_implementers[$method_name][] = new \Psalm\Internal\MethodIdentifier(
821
                        $implemented_interface_storage->name,
822
                        $method_name
823
                    );
824
                }
825
            }
826
        }
827
828
        foreach ($interface_method_implementers as $method_name => $interface_method_ids) {
829
            if (count($interface_method_ids) === 1) {
830
                if (isset($storage->methods[$method_name])) {
831
                    $method_storage = $storage->methods[$method_name];
832
833
                    if ($method_storage->signature_return_type
834
                        && !$method_storage->signature_return_type->isVoid()
835
                        && $method_storage->return_type === $method_storage->signature_return_type
836
                    ) {
837
                        $interface_fqcln = $interface_method_ids[0]->fq_class_name;
838
                        $interface_storage = $storage_provider->get($interface_fqcln);
839
840
                        if (isset($interface_storage->methods[$method_name])) {
841
                            $interface_method_storage = $interface_storage->methods[$method_name];
842
843
                            if ($interface_method_storage->throws
844
                                && (!$method_storage->throws || $method_storage->inheritdoc)
845
                            ) {
846
                                $method_storage->throws += $interface_method_storage->throws;
847
                            }
848
849
                            if ($interface_method_storage->return_type
850
                                && $interface_method_storage->signature_return_type
851
                                && $interface_method_storage->return_type
852
                                    !== $interface_method_storage->signature_return_type
853
                                && TypeAnalyzer::isSimplyContainedBy(
854
                                    $interface_method_storage->signature_return_type,
855
                                    $method_storage->signature_return_type
856
                                )
857
                            ) {
858
                                $method_storage->return_type = $interface_method_storage->return_type;
859
                                $method_storage->inherited_return_type = true;
860
                            }
861
                        }
862
                    }
863
                }
864
            }
865
866
            foreach ($interface_method_ids as $interface_method_id) {
867
                $storage->overridden_method_ids[$method_name][$interface_method_id->fq_class_name]
868
                    = $interface_method_id;
869
            }
870
        }
871
    }
872
873
    /**
874
     * @param  FileStorage $storage
875
     * @param  array<string, bool> $dependent_file_paths
876
     *
877
     * @return void
878
     */
879
    private function populateFileStorage(FileStorage $storage, array $dependent_file_paths = [])
880
    {
881
        if ($storage->populated) {
882
            return;
883
        }
884
885
        $file_path_lc = strtolower($storage->file_path);
886
887
        if (isset($dependent_file_paths[$file_path_lc])) {
888
            return;
889
        }
890
891
        $dependent_file_paths[$file_path_lc] = true;
892
893
        $all_required_file_paths = $storage->required_file_paths;
894
895
        foreach ($storage->required_file_paths as $included_file_path => $_) {
896
            try {
897
                $included_file_storage = $this->file_storage_provider->get($included_file_path);
898
            } catch (\InvalidArgumentException $e) {
899
                continue;
900
            }
901
902
            $this->populateFileStorage($included_file_storage, $dependent_file_paths);
903
904
            $all_required_file_paths = $all_required_file_paths + $included_file_storage->required_file_paths;
905
        }
906
907
        foreach ($all_required_file_paths as $included_file_path => $_) {
908
            try {
909
                $included_file_storage = $this->file_storage_provider->get($included_file_path);
910
            } catch (\InvalidArgumentException $e) {
911
                continue;
912
            }
913
914
            $storage->declaring_function_ids = array_merge(
915
                $included_file_storage->declaring_function_ids,
916
                $storage->declaring_function_ids
917
            );
918
919
            $storage->declaring_constants = array_merge(
920
                $included_file_storage->declaring_constants,
921
                $storage->declaring_constants
922
            );
923
        }
924
925
        foreach ($storage->referenced_classlikes as $fq_class_name) {
926
            try {
927
                $classlike_storage = $this->classlike_storage_provider->get($fq_class_name);
928
            } catch (\InvalidArgumentException $e) {
929
                continue;
930
            }
931
932
            if (!$classlike_storage->location) {
933
                continue;
934
            }
935
936
            try {
937
                $included_file_storage = $this->file_storage_provider->get($classlike_storage->location->file_path);
938
            } catch (\InvalidArgumentException $e) {
939
                continue;
940
            }
941
942 View Code Duplication
            foreach ($classlike_storage->used_traits as $used_trait) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
943
                try {
944
                    $trait_storage = $this->classlike_storage_provider->get($used_trait);
945
                } catch (\InvalidArgumentException $e) {
946
                    continue;
947
                }
948
949
                if (!$trait_storage->location) {
950
                    continue;
951
                }
952
953
                try {
954
                    $included_trait_file_storage = $this->file_storage_provider->get(
955
                        $trait_storage->location->file_path
956
                    );
957
                } catch (\InvalidArgumentException $e) {
958
                    continue;
959
                }
960
961
                $storage->declaring_function_ids = array_merge(
962
                    $included_trait_file_storage->declaring_function_ids,
963
                    $storage->declaring_function_ids
964
                );
965
            }
966
967
            $storage->declaring_function_ids = array_merge(
968
                $included_file_storage->declaring_function_ids,
969
                $storage->declaring_function_ids
970
            );
971
        }
972
973
        $storage->required_file_paths = $all_required_file_paths;
974
975
        foreach ($all_required_file_paths as $required_file_path) {
976
            try {
977
                $required_file_storage = $this->file_storage_provider->get($required_file_path);
978
            } catch (\InvalidArgumentException $e) {
979
                continue;
980
            }
981
982
            $required_file_storage->required_by_file_paths += [$file_path_lc => $storage->file_path];
983
        }
984
985 View Code Duplication
        foreach ($storage->required_classes as $required_classlike) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
986
            try {
987
                $classlike_storage = $this->classlike_storage_provider->get($required_classlike);
988
            } catch (\InvalidArgumentException $e) {
989
                continue;
990
            }
991
992
            if (!$classlike_storage->location) {
993
                continue;
994
            }
995
996
            try {
997
                $required_file_storage = $this->file_storage_provider->get($classlike_storage->location->file_path);
998
            } catch (\InvalidArgumentException $e) {
999
                continue;
1000
            }
1001
1002
            $required_file_storage->required_by_file_paths += [$file_path_lc => $storage->file_path];
1003
        }
1004
1005
        $storage->populated = true;
1006
    }
1007
1008
    /**
1009
     * @param  Type\Union $candidate
1010
     * @param  bool       $is_property
1011
     *
1012
     * @return void
1013
     */
1014
    private function convertPhpStormGenericToPsalmGeneric(Type\Union $candidate, $is_property = false)
1015
    {
1016
        $atomic_types = $candidate->getAtomicTypes();
1017
1018
        if (isset($atomic_types['array']) && count($atomic_types) > 1 && !isset($atomic_types['null'])) {
1019
            $iterator_name = null;
1020
            $generic_params = null;
1021
            $iterator_key = null;
1022
1023
            try {
1024
                foreach ($atomic_types as $type_key => $type) {
1025
                    if ($type instanceof Type\Atomic\TIterable
1026
                        || ($type instanceof Type\Atomic\TNamedObject
1027
                            && (!$type->from_docblock || $is_property)
1028
                            && (
1029
                                strtolower($type->value) === 'traversable'
1030
                                || $this->classlikes->interfaceExtends(
1031
                                    $type->value,
1032
                                    'Traversable'
1033
                                )
1034
                                || $this->classlikes->classImplements(
1035
                                    $type->value,
1036
                                    'Traversable'
1037
                                )
1038
                            ))
1039
                    ) {
1040
                        $iterator_name = $type->value;
1041
                        $iterator_key = $type_key;
1042
                    } elseif ($type instanceof Type\Atomic\TArray) {
1043
                        $generic_params = $type->type_params;
1044
                    }
1045
                }
1046
            } catch (\InvalidArgumentException $e) {
1047
                // ignore class-not-found issues
1048
            }
1049
1050
            if ($iterator_name && $iterator_key && $generic_params) {
1051
                if ($iterator_name === 'iterable') {
1052
                    $generic_iterator = new Type\Atomic\TIterable($generic_params);
1053
                } else {
1054
                    if (strtolower($iterator_name) === 'generator') {
1055
                        $generic_params[] = Type::getMixed();
1056
                        $generic_params[] = Type::getMixed();
1057
                    }
1058
                    $generic_iterator = new Type\Atomic\TGenericObject($iterator_name, $generic_params);
1059
                }
1060
1061
                $candidate->removeType('array');
1062
                $candidate->removeType($iterator_key);
1063
                $candidate->addType($generic_iterator);
1064
            }
1065
        }
1066
    }
1067
1068
    /**
1069
     * @param ClassLikeStorage $storage
1070
     * @param ClassLikeStorage $parent_storage
1071
     *
1072
     * @return void
1073
     */
1074
    protected function inheritMethodsFromParent(
1075
        ClassLikeStorage $storage,
1076
        ClassLikeStorage $parent_storage
1077
    ) {
1078
        $fq_class_name = $storage->name;
1079
        $fq_class_name_lc = strtolower($fq_class_name);
1080
1081
        if ($parent_storage->sealed_methods) {
1082
            $storage->sealed_methods = true;
1083
        }
1084
1085
        // register where they appear (can never be in a trait)
1086
        foreach ($parent_storage->appearing_method_ids as $method_name_lc => $appearing_method_id) {
1087
            $aliased_method_names = [$method_name_lc];
1088
1089 View Code Duplication
            if ($parent_storage->is_trait
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1090
                && $storage->trait_alias_map
1091
            ) {
1092
                $aliased_method_names = array_merge(
1093
                    $aliased_method_names,
1094
                    array_keys($storage->trait_alias_map, $method_name_lc, true)
1095
                );
1096
            }
1097
1098
            foreach ($aliased_method_names as $aliased_method_name) {
1099
                if (isset($storage->appearing_method_ids[$aliased_method_name])) {
1100
                    continue;
1101
                }
1102
1103
                $implemented_method_id = new \Psalm\Internal\MethodIdentifier(
1104
                    $fq_class_name,
1105
                    $aliased_method_name
1106
                );
1107
1108
                $storage->appearing_method_ids[$aliased_method_name] =
1109
                    $parent_storage->is_trait ? $implemented_method_id : $appearing_method_id;
1110
1111
                $this_method_id = $fq_class_name_lc . '::' . $method_name_lc;
1112
1113
                if (isset($storage->methods[$aliased_method_name])) {
1114
                    $storage->potential_declaring_method_ids[$aliased_method_name] = [$this_method_id => true];
1115
                } else {
1116
                    if (isset($parent_storage->potential_declaring_method_ids[$aliased_method_name])) {
1117
                        $storage->potential_declaring_method_ids[$aliased_method_name]
1118
                            = $parent_storage->potential_declaring_method_ids[$aliased_method_name];
1119
                    }
1120
1121
                    $storage->potential_declaring_method_ids[$aliased_method_name][$this_method_id] = true;
1122
1123
                    $parent_method_id = strtolower($parent_storage->name) . '::' . $method_name_lc;
1124
                    $storage->potential_declaring_method_ids[$aliased_method_name][$parent_method_id] = true;
1125
                }
1126
            }
1127
        }
1128
1129
        // register where they're declared
1130
        foreach ($parent_storage->inheritable_method_ids as $method_name_lc => $declaring_method_id) {
1131
            if ($method_name_lc !== '__construct') {
1132
                if ($parent_storage->is_trait) {
1133
                    $declaring_class = $declaring_method_id->fq_class_name;
1134
                    $declaring_class_storage = $this->classlike_storage_provider->get($declaring_class);
1135
1136
                    if (isset($declaring_class_storage->methods[$method_name_lc])
1137
                        && $declaring_class_storage->methods[$method_name_lc]->abstract
1138
                    ) {
1139
                        $storage->overridden_method_ids[$method_name_lc][$declaring_method_id->fq_class_name]
1140
                            = $declaring_method_id;
1141
                    }
1142
                } else {
1143
                    $storage->overridden_method_ids[$method_name_lc][$declaring_method_id->fq_class_name]
1144
                        = $declaring_method_id;
1145
                }
1146
1147
                if (isset($parent_storage->overridden_method_ids[$method_name_lc])
1148
                    && isset($storage->overridden_method_ids[$method_name_lc])
1149
                ) {
1150
                    $storage->overridden_method_ids[$method_name_lc]
1151
                        += $parent_storage->overridden_method_ids[$method_name_lc];
1152
                }
1153
            }
1154
1155
            $aliased_method_names = [$method_name_lc];
1156
1157 View Code Duplication
            if ($parent_storage->is_trait
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1158
                && $storage->trait_alias_map
1159
            ) {
1160
                $aliased_method_names = array_merge(
1161
                    $aliased_method_names,
1162
                    array_keys($storage->trait_alias_map, $method_name_lc, true)
1163
                );
1164
            }
1165
1166
            foreach ($aliased_method_names as $aliased_method_name) {
1167
                if (isset($storage->declaring_method_ids[$aliased_method_name])) {
1168
                    $implementing_method_id = $storage->declaring_method_ids[$aliased_method_name];
1169
1170
                    $implementing_class_storage = $this->classlike_storage_provider->get(
1171
                        $implementing_method_id->fq_class_name
1172
                    );
1173
1174
                    if (!$implementing_class_storage->methods[$implementing_method_id->method_name]->abstract
1175
                        || !empty($storage->methods[$implementing_method_id->method_name]->abstract)
1176
                    ) {
1177
                        continue;
1178
                    }
1179
                }
1180
1181
                /** @psalm-suppress PropertyTypeCoercion */
1182
                $storage->declaring_method_ids[$aliased_method_name] = $declaring_method_id;
1183
                /** @psalm-suppress PropertyTypeCoercion */
1184
                $storage->inheritable_method_ids[$aliased_method_name] = $declaring_method_id;
1185
            }
1186
        }
1187
    }
1188
1189
    /**
1190
     * @param ClassLikeStorage $storage
1191
     * @param ClassLikeStorage $parent_storage
1192
     *
1193
     * @return void
1194
     */
1195
    private function inheritPropertiesFromParent(
1196
        ClassLikeStorage $storage,
1197
        ClassLikeStorage $parent_storage
1198
    ) {
1199
        if ($parent_storage->sealed_properties) {
1200
            $storage->sealed_properties = true;
1201
        }
1202
1203
        // register where they appear (can never be in a trait)
1204
        foreach ($parent_storage->appearing_property_ids as $property_name => $appearing_property_id) {
1205
            if (isset($storage->appearing_property_ids[$property_name])) {
1206
                continue;
1207
            }
1208
1209
            if (!$parent_storage->is_trait
1210
                && isset($parent_storage->properties[$property_name])
1211
                && $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
1212
            ) {
1213
                continue;
1214
            }
1215
1216
            $implemented_property_id = $storage->name . '::$' . $property_name;
1217
1218
            $storage->appearing_property_ids[$property_name] =
1219
                $parent_storage->is_trait ? $implemented_property_id : $appearing_property_id;
1220
        }
1221
1222
        // register where they're declared
1223 View Code Duplication
        foreach ($parent_storage->declaring_property_ids as $property_name => $declaring_property_class) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1224
            if (isset($storage->declaring_property_ids[$property_name])) {
1225
                continue;
1226
            }
1227
1228
            if (!$parent_storage->is_trait
1229
                && isset($parent_storage->properties[$property_name])
1230
                && $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
1231
            ) {
1232
                continue;
1233
            }
1234
1235
            $storage->declaring_property_ids[$property_name] = $declaring_property_class;
1236
        }
1237
1238
        // register where they're declared
1239
        foreach ($parent_storage->inheritable_property_ids as $property_name => $inheritable_property_id) {
1240
            if (!$parent_storage->is_trait
1241
                && isset($parent_storage->properties[$property_name])
1242
                && $parent_storage->properties[$property_name]->visibility === ClassLikeAnalyzer::VISIBILITY_PRIVATE
1243
            ) {
1244
                continue;
1245
            }
1246
1247
            if (!$parent_storage->is_trait) {
1248
                $storage->overridden_property_ids[$property_name][] = $inheritable_property_id;
1249
            }
1250
1251
            $storage->inheritable_property_ids[$property_name] = $inheritable_property_id;
1252
        }
1253
    }
1254
}
1255