StaticCallAnalyzer::checkPseudoMethod()   C
last analyzed

Complexity

Conditions 8
Paths 27

Size

Total Lines 103

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 27
nop 8
dl 0
loc 103
rs 6.7555
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
namespace Psalm\Internal\Analyzer\Statements\Expression\Call;
3
4
use PhpParser;
5
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
6
use Psalm\Internal\Analyzer\MethodAnalyzer;
7
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
8
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
9
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\InstancePropertyFetchAnalyzer;
10
use Psalm\Internal\Analyzer\StatementsAnalyzer;
11
use Psalm\CodeLocation;
12
use Psalm\Context;
13
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
14
use Psalm\Internal\MethodIdentifier;
15
use Psalm\Issue\AbstractMethodCall;
16
use Psalm\Issue\DeprecatedClass;
17
use Psalm\Issue\ImpureMethodCall;
18
use Psalm\Issue\InvalidStringClass;
19
use Psalm\Issue\InternalClass;
20
use Psalm\Issue\MixedMethodCall;
21
use Psalm\Issue\NonStaticSelfCall;
22
use Psalm\Issue\ParentNotFound;
23
use Psalm\Issue\UndefinedClass;
24
use Psalm\Issue\UndefinedMethod;
25
use Psalm\IssueBuffer;
26
use Psalm\Storage\Assertion;
27
use Psalm\Type;
28
use Psalm\Type\Atomic\TNamedObject;
29
use function count;
30
use function in_array;
31
use function strtolower;
32
use function array_map;
33
use function explode;
34
use function strpos;
35
use function is_string;
36
use function strlen;
37
use function substr;
38
use Psalm\Internal\Taint\Source;
39
use Psalm\Internal\Taint\TaintNode;
40
41
/**
42
 * @internal
43
 */
44
class StaticCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer
45
{
46
    public static function analyze(
47
        StatementsAnalyzer $statements_analyzer,
48
        PhpParser\Node\Expr\StaticCall $stmt,
49
        Context $context
50
    ) : bool {
51
        $method_id = null;
52
        $cased_method_id = null;
0 ignored issues
show
Unused Code introduced by
$cased_method_id 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...
53
54
        $lhs_type = null;
55
56
        $file_analyzer = $statements_analyzer->getFileAnalyzer();
57
        $codebase = $statements_analyzer->getCodebase();
58
        $source = $statements_analyzer->getSource();
59
60
        $stmt_type = null;
61
62
        $config = $codebase->config;
63
64
        if ($stmt->class instanceof PhpParser\Node\Name) {
65
            $fq_class_name = null;
66
67
            if (count($stmt->class->parts) === 1
68
                && in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)
69
            ) {
70
                if ($stmt->class->parts[0] === 'parent') {
71
                    $child_fq_class_name = $context->self;
72
73
                    $class_storage = $child_fq_class_name
74
                        ? $codebase->classlike_storage_provider->get($child_fq_class_name)
75
                        : null;
76
77
                    if (!$class_storage || !$class_storage->parent_class) {
78
                        if (IssueBuffer::accepts(
79
                            new ParentNotFound(
80
                                'Cannot call method on parent as this class does not extend another',
81
                                new CodeLocation($statements_analyzer->getSource(), $stmt)
82
                            ),
83
                            $statements_analyzer->getSuppressedIssues()
84
                        )) {
85
                            return false;
86
                        }
87
88
                        return true;
89
                    }
90
91
                    $fq_class_name = $class_storage->parent_class;
92
93
                    $class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
94
95
                    $fq_class_name = $class_storage->name;
96
                } elseif ($context->self) {
97
                    if ($stmt->class->parts[0] === 'static' && isset($context->vars_in_scope['$this'])) {
98
                        $fq_class_name = (string) $context->vars_in_scope['$this'];
99
                        $lhs_type = clone $context->vars_in_scope['$this'];
100
                    } else {
101
                        $fq_class_name = $context->self;
102
                    }
103
                } else {
104
                    if (IssueBuffer::accepts(
105
                        new NonStaticSelfCall(
106
                            'Cannot use ' . $stmt->class->parts[0] . ' outside class context',
107
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
108
                        ),
109
                        $statements_analyzer->getSuppressedIssues()
110
                    )) {
111
                        return false;
112
                    }
113
114
                    return true;
115
                }
116
117
                if ($context->isPhantomClass($fq_class_name)) {
118
                    return true;
119
                }
120
            } elseif ($context->check_classes) {
121
                $aliases = $statements_analyzer->getAliases();
122
123
                if ($context->calling_method_id
124
                    && !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
125
                ) {
126
                    $codebase->file_reference_provider->addMethodReferenceToClassMember(
127
                        $context->calling_method_id,
128
                        'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
129
                    );
130
                }
131
132
                $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
133
                    $stmt->class,
134
                    $aliases
135
                );
136
137
                if ($context->isPhantomClass($fq_class_name)) {
138
                    return true;
139
                }
140
141
                $does_class_exist = false;
142
143
                if ($context->self) {
144
                    $self_storage = $codebase->classlike_storage_provider->get($context->self);
145
146
                    if (isset($self_storage->used_traits[strtolower($fq_class_name)])) {
147
                        $fq_class_name = $context->self;
148
                        $does_class_exist = true;
149
                    }
150
                }
151
152
                if (!isset($context->phantom_classes[strtolower($fq_class_name)])
153
                    && !$does_class_exist
154
                ) {
155
                    $does_class_exist = ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
156
                        $statements_analyzer,
157
                        $fq_class_name,
158
                        new CodeLocation($source, $stmt->class),
159
                        !$context->collect_initializations
160
                            && !$context->collect_mutations
161
                            ? $context->self
162
                            : null,
163
                        !$context->collect_initializations
164
                            && !$context->collect_mutations
165
                            ? $context->calling_method_id
166
                            : null,
167
                        $statements_analyzer->getSuppressedIssues(),
168
                        false,
169
                        false,
170
                        false
171
                    );
172
                }
173
174
                if (!$does_class_exist) {
175
                    return $does_class_exist !== false;
176
                }
177
            }
178
179
            if ($codebase->store_node_types
180
                && $fq_class_name
181
                && !$context->collect_initializations
182
                && !$context->collect_mutations
183
            ) {
184
                $codebase->analyzer->addNodeReference(
185
                    $statements_analyzer->getFilePath(),
186
                    $stmt->class,
187
                    $fq_class_name
188
                );
189
            }
190
191
            if ($fq_class_name && !$lhs_type) {
192
                $lhs_type = new Type\Union([new TNamedObject($fq_class_name)]);
193
            }
