Conditions | 258 |
Paths | > 20000 |
Total Lines | 1302 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
1 | <?php |
||
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; |
||
|
|||
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 | |||
1512 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
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.