Passed
Pull Request — master (#861)
by SignpostMarv
06:33 queued 02:07
created

Methods::getCachedChecker()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Psalm\Codebase;
3
4
use PhpParser;
5
use Psalm\Checker\MethodChecker;
6
use Psalm\CodeLocation;
7
use Psalm\Provider\ClassLikeStorageProvider;
8
use Psalm\Storage\MethodStorage;
9
use Psalm\Type;
10
11
/**
12
 * @internal
13
 *
14
 * Handles information about class methods
15
 */
16
class Methods
17
{
18
    /**
19
     * @var ClassLikeStorageProvider
20
     */
21
    private $classlike_storage_provider;
22
23
    /**
24
     * @var \Psalm\Config
25
     */
26
    private $config;
27
28
    /**
29
     * @var array<string, MethodChecker>
30
     */
31
    private $method_checkers = [];
32
33
    /**
34
     * @var bool
35
     */
36
    public $collect_references = false;
37
38
    /**
39
     * @param ClassLikeStorageProvider $storage_provider
40
     */
41
    public function __construct(
42
        \Psalm\Config $config,
43
        ClassLikeStorageProvider $storage_provider
44
    ) {
45
        $this->classlike_storage_provider = $storage_provider;
46
        $this->config = $config;
47
    }
48
49
    /**
50
     * Whether or not a given method exists
51
     *
52
     * @param  string       $method_id
53
     * @param  CodeLocation|null $code_location
54
     *
55
     * @return bool
56
     */
57
    public function methodExists(
58
        $method_id,
59
        CodeLocation $code_location = null
60
    ) {
61
        // remove trailing backslash if it exists
62
        $method_id = preg_replace('/^\\\\/', '', $method_id);
63
        list($fq_class_name, $method_name) = explode('::', $method_id);
64
        $method_name = strtolower($method_name);
65
        $method_id = $fq_class_name . '::' . $method_name;
66
67
        $old_method_id = null;
68
69
        $class_storage = $this->classlike_storage_provider->get($fq_class_name);
70
71
        if (isset($class_storage->declaring_method_ids[$method_name])) {
72
            if ($this->collect_references && $code_location) {
73
                $declaring_method_id = $class_storage->declaring_method_ids[$method_name];
74
                list($declaring_method_class, $declaring_method_name) = explode('::', $declaring_method_id);
75
76
                $declaring_class_storage = $this->classlike_storage_provider->get($declaring_method_class);
77
                $declaring_method_storage = $declaring_class_storage->methods[strtolower($declaring_method_name)];
78
                if ($declaring_method_storage->referencing_locations === null) {
79
                    $declaring_method_storage->referencing_locations = [];
80
                }
81
                $declaring_method_storage->referencing_locations[$code_location->file_path][] = $code_location;
82
83
                foreach ($class_storage->class_implements as $fq_interface_name) {
84
                    $interface_storage = $this->classlike_storage_provider->get($fq_interface_name);
85
                    if (isset($interface_storage->methods[$method_name])) {
86
                        $interface_method_storage = $interface_storage->methods[$method_name];
87
                        if (!isset($interface_method_storage->referencing_locations)) {
88
                            $interface_method_storage->referencing_locations = [];
89
                        }
90
                        $interface_method_storage->referencing_locations[$code_location->file_path][] = $code_location;
91
                    }
92
                }
93
94
                if (isset($declaring_class_storage->overridden_method_ids[$declaring_method_name])) {
95
                    $overridden_method_ids = $declaring_class_storage->overridden_method_ids[$declaring_method_name];
96
97
                    foreach ($overridden_method_ids as $overridden_method_id) {
98
                        list($overridden_method_class, $overridden_method_name) = explode('::', $overridden_method_id);
99
100
                        $class_storage = $this->classlike_storage_provider->get($overridden_method_class);
101
                        $method_storage = $class_storage->methods[strtolower($overridden_method_name)];
102
                        if ($method_storage->referencing_locations === null) {
103
                            $method_storage->referencing_locations = [];
104
                        }
105
                        $method_storage->referencing_locations[$code_location->file_path][] = $code_location;
106
                    }
107
                }
108
            }
109
110
            return true;
111
        }
112
113
        if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) {
114
            return true;
115
        }
116
117
        // support checking oldstyle constructors
118
        if ($method_name === '__construct') {
119
            $method_name_parts = explode('\\', $fq_class_name);
120
            $old_constructor_name = array_pop($method_name_parts);
121
            $old_method_id = $fq_class_name . '::' . $old_constructor_name;
122
        }
123
124
        if (!$class_storage->user_defined
125
            && (CallMap::inCallMap($method_id) || ($old_method_id && CallMap::inCallMap($method_id)))
126
        ) {
127
            return true;
128
        }
129
130
        return false;
131
    }
132
133
    /**
134
     * @param  string $method_id
135
     *
136
     * @return array<int, \Psalm\Storage\FunctionLikeParameter>
137
     */
138
    public function getMethodParams($method_id)
139
    {
140
        if ($method_id = $this->getDeclaringMethodId($method_id)) {
141
            $storage = $this->getStorage($method_id);
142
143
            return $storage->params;
144
        }
145
146
        throw new \UnexpectedValueException('Cannot get method params for ' . $method_id);
147
    }
148
149
    /**
150
     * @param  string $method_id
151
     *
152
     * @return bool
153
     */
154
    public function isVariadic($method_id)
155
    {
156
        $method_id = (string) $this->getDeclaringMethodId($method_id);
157
158
        list($fq_class_name, $method_name) = explode('::', $method_id);
159
160
        return $this->classlike_storage_provider->get($fq_class_name)->methods[$method_name]->variadic;
161
    }
162
163
    /**
164
     * @param  string $method_id
165
     * @param  string $self_class
166
     * @param  array<int, PhpParser\Node\Arg>|null $args
167
     *
168
     * @return Type\Union|null
169
     */
170
    public function getMethodReturnType($method_id, &$self_class, array $args = null)
171
    {
172
        if ($this->config->use_phpdoc_methods_without_call) {
173
            list($original_fq_class_name, $original_method_name) = explode('::', $method_id);
174
175
            $original_class_storage = $this->classlike_storage_provider->get($original_fq_class_name);
176
177
            if (isset($original_class_storage->pseudo_methods[strtolower($original_method_name)])) {
178
                return $original_class_storage->pseudo_methods[strtolower($original_method_name)]->return_type;
179
            }
180
        }
181
182
        $declaring_method_id = $this->getDeclaringMethodId($method_id);
183
184
        if (!$declaring_method_id) {
185
            return null;
186
        }
187
188
        $appearing_method_id = $this->getAppearingMethodId($method_id);
189
190
        if (!$appearing_method_id) {
191
            return null;
192
        }
193
194
        list($appearing_fq_class_name, $appearing_method_name) = explode('::', $appearing_method_id);
195
196
        $appearing_fq_class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
197
198
        if (!$appearing_fq_class_storage->user_defined && CallMap::inCallMap($appearing_method_id)) {
199
            if ($appearing_method_id === 'Closure::fromcallable'
200
                && isset($args[0]->value->inferredType)
201
                && $args[0]->value->inferredType->isSingle()
202
            ) {
203
                foreach ($args[0]->value->inferredType->getTypes() as $atomic_type) {
204
                    if ($atomic_type instanceof Type\Atomic\TCallable || $atomic_type instanceof Type\Atomic\Fn) {
205
                        $callable_type = clone $atomic_type;
206
207
                        return new Type\Union([new Type\Atomic\Fn(
208
                            'Closure',
209
                            $callable_type->params,
210
                            $callable_type->return_type
211
                        )]);
212
                    }
213
                }
214
            }
215
            return CallMap::getReturnTypeFromCallMap($appearing_method_id);
216
        }
217
218
        $storage = $this->getStorage($declaring_method_id);
219
220
        if ($storage->return_type) {
221
            $self_class = $appearing_fq_class_name;
222
223
            return clone $storage->return_type;
224
        }
225
226
        $class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
227
228
        if (!isset($class_storage->overridden_method_ids[$appearing_method_name])) {
229
            return null;
230
        }
231
232
        foreach ($class_storage->overridden_method_ids[$appearing_method_name] as $overridden_method_id) {
233
            $overridden_storage = $this->getStorage($overridden_method_id);
234
235
            if ($overridden_storage->return_type) {
236
                if ($overridden_storage->return_type->isNull()) {
237
                    return Type::getVoid();
238
                }
239
240
                list($fq_overridden_class) = explode('::', $overridden_method_id);
241
242
                $overridden_class_storage =
243
                    $this->classlike_storage_provider->get($fq_overridden_class);
244
245
                $overridden_return_type = clone $overridden_storage->return_type;
246
247
                if ($overridden_class_storage->template_types) {
248
                    $generic_types = [];
249
                    $overridden_return_type->replaceTemplateTypesWithStandins(
250
                        $overridden_class_storage->template_types,
251
                        $generic_types
252
                    );
253
                }
254
255
                $self_class = $overridden_class_storage->name;
256
257
                return $overridden_return_type;
258
            }
259
        }
260
261
        return null;
262
    }
263
264
    /**
265
     * @param  string $method_id
266
     *
267
     * @return bool
268
     */
269
    public function getMethodReturnsByRef($method_id)
270
    {
271
        $method_id = $this->getDeclaringMethodId($method_id);
272
273
        if (!$method_id) {
274
            return false;
275
        }
276
277
        list($fq_class_name) = explode('::', $method_id);
278
279
        $fq_class_storage = $this->classlike_storage_provider->get($fq_class_name);
280
281
        if (!$fq_class_storage->user_defined && CallMap::inCallMap($method_id)) {
282
            return false;
283
        }
284
285
        $storage = $this->getStorage($method_id);
286
287
        return $storage->returns_by_ref;
288
    }
289
290
    /**
291
     * @param  string               $method_id
292
     * @param  CodeLocation|null    $defined_location
293
     *
294
     * @return CodeLocation|null
295
     */
296
    public function getMethodReturnTypeLocation(
297
        $method_id,
298
        CodeLocation &$defined_location = null
299
    ) {
300
        $method_id = $this->getDeclaringMethodId($method_id);
301
302
        if ($method_id === null) {
303
            return null;
304
        }
305
306
        $storage = $this->getStorage($method_id);
307
308
        if (!$storage->return_type_location) {
309
            $overridden_method_ids = $this->getOverriddenMethodIds($method_id);
310
311
            foreach ($overridden_method_ids as $overridden_method_id) {
312
                $overridden_storage = $this->getStorage($overridden_method_id);
313
314
                if ($overridden_storage->return_type_location) {
315
                    $defined_location = $overridden_storage->return_type_location;
316
                    break;
317
                }
318
            }
319
        }
320
321
        return $storage->return_type_location;
322
    }
323
324
    /**
325
     * @param  string $method_id
326
     *
327
     * @return array<int, \Psalm\Storage\Assertion>
328
     */
329
    public function getMethodAssertions($method_id)
330
    {
331
        $method_id = $this->getDeclaringMethodId($method_id);
332
333
        if (!$method_id) {
334
            return [];
335
        }
336
337
        list($fq_class_name) = explode('::', $method_id);
338
339
        $fq_class_storage = $this->classlike_storage_provider->get($fq_class_name);
340
341
        if (!$fq_class_storage->user_defined && CallMap::inCallMap($method_id)) {
342
            return [];
343
        }
344
345
        $storage = $this->getStorage($method_id);
346
347
        return $storage->assertions;
348
    }
349
350
    /**
351
     * @param string $method_id
352
     * @param string $declaring_method_id
353
     *
354
     * @return void
355
     */
356
    public function setDeclaringMethodId(
357
        $method_id,
358
        $declaring_method_id
359
    ) {
360
        list($fq_class_name, $method_name) = explode('::', $method_id);
361
362
        $class_storage = $this->classlike_storage_provider->get($fq_class_name);
363
364
        $class_storage->declaring_method_ids[$method_name] = $declaring_method_id;
365
    }
366
367
    /**
368
     * @param string $method_id
369
     * @param string $appearing_method_id
370
     *
371
     * @return void
372
     */
373
    public function setAppearingMethodId(
374
        $method_id,
375
        $appearing_method_id
376
    ) {
377
        list($fq_class_name, $method_name) = explode('::', $method_id);
378
379
        $class_storage = $this->classlike_storage_provider->get($fq_class_name);
380
381
        $class_storage->appearing_method_ids[$method_name] = $appearing_method_id;
382
    }
383
384
    /**
385
     * @param  string $method_id
386
     *
387
     * @return string|null
388
     */
389
    public function getDeclaringMethodId($method_id)
390
    {
391
        $method_id = strtolower($method_id);
392
393
        list($fq_class_name, $method_name) = explode('::', $method_id);
394
395
        $class_storage = $this->classlike_storage_provider->get($fq_class_name);
396
397
        if (isset($class_storage->declaring_method_ids[$method_name])) {
398
            return $class_storage->declaring_method_ids[$method_name];
399
        }
400
401
        if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) {
402
            return $class_storage->overridden_method_ids[$method_name][0];
403
        }
404
    }