194
        } else {
195
            ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context);
196
            $lhs_type = $statements_analyzer->node_data->getType($stmt->class) ?: Type::getMixed();
197
        }
198
199
        if (!$lhs_type) {
200
            if (ArgumentsAnalyzer::analyze(
201
                $statements_analyzer,
202
                $stmt->args,
203
                null,
204
                null,
205
                $context
206
            ) === false) {
207
                return false;
208
            }
209
210
            return true;
211
        }
212
213
        $has_mock = false;
214
        $moved_call = false;
215
216
        foreach ($lhs_type->getAtomicTypes() as $lhs_type_part) {
217
            $intersection_types = [];
218
219
            if ($lhs_type_part instanceof TNamedObject) {
220
                $fq_class_name = $lhs_type_part->value;
221
222
                if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
223
                    $statements_analyzer,
224
                    $fq_class_name,
225
                    new CodeLocation($source, $stmt->class),
226
                    !$context->collect_initializations
227
                        && !$context->collect_mutations
228
                        ? $context->self
229
                        : null,
230
                    !$context->collect_initializations
231
                        && !$context->collect_mutations
232
                        ? $context->calling_method_id
233
                        : null,
234
                    $statements_analyzer->getSuppressedIssues(),
235
                    $stmt->class instanceof PhpParser\Node\Name
236
                        && count($stmt->class->parts) === 1
237
                        && in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true)
238
                )) {
239
                    return false;
240
                }
241
242
                $intersection_types = $lhs_type_part->extra_types;
243
            } elseif ($lhs_type_part instanceof Type\Atomic\TClassString
244
                && $lhs_type_part->as_type
245
            ) {
246
                $fq_class_name = $lhs_type_part->as_type->value;
247
248
                if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
249
                    $statements_analyzer,
250
                    $fq_class_name,
251
                    new CodeLocation($source, $stmt->class),
252
                    $context->self,
253
                    $context->calling_method_id,
254
                    $statements_analyzer->getSuppressedIssues(),
255
                    false
256
                )) {
257
                    return false;
258
                }
259
260
                $intersection_types = $lhs_type_part->as_type->extra_types;
261
            } elseif ($lhs_type_part instanceof Type\Atomic\GetClassT
262
                && !$lhs_type_part->as_type->hasObject()
263
            ) {
264
                $fq_class_name = 'object';
265
266
                if ($lhs_type_part->as_type->hasObjectType()
267
                    && $lhs_type_part->as_type->isSingle()
268
                ) {
269
                    foreach ($lhs_type_part->as_type->getAtomicTypes() as $typeof_type_atomic) {
270
                        if ($typeof_type_atomic instanceof Type\Atomic\TNamedObject) {
271
                            $fq_class_name = $typeof_type_atomic->value;
272
                        }
273
                    }
274
                }
275
276
                if ($fq_class_name === 'object') {
277
                    continue;
278
                }
279
            } elseif ($lhs_type_part instanceof Type\Atomic\TLiteralClassString) {
280
                $fq_class_name = $lhs_type_part->value;
281
282
                if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
283
                    $statements_analyzer,
284
                    $fq_class_name,
285
                    new CodeLocation($source, $stmt->class),
286
                    $context->self,
287
                    $context->calling_method_id,
288
                    $statements_analyzer->getSuppressedIssues(),
289
                    false
290
                )) {
291
                    return false;
292
                }
293
            } elseif ($lhs_type_part instanceof Type\Atomic\TTemplateParam
294
                && !$lhs_type_part->as->isMixed()
295
                && !$lhs_type_part->as->hasObject()
296
            ) {
297
                $fq_class_name = null;
298
299
                foreach ($lhs_type_part->as->getAtomicTypes() as $generic_param_type) {
300
                    if (!$generic_param_type instanceof TNamedObject) {
301
                        continue 2;
302
                    }
303
304
                    $fq_class_name = $generic_param_type->value;
305
                    break;
306
                }
307
308
                if (!$fq_class_name) {
309
                    if (IssueBuffer::accepts(
310
                        new UndefinedClass(
311
                            'Type ' . $lhs_type_part->as . ' cannot be called as a class',
312
                            new CodeLocation($statements_analyzer->getSource(), $stmt),
313
                            (string) $lhs_type_part
314
                        ),
315
                        $statements_analyzer->getSuppressedIssues()
316
                    )) {
317
                        // fall through
318
                    }
319
320
                    continue;
321
                }
322
            } else {
323
                if ($lhs_type_part instanceof Type\Atomic\TMixed
324
                    || $lhs_type_part instanceof Type\Atomic\TTemplateParam
325
                    || $lhs_type_part instanceof Type\Atomic\TClassString
326
                ) {
327
                    if ($stmt->name instanceof PhpParser\Node\Identifier) {
328
                        $codebase->analyzer->addMixedMemberName(
329
                            strtolower($stmt->name->name),
330
                            $context->calling_method_id ?: $statements_analyzer->getFileName()
331
                        );
332
                    }
333
334
                    if (IssueBuffer::accepts(
335
                        new MixedMethodCall(
336
                            'Cannot call method on an unknown class',
337
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
338
                        ),
339
                        $statements_analyzer->getSuppressedIssues()
340
                    )) {
341
                        // fall through
342
                    }
343
344
                    continue;
345
                }
346
347
                if ($lhs_type_part instanceof Type\Atomic\TString) {
348
                    if ($config->allow_string_standin_for_class
349
                        && !$lhs_type_part instanceof Type\Atomic\TNumericString
350
                    ) {
351
                        continue;
352
                    }
353
354
                    if (IssueBuffer::accepts(
355
                        new InvalidStringClass(
356
                            'String cannot be used as a class',
357
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
358
                        ),
359
                        $statements_analyzer->getSuppressedIssues()
360
                    )) {
361
                        // fall through
362
                    }
363
364
                    continue;
365
                }
366
367
                if ($lhs_type_part instanceof Type\Atomic\TNull
368
                    && $lhs_type->ignore_nullable_issues
369
                ) {
370
                    continue;
371
                }
372
373
                if (IssueBuffer::accepts(
374
                    new UndefinedClass(
375
                        'Type ' . $lhs_type_part . ' cannot be called as a class',
376
                        new CodeLocation($statements_analyzer->getSource(), $stmt),
377
                        (string) $lhs_type_part
378
                    ),
379
                    $statements_analyzer->getSuppressedIssues()
380
                )) {
381
                    // fall through
382
                }
383
384
                continue;
385
            }
386
387
            $fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name);
388
389
            $is_mock = ExpressionAnalyzer::isMock($fq_class_name);
