Context::isSuppressingExceptions()   A
last analyzed

Complexity

Conditions 5
Paths 7

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 7
nop 1
dl 0
loc 21
rs 9.2728
c 0
b 0
f 0
1
<?php
2
namespace Psalm;
3
4
use function array_filter;
5
use function array_keys;
6
use function count;
7
use function in_array;
8
use function json_encode;
9
use function preg_match;
10
use function preg_quote;
11
use function preg_replace;
12
use Psalm\Internal\Analyzer\StatementsAnalyzer;
13
use Psalm\Internal\Clause;
14
use Psalm\Internal\MethodIdentifier;
15
use Psalm\Storage\FunctionLikeStorage;
16
use Psalm\Internal\Type\AssertionReconciler;
17
use Psalm\Type\Union;
18
use function strpos;
19
use function strtolower;
20
use function array_search;
21
use function is_int;
22
23
class Context
24
{
25
    /**
26
     * @var array<string, Type\Union>
27
     */
28
    public $vars_in_scope = [];
29
30
    /**
31
     * @var array<string, bool>
32
     */
33
    public $vars_possibly_in_scope = [];
34
35
    /**
36
     * Whether or not we're inside the conditional of an if/where etc.
37
     *
38
     * This changes whether or not the context is cloned
39
     *
40
     * @var bool
41
     */
42
    public $inside_conditional = false;
43
44
    /**
45
     * Whether or not we're inside a __construct function
46
     *
47
     * @var bool
48
     */
49
    public $inside_constructor = false;
50
51
    /**
52
     * Whether or not we're inside an isset call
53
     *
54
     * Inside isssets Psalm is more lenient about certain things
55
     *
56
     * @var bool
57
     */
58
    public $inside_isset = false;
59
60
    /**
61
     * Whether or not we're inside an unset call, where
62
     * we don't care about possibly undefined variables
63
     *
64
     * @var bool
65
     */
66
    public $inside_unset = false;
67
68
    /**
69
     * Whether or not we're inside an class_exists call, where
70
     * we don't care about possibly undefined classes
71
     *
72
     * @var bool
73
     */
74
    public $inside_class_exists = false;
75
76
    /**
77
     * Whether or not we're inside a function/method call
78
     *
79
     * @var bool
80
     */
81
    public $inside_call = false;
82
83
    /**
84
     * Whether or not we're inside a throw
85
     *
86
     * @var bool
87
     */
88
    public $inside_throw = false;
89
90
    /**
91
     * Whether or not we're inside an assignment
92
     *
93
     * @var bool
94
     */
95
    public $inside_assignment = false;
96
97
    /**
98
     * @var null|CodeLocation
99
     */
100
    public $include_location = null;
101
102
    /**
103
     * @var string|null
104
     */
105
    public $self;
106
107
    /**
108
     * @var string|null
109
     */
110
    public $parent;
111
112
    /**
113
     * @var bool
114
     */
115
    public $check_classes = true;
116
117
    /**
118
     * @var bool
119
     */
120
    public $check_variables = true;
121
122
    /**
123
     * @var bool
124
     */
125
    public $check_methods = true;
126
127
    /**
128
     * @var bool
129
     */
130
    public $check_consts = true;
131
132
    /**
133
     * @var bool
134
     */
135
    public $check_functions = true;
136
137
    /**
138
     * A list of classes checked with class_exists
139
     *
140
     * @var array<string,bool>
141
     */
142
    public $phantom_classes = [];
143
144
    /**
145
     * A list of files checked with file_exists
146
     *
147
     * @var array<string,bool>
148
     */
149
    public $phantom_files = [];
150
151
    /**
152
     * A list of clauses in Conjunctive Normal Form
153
     *
154
     * @var list<Clause>
155
     */
156
    public $clauses = [];
157
158
    /**
159
     * A list of hashed clauses that have already been factored in
160
     *
161
     * @var list<string>
162
     */
163
    public $reconciled_expression_clauses = [];
164
165
    /**
166
     * Whether or not to do a deep analysis and collect mutations to this context
167
     *
168
     * @var bool
169
     */
170
    public $collect_mutations = false;
171
172
    /**
173
     * Whether or not to do a deep analysis and collect initializations from private or final methods
174
     *
175
     * @var bool
176
     */
177
    public $collect_initializations = false;
178
179
    /**
180
     * Whether or not to do a deep analysis and collect initializations from public non-final methods
181
     *
182
     * @var bool
183
     */
184
    public $collect_nonprivate_initializations = false;
185
186
    /**
187
     * Stored to prevent re-analysing methods when checking for initialised properties
188
     *
189
     * @var array<string, bool>|null
190
     */
191
    public $initialized_methods = null;
192
193
    /**
194
     * @var array<string, Type\Union>
195
     */
196
    public $constants = [];
197
198
    /**
199
     * Whether or not to track exceptions
200
     *
201
     * @var bool
202
     */
203
    public $collect_exceptions = false;
204
205
    /**
206
     * A list of variables that have been referenced
207
     *
208
     * @var array<string, bool>
209
     */
210
    public $referenced_var_ids = [];
211
212
    /**
213
     * A list of variables that have never been referenced
214
     *
215
     * @var array<string, array<string, CodeLocation>>
216
     */
217
    public $unreferenced_vars = [];
218
219
    /**
220
     * A list of variables that have been passed by reference (where we know their type)
221
     *
222
     * @var array<string, \Psalm\Internal\ReferenceConstraint>|null
223
     */
224
    public $byref_constraints;
225
226
    /**
227
     * If this context inherits from a context, it is here
228
     *
229
     * @var Context|null
230
     */
231
    public $parent_context;
232
233
    /**
234
     * @var array<string, Type\Union>
235
     */
236
    public $possible_param_types = [];
237
238
    /**
239
     * A list of vars that have been assigned to
240
     *
241
     * @var array<string, bool>
242
     */
243
    public $assigned_var_ids = [];
244
245
    /**
246
     * A list of vars that have been may have been assigned to
247
     *
248
     * @var array<string, bool>
249
     */
250
    public $possibly_assigned_var_ids = [];
251
252
    /**
253
     * A list of classes or interfaces that may have been thrown
254
     *
255
     * @var array<string, array<array-key, CodeLocation>>
256
     */
257
    public $possibly_thrown_exceptions = [];
258
259
    /**
260
     * @var bool
261
     */
262
    public $is_global = false;
263
264
    /**
265
     * @var array<string, bool>
266
     */
267
    public $protected_var_ids = [];
268
269
    /**
270
     * If we've branched from the main scope, a byte offset for where that branch happened
271
     *
272
     * @var int|null
273
     */
274
    public $branch_point;
275
276
    /**
277
     * What does break mean in this context?
278
     *
279
     * 'loop' means we're breaking out of a loop,
280
     * 'switch' means we're breaking out of a switch
281
     *
282
     * @var list<'loop'|'switch'>
283
     */
284
    public $break_types = [];
285
286
    /**
287
     * @var bool
288
     */
289
    public $inside_loop = false;
290
291
    /**
292
     * @var Internal\Scope\LoopScope|null
293
     */
294
    public $loop_scope = null;
295
296
    /**
297
     * @var Internal\Scope\CaseScope|null
298
     */
299
    public $case_scope = null;
300
301
    /**
302
     * @var Context|null
303
     */
304
    public $if_context = null;
305
306
    /**
307
     * @var \Psalm\Internal\Scope\IfScope|null
308
     */
309
    public $if_scope = null;
310
311
    /**
312
     * @var bool
313
     */
314
    public $strict_types = false;
315
316
    /**
317
     * @var string|null
318
     */
319
    public $calling_function_id;
320
321
    /**
322
     * @var lowercase-string|null
323
     */
324
    public $calling_method_id;
325
326
    /**
327
     * @var bool
328
     */
329
    public $inside_negation = false;
330
331
    /**
332
     * @var bool
333
     */
334
    public $ignore_variable_property = false;
335
336
    /**
337
     * @var bool
338
     */
339
    public $ignore_variable_method = false;
340
341
    /**
342
     * @var bool
343
     */
344
    public $pure = false;
345
346
    /**
347
     * @var bool
348
     */
349
    public $mutation_free = false;
350
351
    /**
352
     * @var bool
353
     */
354
    public $external_mutation_free = false;
355
356
    /**
357
     * @var bool
358
     */
359
    public $error_suppressing = false;
360
361
    /**
362
     * @var bool
363
     */
364
    public $has_returned = false;
365
366
    /**
367
     * @param string|null $self
368
     */
369
    public function __construct($self = null)
370
    {
371
        $this->self = $self;
372
    }
373
374
    public function __destruct()
375
    {
376
        $this->case_scope = null;
377
        $this->parent_context = null;
378
    }
379
380
    /**
381
     * @return void
382
     */
383
    public function __clone()
384
    {
385
        foreach ($this->clauses as &$clause) {
386
            $clause = clone $clause;
387
        }
388
389
        foreach ($this->constants as &$constant) {
390
            $constant = clone $constant;
391
        }
392
    }
393
394
    /**
395
     * Updates the parent context, looking at the changes within a block and then applying those changes, where
396
     * necessary, to the parent context
397
     *
398
     * @param  Context     $start_context
399
     * @param  Context     $end_context
400
     * @param  bool        $has_leaving_statements   whether or not the parent scope is abandoned between
401
     *                                               $start_context and $end_context
402
     * @param  array       $vars_to_update
403
     * @param  array<string, bool>  $updated_vars
404
     *
405
     * @return void
406
     */
407
    public function update(
408
        Context $start_context,
409
        Context $end_context,
410
        $has_leaving_statements,
411
        array $vars_to_update,
412
        array &$updated_vars
413
    ) {
414
        foreach ($start_context->vars_in_scope as $var_id => $old_type) {
415
            // this is only true if there was some sort of type negation
416
            if (in_array($var_id, $vars_to_update, true)) {
417
                // if we're leaving, we're effectively deleting the possibility of the if types
418
                $new_type = !$has_leaving_statements && $end_context->hasVariable($var_id)
419
                    ? $end_context->vars_in_scope[$var_id]
420
                    : null;
421
422
                $existing_type = isset($this->vars_in_scope[$var_id]) ? $this->vars_in_scope[$var_id] : null;
423
424
                if (!$existing_type) {
425
                    if ($new_type) {
426
                        $this->vars_in_scope[$var_id] = clone $new_type;
427
                        $updated_vars[$var_id] = true;
428
                    }
429
430
                    continue;
431
                }
432
433
                $existing_type = clone $existing_type;
434
435
                // if the type changed within the block of statements, process the replacement
436
                // also never allow ourselves to remove all types from a union
437
                if ((!$new_type || !$old_type->equals($new_type))
438
                    && ($new_type || count($existing_type->getAtomicTypes()) > 1)
439
                ) {
440
                    $existing_type->substitute($old_type, $new_type);
441
442
                    if ($new_type && $new_type->from_docblock) {
443
                        $existing_type->setFromDocblock();
444
                    }
445
446
                    $updated_vars[$var_id] = true;
447
                }
448
449
                $this->vars_in_scope[$var_id] = $existing_type;
450
            }
451
        }
452
    }
453
454
    /**
455
     * @param  array<string, Type\Union> $new_vars_in_scope
456
     * @param  bool $include_new_vars
457
     *
458
     * @return array<string,Type\Union>
459
     */
460
    public function getRedefinedVars(array $new_vars_in_scope, $include_new_vars = false)
461
    {
462
        $redefined_vars = [];
463
464
        foreach ($this->vars_in_scope as $var_id => $this_type) {
465
            if (!isset($new_vars_in_scope[$var_id])) {
466
                if ($include_new_vars) {
467
                    $redefined_vars[$var_id] = $this_type;
468
                }
469
                continue;
470
            }
471
472
            $new_type = $new_vars_in_scope[$var_id];
473
474
            if (!$this_type->failed_reconciliation
475
                && !$this_type->isEmpty()
476
                && !$new_type->isEmpty()
477
                && !$this_type->equals($new_type)
478
            ) {
479
                $redefined_vars[$var_id] = $this_type;
480
            }
481
        }
482
483
        return $redefined_vars;
484
    }
485
486
    /**
487
     * @param  Context $original_context
488
     * @param  Context $new_context
489
     *
490
     * @return array<int, string>
0 ignored issues
show
Documentation introduced by
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
491
     */
492
    public static function getNewOrUpdatedVarIds(Context $original_context, Context $new_context)
493
    {
494
        $redefined_var_ids = [];
495
496
        foreach ($new_context->vars_in_scope as $var_id => $context_type) {
497
            if (!isset($original_context->vars_in_scope[$var_id])
498
                || !$original_context->vars_in_scope[$var_id]->equals($context_type)
499
            ) {
500
                $redefined_var_ids[] = $var_id;
501
            }
502
        }
503
504
        return $redefined_var_ids;
505
    }
506
507
    /**
508
     * @param  string $remove_var_id
509
     *
510
     * @return void
511
     */
512
    public function remove($remove_var_id)
513
    {
514
        unset(
515
            $this->referenced_var_ids[$remove_var_id],
516
            $this->vars_possibly_in_scope[$remove_var_id]
517
        );
518
519
        if (isset($this->vars_in_scope[$remove_var_id])) {
520
            $existing_type = $this->vars_in_scope[$remove_var_id];
521
            unset($this->vars_in_scope[$remove_var_id]);
522
523
            $this->removeDescendents($remove_var_id, $existing_type);
524
        }
525
    }
526
527
    /**
528
     * @param  Clause[]             $clauses
529
     * @param  array<string, bool>  $changed_var_ids
530
     *
531
     * @return array{0: list<Clause>, list<Clause>}
0 ignored issues
show
Documentation introduced by
The doc-type array{0: could not be parsed: Unknown type name "array{0:" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
532
     */
533
    public static function removeReconciledClauses(array $clauses, array $changed_var_ids)
534
    {
535
        $included_clauses = [];
536
        $rejected_clauses = [];
537
538
        foreach ($clauses as $c) {
539
            if ($c->wedge) {
540
                $included_clauses[] = $c;
541
                continue;
542
            }
543
544
            foreach ($c->possibilities as $key => $_) {
545
                if (isset($changed_var_ids[$key])) {
546
                    $rejected_clauses[] = $c;
547
                    continue 2;
548
                }
549
            }
550
551
            $included_clauses[] = $c;
552
        }
553
554
        return [$included_clauses, $rejected_clauses];
555
    }
556
557
    /**
558
     * @param  string                 $remove_var_id
559
     * @param  Clause[]               $clauses
560
     * @param  Union|null             $new_type
561
     * @param  StatementsAnalyzer|null $statements_analyzer
562
     *
563
     * @return list<Clause>
0 ignored issues
show
Documentation introduced by
The doc-type list<Clause> could not be parsed: Expected "|" or "end of type", but got "<" at position 4. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
564
     */
565
    public static function filterClauses(
566
        $remove_var_id,
567
        array $clauses,
568
        Union $new_type = null,
569
        StatementsAnalyzer $statements_analyzer = null
570
    ) {
571
        $new_type_string = $new_type ? $new_type->getId() : '';
572
573
        $clauses_to_keep = [];
574
575
        foreach ($clauses as $clause) {
576
            \Psalm\Type\Algebra::calculateNegation($clause);
577
578
            $quoted_remove_var_id = preg_quote($remove_var_id, '/');
579
580
            foreach ($clause->possibilities as $var_id => $_) {
581
                if (preg_match('/' . $quoted_remove_var_id . '[\]\[\-]/', $var_id)) {
582
                    break 2;
583
                }
584
            }
585
586
            if (!isset($clause->possibilities[$remove_var_id]) ||
587
                $clause->possibilities[$remove_var_id] === [$new_type_string]
588
            ) {
589
                $clauses_to_keep[] = $clause;
590
            } elseif ($statements_analyzer &&
591
                $new_type &&
592
                !$new_type->hasMixed()
593
            ) {
594
                $type_changed = false;
595
596
                // if the clause contains any possibilities that would be altered
597
                // by the new type
598
                foreach ($clause->possibilities[$remove_var_id] as $type) {
599
                    // if we're negating a type, we generally don't need the clause anymore
600
                    if ($type[0] === '!' && $type !== '!falsy' && $type !== '!empty') {
601
                        $type_changed = true;
602
                        break;
603
                    }
604
605
                    // empty and !empty are not definitive for arrays and scalar types
606
                    if (($type === '!falsy' || $type === 'falsy') &&
607
                        ($new_type->hasArray() || $new_type->hasPossiblyNumericType())
608
                    ) {
609
                        $type_changed = true;
610
                        break;
611
                    }
612
613
                    $result_type = AssertionReconciler::reconcile(
614
                        $type,
615
                        clone $new_type,
616
                        null,
617
                        $statements_analyzer,
618
                        false,
619
                        [],
620
                        null,
621
                        [],
622
                        $failed_reconciliation
623
                    );
624
625
                    if ($result_type->getId() !== $new_type_string) {
626
                        $type_changed = true;
627
                        break;
628
                    }
629
                }
630
631
                if (!$type_changed) {
632
                    $clauses_to_keep[] = $clause;
633
                }
634
            }
635
        }
636
637
        return $clauses_to_keep;
638
    }
639
640
    /**
641
     * @param  string               $remove_var_id
642
     * @param  Union|null           $new_type
643
     * @param  null|StatementsAnalyzer   $statements_analyzer
644
     *
645
     * @return void
646
     */
647
    public function removeVarFromConflictingClauses(
648
        $remove_var_id,
649
        Union $new_type = null,
650
        StatementsAnalyzer $statements_analyzer = null
651
    ) {
652
        $this->clauses = self::filterClauses($remove_var_id, $this->clauses, $new_type, $statements_analyzer);
653
654
        if ($this->parent_context) {
655
            $this->parent_context->removeVarFromConflictingClauses($remove_var_id);
656
        }
657
    }
658
659
    /**
660
     * @param  string                 $remove_var_id
661
     * @param  \Psalm\Type\Union|null $existing_type
662
     * @param  \Psalm\Type\Union|null $new_type
663
     * @param  null|StatementsAnalyzer     $statements_analyzer
664
     *
665
     * @return void
666
     */
667
    public function removeDescendents(
668
        $remove_var_id,
669
        Union $existing_type = null,
670
        Union $new_type = null,
671
        StatementsAnalyzer $statements_analyzer = null
672
    ) {
673
        if (!$existing_type && isset($this->vars_in_scope[$remove_var_id])) {
674
            $existing_type = $this->vars_in_scope[$remove_var_id];
675
        }
676
677
        if (!$existing_type) {
678
            return;
679
        }
680
681
        $this->removeVarFromConflictingClauses(
682
            $remove_var_id,
683
            $existing_type->hasMixed()
684
                || ($new_type && $existing_type->from_docblock !== $new_type->from_docblock)
685
                ? null
686
                : $new_type,
687
            $statements_analyzer
688
        );
689
690
        $vars_to_remove = [];
691
692
        foreach ($this->vars_in_scope as $var_id => $_) {
693
            if (preg_match('/' . preg_quote($remove_var_id, '/') . '[\]\[\-]/', $var_id)) {
694
                $vars_to_remove[] = $var_id;
695
            }
696
        }
697
698
        foreach ($vars_to_remove as $var_id) {
699
            unset($this->vars_in_scope[$var_id]);
700
        }
701
    }
702
703
    /**
704
     * @return void
705
     */
706
    public function removeAllObjectVars()
707
    {
708
        $vars_to_remove = [];
709
710
        foreach ($this->vars_in_scope as $var_id => $_) {
711
            if (strpos($var_id, '->') !== false || strpos($var_id, '::') !== false) {
712
                $vars_to_remove[] = $var_id;
713
            }
714
        }
715
716
        if (!$vars_to_remove) {
717
            return;
718
        }
719
720
        foreach ($vars_to_remove as $var_id) {
721
            unset($this->vars_in_scope[$var_id], $this->vars_possibly_in_scope[$var_id]);
722
        }
723
724
        $clauses_to_keep = [];
725
726
        foreach ($this->clauses as $clause) {
727
            $abandon_clause = false;
728
729
            foreach (array_keys($clause->possibilities) as $key) {
730
                if (strpos($key, '->') !== false || strpos($key, '::') !== false) {
731
                    $abandon_clause = true;
732
                    break;
733
                }
734
            }
735
736
            if (!$abandon_clause) {
737
                $clauses_to_keep[] = $clause;
738
            }
739
        }
740
741
        $this->clauses = $clauses_to_keep;
742
    }
743
744
    /**
745
     * @param   Context $op_context
746
     *
747
     * @return  void
748
     */
749
    public function updateChecks(Context $op_context)
750
    {
751
        $this->check_classes = $this->check_classes && $op_context->check_classes;
752
        $this->check_variables = $this->check_variables && $op_context->check_variables;
753
        $this->check_methods = $this->check_methods && $op_context->check_methods;
754
        $this->check_functions = $this->check_functions && $op_context->check_functions;
755
        $this->check_consts = $this->check_consts && $op_context->check_consts;
756
    }
757
758
    /**
759
     * @param   string $class_name
760
     *
761
     * @return  bool
762
     */
763
    public function isPhantomClass($class_name)
764
    {
765
        return isset($this->phantom_classes[strtolower($class_name)]);
766
    }
767
768
    /**
769
     * @param  string|null  $var_name
770
     *
771
     * @return bool
772
     */
773
    public function hasVariable($var_name, StatementsAnalyzer $statements_analyzer = null)
774
    {
775
        if (!$var_name) {
776
            return false;
777
        }
778
779
        $stripped_var = preg_replace('/(->|\[).*$/', '', $var_name);
780
781
        if ($stripped_var !== '$this' || $var_name !== $stripped_var) {
782
            $this->referenced_var_ids[$var_name] = true;
783
784
            if (!isset($this->vars_possibly_in_scope[$var_name])
785
                && !isset($this->vars_in_scope[$var_name])
786
            ) {
787
                return false;
788
            }
789
790
            if ($statements_analyzer && $statements_analyzer->getCodebase()->find_unused_variables) {
791
                if (isset($this->unreferenced_vars[$var_name])) {
792
                    $statements_analyzer->registerVariableUses($this->unreferenced_vars[$var_name]);
793
                }
794
795
                unset($this->unreferenced_vars[$var_name]);
796
            }
797
        }
798
799
        return isset($this->vars_in_scope[$var_name]);
800
    }
801
802
    public function getScopeSummary() : string
803
    {
804
        $summary = [];
805
        foreach ($this->vars_possibly_in_scope as $k => $_) {
806
            $summary[$k] = true;
807
        }
808
        foreach ($this->vars_in_scope as $k => $v) {
809
            $summary[$k] = $v->getId();
810
        }
811
812
        return json_encode($summary);
813
    }
814
815
    /**
816
     * @return void
817
     */
818
    public function defineGlobals()
819
    {
820
        $globals = [
821
            '$argv' => new Type\Union([
822
                new Type\Atomic\TArray([Type::getInt(), Type::getString()]),
823
            ]),
824
            '$argc' => Type::getInt(),
825
        ];
826
827
        $config = Config::getInstance();
828
829
        foreach ($config->globals as $global_id => $type_string) {
830
            $globals[$global_id] = Type::parseString($type_string);
831
        }
832
833
        foreach ($globals as $global_id => $type) {
834
            $this->vars_in_scope[$global_id] = $type;
835
            $this->vars_possibly_in_scope[$global_id] = true;
836
        }
837
    }
838
839
    /**
840
     * @return void
841
     */
842
    public function mergeExceptions(Context $other_context)
843
    {
844
        foreach ($other_context->possibly_thrown_exceptions as $possibly_thrown_exception => $codelocations) {
845
            foreach ($codelocations as $hash => $codelocation) {
846
                $this->possibly_thrown_exceptions[$possibly_thrown_exception][$hash] = $codelocation;
847
            }
848
        }
849
    }
850
851
    /**
852
     * @return bool
853
     */
854
    public function isSuppressingExceptions(StatementsAnalyzer $statements_analyzer)
855
    {
856
        if (!$this->collect_exceptions) {
857
            return true;
858
        }
859
860
        $issue_type = $this->is_global ? 'UncaughtThrowInGlobalScope' : 'MissingThrowsDocblock';
861
        $suppressed_issues = $statements_analyzer->getSuppressedIssues();
862
        $suppressed_issue_position = array_search($issue_type, $suppressed_issues, true);
863
        if ($suppressed_issue_position !== false) {
864
            if (is_int($suppressed_issue_position)) {
865
                $file = $statements_analyzer->getFileAnalyzer()->getFilePath();
866
                IssueBuffer::addUsedSuppressions([
867
                    $file => [$suppressed_issue_position => true],
868
                ]);
869
            }
870
            return true;
871
        }
872
873
        return false;
874
    }
875
876
    /**
877
     * @return void
878
     */
879
    public function mergeFunctionExceptions(
880
        FunctionLikeStorage $function_storage,
881
        CodeLocation $codelocation
882
    ) {
883
        $hash = $codelocation->getHash();
884
        foreach ($function_storage->throws as $possibly_thrown_exception => $_) {
885
            $this->possibly_thrown_exceptions[$possibly_thrown_exception][$hash] = $codelocation;
886
        }
887
    }
888
}
889