405
406
    /**
407
     * Get the class this method appears in (vs is declared in, which could give a trait)
408
     *
409
     * @param  string $method_id
410
     *
411
     * @return string|null
412
     */
413
    public function getAppearingMethodId($method_id)
414
    {
415
        $method_id = strtolower($method_id);
416
417
        list($fq_class_name, $method_name) = explode('::', $method_id);
418
419
        $class_storage = $this->classlike_storage_provider->get($fq_class_name);
420
421
        if (isset($class_storage->appearing_method_ids[$method_name])) {
422
            return $class_storage->appearing_method_ids[$method_name];
423
        }
424
    }
425
426
    /**
427
     * @param  string $method_id
428
     *
429
     * @return array<string>
430
     */
431
    public function getOverriddenMethodIds($method_id)
432
    {
433
        list($fq_class_name, $method_name) = explode('::', $method_id);
434
435
        $class_storage = $this->classlike_storage_provider->get($fq_class_name);
436
437
        if (isset($class_storage->overridden_method_ids[$method_name])) {
438
            return $class_storage->overridden_method_ids[$method_name];
439
        }
440
441
        return [];
442
    }
443
444
    /**
445
     * @param  string $original_method_id
446
     *
447
     * @return string
448
     */
449
    public function getCasedMethodId($original_method_id)