390
391
            $has_mock = $has_mock || $is_mock;
392
393
            if ($stmt->name instanceof PhpParser\Node\Identifier && !$is_mock) {
394
                $method_name_lc = strtolower($stmt->name->name);
395
                $method_id = new MethodIdentifier($fq_class_name, $method_name_lc);
396
397
                $cased_method_id = $fq_class_name . '::' . $stmt->name->name;
398
399
                if ($codebase->store_node_types
400
                    && !$context->collect_initializations
401
                    && !$context->collect_mutations
402
                ) {
403
                    ArgumentMapPopulator::recordArgumentPositions(
404
                        $statements_analyzer,
405
                        $stmt,
406
                        $codebase,
407
                        (string) $method_id
408
                    );
409
                }
410
411
                $args = $stmt->args;
412
413
                if ($intersection_types
414
                    && !$codebase->methods->methodExists($method_id)
415
                ) {
416
                    foreach ($intersection_types as $intersection_type) {
417
                        if (!$intersection_type instanceof TNamedObject) {
418
                            continue;
419
                        }
420
421
                        $intersection_method_id = new MethodIdentifier(
422
                            $intersection_type->value,
423
                            $method_name_lc
424
                        );
425
426
                        if ($codebase->methods->methodExists($intersection_method_id)) {
427
                            $method_id = $intersection_method_id;
428
                            $cased_method_id = $intersection_type->value . '::' . $stmt->name->name;
429
                            $fq_class_name = $intersection_type->value;
430
                            break;
431
                        }
432
                    }
433
                }
434
435
                $class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
436
437
                $naive_method_exists = $codebase->methods->methodExists(
438
                    $method_id,
439
                    !$context->collect_initializations
440
                        && !$context->collect_mutations
441
                        ? $context->calling_method_id
442
                        : null,
443
                    $codebase->collect_locations
444
                        ? new CodeLocation($source, $stmt->name)
445
                        : null,
446
                    $statements_analyzer,
447
                    $statements_analyzer->getFilePath()
448
                );
449
450
                if (!$naive_method_exists
451
                    && $class_storage->mixin_declaring_fqcln
452
                    && $class_storage->mixin instanceof Type\Atomic\TNamedObject
453
                ) {
454
                    $new_method_id = new MethodIdentifier(
455
                        $class_storage->mixin->value,
456
                        $method_name_lc
457
                    );
458
459
                    if ($codebase->methods->methodExists(
460
                        $new_method_id,
461
                        $context->calling_method_id,
462
                        $codebase->collect_locations
463
                            ? new CodeLocation($source, $stmt->name)
464
                            : null,
465
                        !$context->collect_initializations
466
                            && !$context->collect_mutations
467
                            ? $statements_analyzer
468
                            : null,
469
                        $statements_analyzer->getFilePath()
470
                    )) {
471
                        $mixin_candidate_type = new Type\Union([clone $class_storage->mixin]);
472
473
                        if ($class_storage->mixin instanceof Type\Atomic\TGenericObject) {
474
                            $mixin_declaring_class_storage = $codebase->classlike_storage_provider->get(
475
                                $class_storage->mixin_declaring_fqcln
476
                            );
477
478
                            $mixin_candidate_type = InstancePropertyFetchAnalyzer::localizePropertyType(
479
                                $codebase,
480
                                new Type\Union([$lhs_type_part]),
481
                                $class_storage->mixin,
482
                                $class_storage,
483
                                $mixin_declaring_class_storage
484
                            );
485
                        }
486
487
                        $new_lhs_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
488
                            $codebase,
489
                            $mixin_candidate_type,
490
                            $fq_class_name,
491
                            $fq_class_name,
492
                            $class_storage->parent_class,
493
                            true,
494
                            false,
495
                            $class_storage->final
496
                        );
497
498
                        $old_data_provider = $statements_analyzer->node_data;
499
500
                        $statements_analyzer->node_data = clone $statements_analyzer->node_data;
501
502
                        $context->vars_in_scope['$tmp_mixin_var'] = $new_lhs_type;
503
504
                        $fake_method_call_expr = new PhpParser\Node\Expr\MethodCall(
505
                            new PhpParser\Node\Expr\Variable(
506
                                'tmp_mixin_var',
507
                                $stmt->class->getAttributes()
508
                            ),
509
                            $stmt->name,
510
                            $stmt->args,
511
                            $stmt->getAttributes()
512
                        );
513
514
                        if (MethodCallAnalyzer::analyze(
515
                            $statements_analyzer,
516
                            $fake_method_call_expr,
517
                            $context
518
                        ) === false) {
519
                            return false;
520
                        }
521
522
                        $fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);
523
524
                        $statements_analyzer->node_data = $old_data_provider;
525
526
                        $statements_analyzer->node_data->setType($stmt, $fake_method_call_type ?: Type::getMixed());
527
528
                        return true;
529
                    }
530
                }
