Skip to content

Fix phpstan/phpstan#9844: Calling a static method over non-generic class-string results in ErrorType#5308

Closed
phpstan-bot wants to merge 8 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-k33amud
Closed

Fix phpstan/phpstan#9844: Calling a static method over non-generic class-string results in ErrorType#5308
phpstan-bot wants to merge 8 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-k33amud

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

Calling a static method on a non-generic class-string variable (e.g. $class::foo()) incorrectly resulted in *ERROR* type instead of mixed.

Changes

  • Modified src/Analyser/ExprHandler/StaticCallHandler.php: When methodCallReturnType returns null and the resolved type is a classless object (from plain class-string), return MixedType instead of ErrorType
  • Added regression test tests/PHPStan/Analyser/nsrt/bug-9844.php

Root cause

When resolving static method calls on dynamic class expressions, ClassStringType::getObjectTypeOrClassStringObjectType() returns ObjectWithoutClassType. This type correctly returns TrinaryLogic::createMaybe() for hasMethod(), but MutatingScope::filterTypeWithMethod() requires yes(), causing it to return null. The StaticCallHandler then defaulted to ErrorType.

The fix detects the case where the resolved type is a classless object type (isObject()->yes() with no class names) — which corresponds to a plain class-string — and returns MixedType instead of ErrorType, since any class could have any method returning any type.

Test

Added tests/PHPStan/Analyser/nsrt/bug-9844.php which verifies that $class::foo() where $class: class-string produces mixed type.

Fixes phpstan/phpstan#9844

Copy link
Copy Markdown
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.

fix failling tests

@VincentLanglet
Copy link
Copy Markdown
Contributor

I would not blindy this fix this looking at https://phpstan.org/r/02f65472-153c-496a-ace9-8d3747108f7d

It's ErrorType for both static and non static call, which is consistent
There is currently an error reported for the non static call

So I wonder if it's not better to report an error (in the StaticMethodCallCheck) for the static call.

@staabm
Copy link
Copy Markdown
Contributor

staabm commented Mar 27, 2026

I think we cannot say its a undefined method.. we should say its a maybe undefined method (so the error will depend on rule-level)?

but type-inference wise, I think its not correct to turn this into a ERROR in case we are not sure

@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

API Error: 529 {"type":"error","error":{"type":"overloaded_error","message":"Overloaded. https://docs.claude.com/en/api/errors"},"request_id":"req_011CZTGsTLp3w35p9Y6ix7va"}

@VincentLanglet
Copy link
Copy Markdown
Contributor

I think we cannot say its a undefined method.. we should say its a maybe undefined method (so the error will depend on rule-level)?

but type-inference wise, I think its not corret to turn this into a ERROR in case we are not sure

If you look at https://phpstan.org/r/e4c2ded1-e04e-4d7f-b439-cec1111fabf1

  • $object::method is ErrorType and reported
  • $object->method is ErrorType and reported
  • $classString::method is ErrorType and not reported

If we consider this as MixedType, it should be mixedType for the 3 situation IMHO.
And If we consider this as "maybe undefined", it should be the same for the other situation.

I would just say for consistency it should stay ErrorType and be reported.
Especially because it's easy to fix the error by changing the phpdoc to class-string<Foo>

@staabm
Copy link
Copy Markdown
Contributor

staabm commented Mar 28, 2026

I would just say for consistency it should stay ErrorType and be reported.

the error message Call to an undefined method object::foo(). does not depend on the ErrorType.
with this PR we get still the error but a MixedType.

this PR does not change which errors we report for your given example

Especially because it's easy to fix the error by changing the phpdoc to class-string

I think class-string is a valid type and in some circumstances cannot be narrowed with a generic (or might appear while analysis without a known generic type)

@VincentLanglet
Copy link
Copy Markdown
Contributor

the error message Call to an undefined method object::foo(). does not depend on the ErrorType. with this PR we get still the error but a MixedType.

ErrorType extends MixedType ; so I'm not sure of the gain to change this type into MixedType.
Maybe ErrorType is especially here to avoid reporting tons on extra error on the following lines...

this PR does not change which errors we report for your given example

Yes, but maybe we should. And start reporting $classString::method as a potential error.

I think class-string is a valid type and in some circumstances cannot be narrowed with a generic (or might appear while analysis without a known generic type)

Which one ?
If you can $unknownClassString::method you cannot be sure method exists.
On the other site, if the class string is types class-string<Foo> you'll be sure.

@staabm
Copy link
Copy Markdown
Contributor

staabm commented Mar 28, 2026

Yes, but maybe we should. And start reporting $classString::method as a potential error.

sounds like a independent topic to me

Which one ?
If you can $unknownClassString::method you cannot be sure method exists.
On the other site, if the class string is types class-string<Foo> you'll be sure.

e.g. if you have logic which is typed on object only:

function doFoo(object $o) {
  $class = get_class($o);
}

in such general purpose functions we won't/cannot get something narrower than class-string:

https://phpstan.org/r/a6589c01-37ac-4af7-b5ce-32fdf5d5314c

@staabm staabm force-pushed the create-pull-request/patch-k33amud branch from 95f0dc2 to 9949f00 Compare March 28, 2026 11:32
Copy link
Copy Markdown
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.

fix failling tests

@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

make tests also completed successfully (exit code 0). Everything is green — tests and static analysis all pass. The fix has already been pushed.

Copy link
Copy Markdown
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.

apply similar fixes to fix failling tests (without changing test expectations)

@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Done. Applied the same fix from StaticCallHandler to MethodCallHandler: when methodCallReturnType returns null and the resolved type is a classless object (from plain class-string), return MixedType instead of ErrorType. This fixes the two failing assertions on lines 16-17 of bug-9844.php ($class->foo() and $class?->foo()).

All tests pass (11674 tests, 0 failures) and make phpstan reports no errors.

@staabm
Copy link
Copy Markdown
Contributor

staabm commented Mar 28, 2026

ah nice, I think the new error in phpstan-latte is valid

staabm and others added 8 commits March 28, 2026 13:12
- Static method calls on non-generic class-string (e.g. $class::foo() where $class is class-string) now return mixed instead of *ERROR*
- The root cause was that ClassStringType::getObjectTypeOrClassStringObjectType() returns ObjectWithoutClassType, which has hasMethod() returning maybe, causing filterTypeWithMethod() to return null
- Added check in StaticCallHandler: when the resolved type is a classless object type (from class-string), return MixedType instead of ErrorType
- New regression test in tests/PHPStan/Analyser/nsrt/bug-9844.php
Instance method calls ($class->foo()) on a class-string variable
correctly return ErrorType since strings don't have instance methods.
Only static calls ($class::foo()) should return mixed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Type

Apply the same fix from StaticCallHandler to MethodCallHandler: when
methodCallReturnType returns null and the resolved type is a classless
object (from plain class-string), return MixedType instead of ErrorType.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm staabm force-pushed the create-pull-request/patch-k33amud branch from cc6b913 to 65815bd Compare March 28, 2026 12:12
@staabm staabm deleted the create-pull-request/patch-k33amud branch March 30, 2026 13:33
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.

3 participants