450
    {
451
        $method_id = $this->getDeclaringMethodId($original_method_id);
452
453
        if ($method_id === null) {
454
            throw new \UnexpectedValueException('Cannot get declaring method id for ' . $original_method_id);
455
        }
456
457
        $storage = $this->getStorage($method_id);
458
459
        list($fq_class_name) = explode('::', $method_id);
460
461
        return $fq_class_name . '::' . $storage->cased_name;
462
    }
463
464
    /**
465
     * @param  string $method_id
466
     *
467
     * @return MethodStorage
468
     */
469
    public function getStorage($method_id)
470
    {
471
        list($fq_class_name, $method_name) = explode('::', $method_id);
472
473
        $class_storage = $this->classlike_storage_provider->get($fq_class_name);
474
475
        $method_name_lc = strtolower($method_name);
476
477
        if (!isset($class_storage->methods[$method_name_lc])) {
478
            throw new \UnexpectedValueException('$storage should not be null for ' . $method_id);
479
        }
480
481
        return $class_storage->methods[$method_name_lc];
482
    }
483
484
    /**
485
     * @param  string $method_id
486
     *
487
     * @return MethodChecker|null
488
     */
489
    public function getCachedChecker($method_id)
490
    {
491
        if (isset($this->method_checkers[$method_id])) {
492
            return $this->method_checkers[$method_id];
493
        }
494
495
        return null;
496
    }
497
498
    /**
499
     * @param  string        $method_id
500
     * @param  MethodChecker $checker
501
     *
502
     * @return void
503
     */
504
    public function cacheChecker($method_id, MethodChecker $checker)
505
    {
506
        $this->method_checkers[$method_id] = $checker;
507
    }
508
}
509