531
532
                if (!$naive_method_exists
533
                    || !MethodAnalyzer::isMethodVisible(
534
                        $method_id,
535
                        $context,
536
                        $statements_analyzer->getSource()
537
                    )
538
                    || (isset($class_storage->pseudo_static_methods[$method_name_lc])
539
                        && ($config->use_phpdoc_method_without_magic_or_parent || $class_storage->parent_class))
540
                ) {
541
                    $callstatic_id = new MethodIdentifier(
542
                        $fq_class_name,
543
                        '__callstatic'
544
                    );
545
                    if ($codebase->methods->methodExists(
546
                        $callstatic_id,
547
                        $context->calling_method_id,
548
                        $codebase->collect_locations
549
                            ? new CodeLocation($source, $stmt->name)
550
                            : null,
551
                        !$context->collect_initializations
552
                            && !$context->collect_mutations
553
                            ? $statements_analyzer
554
                            : null,
555
                        $statements_analyzer->getFilePath()
556
                    )) {
557
                        if (isset($class_storage->pseudo_static_methods[$method_name_lc])) {
558
                            $pseudo_method_storage = $class_storage->pseudo_static_methods[$method_name_lc];
559
560
                            if (self::checkPseudoMethod(
561
                                $statements_analyzer,
562
                                $stmt,
563
                                $method_id,
564
                                $fq_class_name,
565
                                $args,
566
                                $class_storage,
567
                                $pseudo_method_storage,
568
                                $context
569
                            ) === false
570
                            ) {
571
                                return false;
572
                            }
573
574
                            if ($pseudo_method_storage->return_type) {
575
                                return true;
576
                            }
577
                        } else {
578
                            if (ArgumentsAnalyzer::analyze(
579
                                $statements_analyzer,
580
                                $args,
581
                                null,
582
                                null,
583
                                $context
584
                            ) === false) {
585
                                return false;
586
                            }
587
                        }
588
589
                        $array_values = array_map(
590
                            /**
591
                             * @return PhpParser\Node\Expr\ArrayItem
592
                             */
593
                            function (PhpParser\Node\Arg $arg) {
594
                                return new PhpParser\Node\Expr\ArrayItem($arg->value);
595
                            },
596
                            $args
597
                        );
598
599
                        $args = [
600
                            new PhpParser\Node\Arg(new PhpParser\Node\Scalar\String_((string) $method_id)),
601
                            new PhpParser\Node\Arg(new PhpParser\Node\Expr\Array_($array_values)),
602
                        ];
603
604
                        $method_id = new MethodIdentifier(
605
                            $fq_class_name,
606
                            '__callstatic'
607
                        );
608
                    } elseif (isset($class_storage->pseudo_static_methods[$method_name_lc])
609
                        && ($config->use_phpdoc_method_without_magic_or_parent || $class_storage->parent_class)
610
                    ) {
611
                        $pseudo_method_storage = $class_storage->pseudo_static_methods[$method_name_lc];
612
613
                        if (self::checkPseudoMethod(
614
                            $statements_analyzer,
615
                            $stmt,
616
                            $method_id,
617
                            $fq_class_name,
618
                            $args,
619
                            $class_storage,
620
                            $pseudo_method_storage,
621
                            $context
622
                        ) === false
623
                        ) {
624
                            return false;
625
                        }
626
627
                        if ($pseudo_method_storage->return_type) {
628
                            return true;
629
                        }
630
                    }
631
632
                    if (!$context->check_methods) {
633
                        if (ArgumentsAnalyzer::analyze(
634
                            $statements_analyzer,
635
                            $stmt->args,
636
                            null,
637
                            null,
638
                            $context
639
                        ) === false) {
640
                            return false;
641
                        }
642
643
                        return true;
644
                    }
645
                }
646
647
                $does_method_exist = MethodAnalyzer::checkMethodExists(
648
                    $codebase,
649
                    $method_id,
650
                    new CodeLocation($source, $stmt),
651
                    $statements_analyzer->getSuppressedIssues(),
652
                    $context->calling_method_id
653
                );
654
655
                if (!$does_method_exist) {
656
                    if (ArgumentsAnalyzer::analyze(
657
                        $statements_analyzer,
658
                        $stmt->args,
659
                        null,
660
                        null,
661
                        $context
662
                    ) === false) {
663
                        return false;
664
                    }
665
666
                    if ($codebase->alter_code && $fq_class_name && !$moved_call) {
667
                        $codebase->classlikes->handleClassLikeReferenceInMigration(
668
                            $codebase,
669
                            $statements_analyzer,
670
                            $stmt->class,
671
                            $fq_class_name,
672
                            $context->calling_method_id
673
                        );
674
                    }
675
676
                    return true;
677
                }
678
679
                $class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
680
681
                if ($class_storage->user_defined
682
                    && $context->self
683
                    && ($context->collect_mutations || $context->collect_initializations)
684
                ) {
685
                    $appearing_method_id = $codebase->methods->getAppearingMethodId($method_id);
686
687
                    if (!$appearing_method_id) {
688
                        if (IssueBuffer::accepts(
689
                            new UndefinedMethod(
690
                                'Method ' . $method_id . ' does not exist',
691
                                new CodeLocation($statements_analyzer->getSource(), $stmt),
692
                                (string) $method_id
693
                            ),
694
                            $statements_analyzer->getSuppressedIssues()
695
                        )) {
696
                            //
697
                        }
698
699
                        return true;
700
                    }
701
702
                    $appearing_method_class_name = $appearing_method_id->fq_class_name;
703
704
                    if ($codebase->classExtends($context->self, $appearing_method_class_name)) {
705
                        $old_context_include_location = $context->include_location;
706
                        $old_self = $context->self;
707
                        $context->include_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
708
                        $context->self = $appearing_method_class_name;
709
710
                        if ($context->collect_mutations) {
711
                            $file_analyzer->getMethodMutations($method_id, $context);
712
                        } else {
713
                            // collecting initializations
714
                            $local_vars_in_scope = [];
715
                            $local_vars_possibly_in_scope = [];
716
717
                            foreach ($context->vars_in_scope as $var => $_) {
718
                                if (strpos($var, '$this->') !== 0 && $var !== '$this') {
719
                                    $local_vars_in_scope[$var] = $context->vars_in_scope[$var];
720
                                }
721
                            }
722
723
                            foreach ($context->vars_possibly_in_scope as $var => $_) {
724
                                if (strpos($var, '$this->') !== 0 && $var !== '$this') {
725
                                    $local_vars_possibly_in_scope[$var] = $context->vars_possibly_in_scope[$var];
726
                                }
727
                            }
728
729
                            if (!isset($context->initialized_methods[(string) $method_id])) {
730
                                if ($context->initialized_methods === null) {
731
                                    $context->initialized_methods = [];
732
                                }
733
734
                                $context->initialized_methods[(string) $method_id] = true;
735
736
                                $file_analyzer->getMethodMutations($method_id, $context);
737
738
                                foreach ($local_vars_in_scope as $var => $type) {
739
                                    $context->vars_in_scope[$var] = $type;
740
                                }
741
742
                                foreach ($local_vars_possibly_in_scope as $var => $type) {
743
                                    $context->vars_possibly_in_scope[$var] = $type;
744
                                }
745
                            }
746
                        }
747
748
                        $context->include_location = $old_context_include_location;
749
                        $context->self = $old_self;
750
                    }
751
                }
752
753
                if ($class_storage->deprecated && $fq_class_name !== $context->self) {
754
                    if (IssueBuffer::accepts(
755
                        new DeprecatedClass(
756
                            $fq_class_name . ' is marked deprecated',
757
                            new CodeLocation($statements_analyzer->getSource(), $stmt),
758
                            $fq_class_name
759
                        ),
760
                        $statements_analyzer->getSuppressedIssues()
761
                    )) {
762
                        // fall through
763
                    }
764
                }
765
766
                if ($class_storage->psalm_internal
767
                    && $context->self
768
                    && ! NamespaceAnalyzer::isWithin($context->self, $class_storage->psalm_internal)
769
                ) {
770
                    if (IssueBuffer::accepts(
771
                        new InternalClass(
772
                            $fq_class_name . ' is marked internal to ' . $class_storage->psalm_internal,
773
                            new CodeLocation($statements_analyzer->getSource(), $stmt),
774
                            $fq_class_name
775
                        ),
776
                        $statements_analyzer->getSuppressedIssues()
777
                    )) {
778
                        // fall through
779
                    }
780
                }
