Skip to content

Fix #9592: Call to an undefined static method after method_exists()#5083

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-rhoos08
Open

Fix #9592: Call to an undefined static method after method_exists()#5083
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-rhoos08

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When method_exists() was called with a string literal class name (e.g., method_exists('ClassName', 'methodName')), PHPStan did not properly narrow the type for subsequent static method calls on that class. This caused false positive "Call to an undefined static method" errors even though the method's existence was verified.

Changes

  • Modified src/Type/Php/MethodExistsTypeSpecifyingExtension.php to also create a ClassConstFetch type narrowing when the first argument is a string literal (not a ClassConstFetch expression like ClassName::class)
  • Added rule regression test in tests/PHPStan/Rules/Methods/data/bug-9592.php covering ternary, early return, and if-block patterns
  • Added NSRT test in tests/PHPStan/Analyser/nsrt/bug-9592.php verifying the ClassConstFetch expression is narrowed with hasMethod()

Root cause

MethodExistsTypeSpecifyingExtension narrowed the type of the first argument expression ($args[0]->value). When the argument was ClassName::class (a ClassConstFetch node), StaticMethodCallCheck could find the narrowing because it looks up $scope->getType(new ClassConstFetch($class, 'class')) which matches the same expression key.

However, when the argument was a string literal like 'ClassName', the narrowing was stored under the string literal's expression key (e.g., 'ClassName'), not under the ClassConstFetch expression key (e.g., \ClassName::class) that StaticMethodCallCheck looks up. The fix bridges this gap by also creating a narrowing for the equivalent ClassConstFetch expression when a string literal is used.

Test

The regression test covers three patterns:

  1. Ternary: method_exists('Class', 'method') ? Class::method() : ''
  2. Early return: if (!method_exists('Class', 'method')) { return; } Class::method();
  3. If block: if (method_exists('Class', 'method')) { Class::method(); }

All should produce no errors (previously all three produced false positive "undefined static method" errors).

Fixes phpstan/phpstan#9592

…alls

- When method_exists() receives a string literal class name (not ClassName::class),
  the type narrowing was only applied to the string expression, not to the equivalent
  ClassConstFetch expression that StaticMethodCallCheck uses
- Added logic in MethodExistsTypeSpecifyingExtension to also narrow the ClassConstFetch
  expression when the first argument is a string literal that is a class-string
- New regression tests in tests/PHPStan/Rules/Methods/data/bug-9592.php and
  tests/PHPStan/Analyser/nsrt/bug-9592.php

Closes phpstan/phpstan#9592
Copy link
Contributor

@staabm staabm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like the wrong fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants