StaticCallAnalyzer   F
last analyzed

Complexity

Total Complexity 277

Size/Duplication

Total Lines 1468
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 62

Importance

Changes 0
Metric Value
dl 0
loc 1468
rs 0.8
c 0
b 0
f 0
wmc 277
lcom 1
cbo 62

3 Methods

Rating   Name   Duplication   Size   Complexity  
F analyze() 0 1302 258
C taintReturnType() 0 54 11
C checkPseudoMethod() 0 103 8

How to fix   Complexity   

Complex Class

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

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

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

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