781
782
                if ($class_storage->internal
783
                    && $context->self
784
                    && !$context->collect_initializations
785
                    && !$context->collect_mutations
786
                ) {
787
                    if (! NamespaceAnalyzer::nameSpaceRootsMatch($context->self, $fq_class_name)) {
788
                        if (IssueBuffer::accepts(
789
                            new InternalClass(
790
                                $fq_class_name . ' is marked internal',
791
                                new CodeLocation($statements_analyzer->getSource(), $stmt),
792
                                $fq_class_name
793
                            ),
794
                            $statements_analyzer->getSuppressedIssues()
795
                        )) {
796
                            // fall through
797
                        }
798
                    }
799
                }
800
801
                if (Method\MethodVisibilityAnalyzer::analyze(
802
                    $method_id,
803
                    $context,
804
                    $statements_analyzer->getSource(),
805
                    new CodeLocation($source, $stmt),
806
                    $statements_analyzer->getSuppressedIssues()
807
                ) === false) {
808
                    return false;
809
                }
810
811
                if ((!$stmt->class instanceof PhpParser\Node\Name
812
                        || $stmt->class->parts[0] !== 'parent'
813
                        || $statements_analyzer->isStatic())
814
                    && (
815
                        !$context->self
816
                        || $statements_analyzer->isStatic()
817
                        || !$codebase->classExtends($context->self, $fq_class_name)
818
                    )
819
                ) {
820
                    if (MethodAnalyzer::checkStatic(
821
                        $method_id,
822
                        ($stmt->class instanceof PhpParser\Node\Name
823
                            && strtolower($stmt->class->parts[0]) === 'self')
824
                            || $context->self === $fq_class_name,
825
                        !$statements_analyzer->isStatic(),
826
                        $codebase,
827
                        new CodeLocation($source, $stmt),
828
                        $statements_analyzer->getSuppressedIssues(),
829
                        $is_dynamic_this_method
830
                    ) === false) {
831
                        // fall through
832
                    }
833
834
                    if ($is_dynamic_this_method) {
835
                        $old_data_provider = $statements_analyzer->node_data;
836
837
                        $statements_analyzer->node_data = clone $statements_analyzer->node_data;
838
839
                        $fake_method_call_expr = new PhpParser\Node\Expr\MethodCall(
840
                            new PhpParser\Node\Expr\Variable(
841
                                'this',
842
                                $stmt->class->getAttributes()
843
                            ),
844
                            $stmt->name,
845
                            $stmt->args,
846
                            $stmt->getAttributes()
847
                        );
848
849
                        if (MethodCallAnalyzer::analyze(
850
                            $statements_analyzer,
851
                            $fake_method_call_expr,
852
                            $context
853
                        ) === false) {
854
                            return false;
855
                        }
856
857
                        $fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);
858
859
                        $statements_analyzer->node_data = $old_data_provider;
860
861
                        if ($fake_method_call_type) {
862
                            $statements_analyzer->node_data->setType($stmt, $fake_method_call_type);
863
                        }
864
865
                        return true;
866
                    }
867
                }
868
869
                if (Method\MethodCallProhibitionAnalyzer::analyze(
870
                    $codebase,
871
                    $context,
872
                    $method_id,
873
                    new CodeLocation($statements_analyzer->getSource(), $stmt),
874
                    $statements_analyzer->getSuppressedIssues()
875
                ) === false) {
876
                    // fall through
877
                }
878
879
                $found_generic_params = ClassTemplateParamCollector::collect(
880
                    $codebase,
881
                    $class_storage,
882
                    $class_storage,
883
                    $method_name_lc,
884
                    $lhs_type_part,
885
                    null
886
                );
887
888
                if ($found_generic_params
889
                    && $stmt->class instanceof PhpParser\Node\Name
890
                    && $stmt->class->parts === ['parent']
891
                    && $context->self
892
                    && ($self_class_storage = $codebase->classlike_storage_provider->get($context->self))
893
                    && $self_class_storage->template_type_extends
894
                ) {
895
                    foreach ($self_class_storage->template_type_extends as $template_fq_class_name => $extended_types) {
896
                        foreach ($extended_types as $type_key => $extended_type) {
897
                            if (!is_string($type_key)) {
898
                                continue;
899
                            }
900
901
                            if (isset($found_generic_params[$type_key][$template_fq_class_name])) {
902
                                $found_generic_params[$type_key][$template_fq_class_name][0] = clone $extended_type;
903
                                continue;
904
                            }
905
906
                            foreach ($extended_type->getAtomicTypes() as $t) {
907
                                if ($t instanceof Type\Atomic\TTemplateParam
908
                                    && isset($found_generic_params[$t->param_name][$t->defining_class])
909
                                ) {
910
                                    $found_generic_params[$type_key][$template_fq_class_name] = [
911
                                        $found_generic_params[$t->param_name][$t->defining_class][0]
912
                                    ];
913
                                } else {
914
                                    $found_generic_params[$type_key][$template_fq_class_name] = [
915
                                        clone $extended_type
916
                                    ];
917
                                    break;
918
                                }
919
                            }
920
                        }
921
                    }
922
                }
923
924
                $template_result = new \Psalm\Internal\Type\TemplateResult([], $found_generic_params ?: []);
925
926
                if (self::checkMethodArgs(
927
                    $method_id,
928
                    $args,
929
                    $template_result,
930
                    $context,
931
                    new CodeLocation($statements_analyzer->getSource(), $stmt),
932
                    $statements_analyzer
933
                ) === false) {
934
                    return false;
935
                }
936
937
                $fq_class_name = $stmt->class instanceof PhpParser\Node\Name && $stmt->class->parts === ['parent']
938
                    ? (string) $statements_analyzer->getFQCLN()
939
                    : $fq_class_name;
940
941
                $self_fq_class_name = $fq_class_name;
942
943
                $return_type_candidate = null;
944
945
                if ($codebase->methods->return_type_provider->has($fq_class_name)) {
946
                    $return_type_candidate = $codebase->methods->return_type_provider->getReturnType(
947
                        $statements_analyzer,
948
                        $fq_class_name,
949
                        $stmt->name->name,
950
                        $stmt->args,
951
                        $context,
952
                        new CodeLocation($statements_analyzer->getSource(), $stmt->name)
953
                    );
954
                }
955
956
                $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
957
958
                if (!$return_type_candidate
959
                    && $declaring_method_id
960
                    && (string) $declaring_method_id !== (string) $method_id
961
                ) {
962
                    $declaring_fq_class_name = $declaring_method_id->fq_class_name;
963
                    $declaring_method_name = $declaring_method_id->method_name;
964
965
                    if ($codebase->methods->return_type_provider->has($declaring_fq_class_name)) {
966
                        $return_type_candidate = $codebase->methods->return_type_provider->getReturnType(
967
                            $statements_analyzer,
968
                            $declaring_fq_class_name,
969
                            $declaring_method_name,
970
                            $stmt->args,
971
                            $context,
972
                            new CodeLocation($statements_analyzer->getSource(), $stmt->name),
973
                            null,
974
                            $fq_class_name,
975
                            $stmt->name->name
976
                        );
977
                    }
978
                }
979
980
                if (!$return_type_candidate) {
981
                    $return_type_candidate = $codebase->methods->getMethodReturnType(
982
                        $method_id,
983
                        $self_fq_class_name,
984
                        $statements_analyzer,
985
                        $args
986
                    );
987
988
                    if ($return_type_candidate) {
989
                        $return_type_candidate = clone $return_type_candidate;
990
991
                        if ($template_result->template_types) {
992
                            $bindable_template_types = $return_type_candidate->getTemplateTypes();
993
994
                            foreach ($bindable_template_types as $template_type) {
995
                                if (!isset(
996
                                    $template_result->upper_bounds
997
                                        [$template_type->param_name]
998
                                        [$template_type->defining_class]
999
                                )) {
1000
                                    if ($template_type->param_name === 'TFunctionArgCount') {
1001
                                        $template_result->upper_bounds[$template_type->param_name] = [
1002
                                            'fn-' . strtolower((string) $method_id) => [
1003
                                                Type::getInt(false, count($stmt->args)),
1004
                                                0
1005
                                            ]
1006
                                        ];
1007
                                    } else {
1008
                                        $template_result->upper_bounds[$template_type->param_name] = [
1009
                                            ($template_type->defining_class) => [Type::getEmpty(), 0]
1010
                                        ];
1011
                                    }
1012
                                }
1013
                            }
1014
                        }
1015
1016
                        if ($lhs_type_part instanceof Type\Atomic\TTemplateParam) {
1017
                            $static_type = $lhs_type_part;
1018
                        } elseif ($lhs_type_part instanceof Type\Atomic\TTemplateParamClass) {
1019
                            $static_type = new Type\Atomic\TTemplateParam(
1020
                                $lhs_type_part->param_name,
1021
                                $lhs_type_part->as_type
1022
                                    ? new Type\Union([$lhs_type_part->as_type])
1023
                                    : Type::getObject(),
1024
                                $lhs_type_part->defining_class
1025
                            );
1026
                        } else {
1027
                            $static_type = $fq_class_name;
1028
                        }
1029
1030
                        if ($template_result->upper_bounds) {
1031
                            $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion(
1032
                                $codebase,
1033
                                $return_type_candidate,
1034
                                null,
1035
                                null,
1036
                                null
1037
                            );
1038
1039
                            $return_type_candidate->replaceTemplateTypesWithArgTypes(
1040
                                $template_result,
1041
                                $codebase
1042
                            );
1043
                        }
1044
1045
                        $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion(
1046
                            $codebase,
1047
                            $return_type_candidate,
1048
                            $self_fq_class_name,
1049
                            $static_type,
1050
                            $class_storage->parent_class,
1051
                            true,
1052
                            false,
1053
                            \is_string($static_type)
1054
                                && $static_type !== $context->self
1055
                        );
1056
1057
                        $return_type_location = $codebase->methods->getMethodReturnTypeLocation(
1058
                            $method_id,
1059
                            $secondary_return_type_location
1060
                        );
1061
1062
                        if ($secondary_return_type_location) {
1063
                            $return_type_location = $secondary_return_type_location;
1064
                        }
1065
1066
                        // only check the type locally if it's defined externally
1067
                        if ($return_type_location && !$config->isInProjectDirs($return_type_location->file_path)) {
1068
                            $return_type_candidate->check(
1069
                                $statements_analyzer,
1070
                                new CodeLocation($source, $stmt),
1071
                                $statements_analyzer->getSuppressedIssues(),
1072
                                $context->phantom_classes,
1073
                                true,
1074
                                false,
1075
                                false,
1076
                                $context->calling_method_id
1077
                            );
1078
                        }
1079
                    }
1080
                }
1081
1082
                $method_storage = $codebase->methods->getUserMethodStorage($method_id);
1083
1084
                if ($method_storage) {
1085
                    if ($method_storage->abstract
1086
                        && $stmt->class instanceof PhpParser\Node\Name
1087
                        && (!$context->self
1088
                            || !\Psalm\Internal\Analyzer\TypeAnalyzer::isContainedBy(
1089
                                $codebase,
1090
                                $context->vars_in_scope['$this']
1091
                                    ?? new Type\Union([
1092
                                        new Type\Atomic\TNamedObject($context->self)
1093
                                    ]),
1094
                                new Type\Union([
1095
                                    new Type\Atomic\TNamedObject($method_id->fq_class_name)
1096
                                ])
1097
                            ))
1098
                    ) {
1099
                        if (IssueBuffer::accepts(
1100
                            new AbstractMethodCall(
1101
                                'Cannot call an abstract static method ' . $method_id . ' directly',
1102
                                new CodeLocation($statements_analyzer->getSource(), $stmt)
1103
                            ),
1104
                            $statements_analyzer->getSuppressedIssues()
1105
                        )) {
1106
                            // fall through
1107
                        }
1108
1109
                        return true;
1110
                    }
1111
1112
                    if (!$context->inside_throw) {
1113
                        if ($context->pure && !$method_storage->pure) {
1114
                            if (IssueBuffer::accepts(
1115
                                new ImpureMethodCall(
1116
                                    'Cannot call an impure method from a pure context',
1117
                                    new CodeLocation($source, $stmt->name)
1118
                                ),
1119
                                $statements_analyzer->getSuppressedIssues()
1120
                            )) {
1121
                                // fall through
1122
                            }
1123
                        } elseif ($context->mutation_free && !$method_storage->mutation_free) {
1124
                            if (IssueBuffer::accepts(
1125
                                new ImpureMethodCall(
1126
                                    'Cannot call an possibly-mutating method from a mutation-free context',
1127
                                    new CodeLocation($source, $stmt->name)
1128
                                ),
1129
                                $statements_analyzer->getSuppressedIssues()
1130
                            )) {
1131
                                // fall through
1132
                            }
1133
                        }
1134
                    }
1135
1136
                    $generic_params = $template_result->upper_bounds;
1137
1138
                    if ($method_storage->assertions) {
1139
                        self::applyAssertionsToContext(
1140
                            $stmt->name,
1141
                            null,
1142
                            $method_storage->assertions,
1143
                            $stmt->args,
1144
                            $generic_params,
1145
                            $context,
1146
                            $statements_analyzer
1147
                        );
1148
                    }
1149
1150
                    if ($method_storage->if_true_assertions) {
1151
                        $statements_analyzer->node_data->setIfTrueAssertions(
1152
                            $stmt,
1153
                            array_map(
1154
                                function (Assertion $assertion) use ($generic_params) : Assertion {
1155
                                    return $assertion->getUntemplatedCopy($generic_params, null);
1156
                                },
1157
                                $method_storage->if_true_assertions
1158
                            )
1159
                        );
1160
                    }
1161
1162
                    if ($method_storage->if_false_assertions) {
1163
                        $statements_analyzer->node_data->setIfFalseAssertions(
1164
                            $stmt,
1165
                            array_map(
1166
                                function (Assertion $assertion) use ($generic_params) : Assertion {
1167
                                    return $assertion->getUntemplatedCopy($generic_params, null);
1168
                                },
1169
                                $method_storage->if_false_assertions
1170
                            )
1171
                        );
1172
                    }
1173
                }
1174
1175
                if ($codebase->alter_code) {
1176
                    foreach ($codebase->call_transforms as $original_pattern => $transformation) {
1177
                        if ($declaring_method_id
1178
                            && strtolower((string) $declaring_method_id) . '\((.*\))' === $original_pattern
1179
                        ) {
1180
                            if (strpos($transformation, '($1)') === strlen($transformation) - 4
1181
                                && $stmt->class instanceof PhpParser\Node\Name
1182
                            ) {
1183
                                $new_method_id = substr($transformation, 0, -4);
1184
                                $old_declaring_fq_class_name = $declaring_method_id->fq_class_name;
1185
                                list($new_fq_class_name, $new_method_name) = explode('::', $new_method_id);
1186
1187
                                if ($codebase->classlikes->handleClassLikeReferenceInMigration(
1188
                                    $codebase,
1189
                                    $statements_analyzer,
1190
                                    $stmt->class,
1191
                                    $new_fq_class_name,
1192
                                    $context->calling_method_id,
1193
                                    strtolower($old_declaring_fq_class_name) !== strtolower($new_fq_class_name),
1194
                                    $stmt->class->parts[0] === 'self'
1195
                                )) {
1196
                                    $moved_call = true;
1197
                                }
1198
1199
                                $file_manipulations = [];
1200
1201
                                $file_manipulations[] = new \Psalm\FileManipulation(
1202
                                    (int) $stmt->name->getAttribute('startFilePos'),
1203
                                    (int) $stmt->name->getAttribute('endFilePos') + 1,
1204
                                    $new_method_name
1205
                                );
1206
1207
                                FileManipulationBuffer::add(
1208
                                    $statements_analyzer->getFilePath(),
1209
                                    $file_manipulations
1210
                                );
1211
                            }
1212
                        }
1213
                    }
1214
                }
1215
1216
                if ($config->after_method_checks) {
1217
                    $file_manipulations = [];
1218
1219
                    $appearing_method_id = $codebase->methods->getAppearingMethodId($method_id);
1220
1221
                    if ($appearing_method_id !== null && $declaring_method_id) {
1222
                        foreach ($config->after_method_checks as $plugin_fq_class_name) {
1223
                            $plugin_fq_class_name::afterMethodCallAnalysis(
1224
                                $stmt,
1225
                                (string) $method_id,
1226
                                (string) $appearing_method_id,
1227
                                (string) $declaring_method_id,
1228
                                $context,
1229
                                $source,
1230
                                $codebase,
1231
                                $file_manipulations,
1232
                                $return_type_candidate
1233
                            );
1234
                        }
1235
                    }
1236
1237
                    if ($file_manipulations) {
1238
                        FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
1239
                    }
1240
                }
1241
1242
                $return_type_candidate = $return_type_candidate ?: Type::getMixed();
1243
1244
                self::taintReturnType(
1245
                    $statements_analyzer,
1246
                    $stmt,
1247
                    $method_id,
1248
                    $cased_method_id,
1249
                    $return_type_candidate,
1250
                    $method_storage
1251
                );
1252
1253
                if ($stmt_type = $statements_analyzer->node_data->getType($stmt)) {
1254
                    $statements_analyzer->node_data->setType(
1255
                        $stmt,
1256
                        Type::combineUnionTypes($stmt_type, $return_type_candidate)
1257
                    );
1258
                } else {
1259
                    $statements_analyzer->node_data->setType($stmt, $return_type_candidate);
1260
                }
1261
            } else {
1262
                if ($stmt->name instanceof PhpParser\Node\Expr) {
1263
                    ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context);
1264
                }
1265
1266
                if (!$context->ignore_variable_method) {
1267
                    $codebase->analyzer->addMixedMemberName(
1268
                        strtolower($fq_class_name) . '::',
1269
                        $context->calling_method_id ?: $statements_analyzer->getFileName()
1270
                    );
1271
                }
1272
1273
                if (ArgumentsAnalyzer::analyze(
1274
                    $statements_analyzer,
1275
                    $stmt->args,
1276
                    null,
1277
                    null,
1278
                    $context
1279
                ) === false) {
1280
                    return false;
1281
                }
1282
            }
1283
1284
            if ($codebase->alter_code
1285
                && $fq_class_name
1286
                && !$moved_call
1287
                && $stmt->class instanceof PhpParser\Node\Name
1288
                && !in_array($stmt->class->parts[0], ['parent', 'static'])
1289
            ) {
1290
                $codebase->classlikes->handleClassLikeReferenceInMigration(
1291
                    $codebase,
1292
                    $statements_analyzer,
1293
                    $stmt->class,
1294
                    $fq_class_name,
1295
                    $context->calling_method_id,
1296
                    false,
1297
                    $stmt->class->parts[0] === 'self'
1298
                );
1299
            }
1300
1301
            if ($codebase->store_node_types
1302
                && $method_id
1303
                && !$context->collect_initializations
1304
                && !$context->collect_mutations
1305
            ) {
1306
                $codebase->analyzer->addNodeReference(
1307
                    $statements_analyzer->getFilePath(),
1308
                    $stmt->name,
1309
                    $method_id . '()'
1310
                );
1311
            }
1312
1313
            if ($codebase->store_node_types
1314
                && !$context->collect_initializations
1315
                && !$context->collect_mutations
1316
                && ($stmt_type = $statements_analyzer->node_data->getType($stmt))
1317
            ) {
1318
                $codebase->analyzer->addNodeType(
1319
                    $statements_analyzer->getFilePath(),
1320
                    $stmt->name,
1321
                    $stmt_type->getId(),
1322
                    $stmt
1323
                );
1324
            }
1325
        }
1326
1327
        if ($method_id === null) {
1328
            return self::checkMethodArgs(
1329
                $method_id,
1330
                $stmt->args,
1331
                null,
1332
                $context,
1333
                new CodeLocation($statements_analyzer->getSource(), $stmt),
1334
                $statements_analyzer
1335
            );
1336
        }
1337
1338
        if (!$config->remember_property_assignments_after_call && !$context->collect_initializations) {
1339
            $context->removeAllObjectVars();
1340
        }
1341
1342
        if (!$statements_analyzer->node_data->getType($stmt)) {
1343
            $statements_analyzer->node_data->setType($stmt, Type::getMixed());
1344
        }
1345
1346
        return true;
1347
    }
1348
1349
    private static function taintReturnType(
1350
        StatementsAnalyzer $statements_analyzer,
1351
        PhpParser\Node\Expr\StaticCall $stmt,
1352
        MethodIdentifier $method_id,
1353
        string $cased_method_id,
1354
        Type\Union $return_type_candidate,
1355
        ?\Psalm\Storage\MethodStorage $method_storage
1356
    ) : void {
1357
        $codebase = $statements_analyzer->getCodebase();
1358
1359
        if (!$codebase->taint
1360
            || !$codebase->config->trackTaintsInPath($statements_analyzer->getFilePath())
1361
            || \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
1362
        ) {
1363
            return;
1364
        }
1365
1366
        $code_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
1367
1368
        $method_location = $method_storage
1369
            ? ($method_storage->signature_return_type_location ?: $method_storage->location)
1370
            : null;
1371
1372
        if ($method_storage && $method_storage->specialize_call) {
1373
            $method_source = TaintNode::getForMethodReturn(
1374
                (string) $method_id,
1375
                $cased_method_id,
1376
                $method_location,
1377
                $code_location
1378
            );
1379
        } else {
1380
            $method_source = TaintNode::getForMethodReturn(
1381
                (string) $method_id,
1382
                $cased_method_id,
1383
                $method_location
1384
            );
1385
        }
1386
1387
        $codebase->taint->addTaintNode($method_source);
1388
1389
        $return_type_candidate->parent_nodes = [$method_source];
1390
1391
        if ($method_storage && $method_storage->taint_source_types) {
1392
            $method_node = Source::getForMethodReturn(
1393
                (string) $method_id,
1394
                $cased_method_id,
1395
                $method_storage->signature_return_type_location ?: $method_storage->location
1396
            );
1397
1398
            $method_node->taints = $method_storage->taint_source_types;
1399
1400
            $codebase->taint->addSource($method_node);
1401
        }
1402
    }
1403
1404
    /**
1405
     * @param  array<int, PhpParser\Node\Arg> $args
1406
     * @return false|null
1407
     */
1408
    private static function checkPseudoMethod(
1409
        StatementsAnalyzer $statements_analyzer,
1410
        PhpParser\Node\Expr\StaticCall $stmt,
1411
        MethodIdentifier $method_id,
1412
        string $fq_class_name,
1413
        array $args,
1414
        \Psalm\Storage\ClassLikeStorage $class_storage,
1415
        \Psalm\Storage\MethodStorage $pseudo_method_storage,
1416
        Context $context
1417
    ) {
1418
        if (ArgumentsAnalyzer::analyze(
1419
            $statements_analyzer,
1420
            $args,
1421
            $pseudo_method_storage->params,
1422
            (string) $method_id,
1423
            $context
1424
        ) === false) {
1425
            return false;
1426
        }
1427
1428
        $codebase = $statements_analyzer->getCodebase();
1429
1430
        if (ArgumentsAnalyzer::checkArgumentsMatch(
1431
            $statements_analyzer,
1432
            $args,
1433
            $method_id,
1434
            $pseudo_method_storage->params,
1435
            $pseudo_method_storage,
1436
            null,
1437
            null,
1438
            new CodeLocation($statements_analyzer, $stmt),
1439
            $context
1440
        ) === false) {
1441
            return false;
1442
        }
1443
1444
        $method_storage = null;
1445
1446
        if ($codebase->taint) {
1447
            try {
1448
                $method_storage = $codebase->methods->getStorage($method_id);
1449
1450
                ArgumentsAnalyzer::analyze(
1451
                    $statements_analyzer,
1452
                    $args,
1453
                    $method_storage->params,
1454
                    (string) $method_id,
1455
                    $context
1456
                );
1457
1458
                ArgumentsAnalyzer::checkArgumentsMatch(
1459
                    $statements_analyzer,
1460
                    $args,
1461
                    $method_id,
1462
                    $method_storage->params,
1463
                    $method_storage,
1464
                    null,
1465
                    null,
1466
                    new CodeLocation($statements_analyzer, $stmt),
1467
                    $context
1468
                );
1469
            } catch (\Exception $e) {
1470
                // do nothing
1471
            }
1472
        }
1473
1474
        if ($pseudo_method_storage->return_type) {
1475
            $return_type_candidate = clone $pseudo_method_storage->return_type;
1476
1477
            $return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion(
1478
                $statements_analyzer->getCodebase(),
1479
                $return_type_candidate,
1480
                $fq_class_name,
1481
                $fq_class_name,
1482
                $class_storage->parent_class
1483
            );
1484
1485
            if ($method_storage) {
1486
                self::taintReturnType(
1487
                    $statements_analyzer,
1488
                    $stmt,
1489
                    $method_id,
1490
                    (string) $method_id,
1491
                    $return_type_candidate,
1492
                    $method_storage
1493
                );
1494
            }
1495
1496
            $stmt_type = $statements_analyzer->node_data->getType($stmt);
1497
1498
            if (!$stmt_type) {
1499
                $statements_analyzer->node_data->setType($stmt, $return_type_candidate);
1500
            } else {
1501
                $statements_analyzer->node_data->setType(
1502
                    $stmt,
1503
                    Type::combineUnionTypes(
1504
                        $return_type_candidate,
1505
                        $stmt_type
1506
                    )
1507
                );
1508
            }
1509
        }
1510
    }
1511
}
1512