Skip to content

Fix phpstan/phpstan#14467: dynamic throw extensions on union types - #5551

Closed
eyupcanakman wants to merge 1 commit into
phpstan:2.3.xfrom
eyupcanakman:fix/method-throw-union-type
Closed

eyupcanakman wants to merge 1 commit into
phpstan:2.3.xfrom
eyupcanakman:fix/method-throw-union-type

Conversation

@eyupcanakman

@eyupcanakman eyupcanakman commented Apr 26, 2026 •

Copy link
Copy Markdown

Summary

Dynamic throw type extensions only fire on the first union member when the receiver is a union, so dead-catch detection on Foo|DateTime misses throws declared on either side.

Root cause

MethodThrowPointHelper::getThrowPoint passes the UnionTypeMethodReflection straight to each extension's isMethodSupported. The reflection forwards getDeclaringClass() to methods[0], so any extension that branches on the declaring class only sees the first inner method.

Fix

Special-case UnionTypeMethodReflection in the helper. Iterate inner methods, recurse, and union the resulting throw types via TypeCombinator::union. The combined point is explicit if any inner point was; otherwise implicit.

Test

Regression fixture at tests/PHPStan/Rules/Exceptions/data/bug-14467.php. The user-defined class FQN must sort after \DateTime so methods[0] exposes the bug at the rule level.

Closes phpstan/phpstan#14467

@phpstan-bot

Copy link
Copy Markdown
Collaborator

You've opened the pull request against the latest branch 2.2.x. PHPStan 2.2 is not going to be released for months. If your code is relevant on 2.1.x and you want it to be released sooner, please rebase your pull request and change its target to 2.1.x.

@VincentLanglet

Copy link
Copy Markdown
Contributor

I will prefer having the review from @ondrejmirtes on such PR, because I feel like there is maybe a more general fix to introduce.

There is multiple way to handle this, like yours or

  • introducing DynamicMethodThrowTypeExtension::getClass like other extension
  • introducing a ExtendedMethodReflection::getDeclaringClasses (and maybe getDeclaringClass will need to be migrated)

Let's see what he think about it

@VincentLanglet VincentLanglet self-assigned this Sep 20, 2026
@ondrejmirtes
ondrejmirtes force-pushed the fix/method-throw-union-type branch from 8ce904a to c094914 Compare October 31, 2026 13:30
@ondrejmirtes
ondrejmirtes changed the base branch from 2.2.x to 2.3.x October 31, 2026 13:31
@ondrejmirtes
ondrejmirtes force-pushed the fix/method-throw-union-type branch from c094914 to 143c743 Compare October 31, 2026 13:57
@ondrejmirtes

Copy link
Copy Markdown
Member

I don't love this solution. Other places solve this with $typeWithMethod->getObjectClassNames() like MethodCallReturnTypeHelper. We should be consistent. instanceof UnionTypeMethodReflection doesn't work for plenty of reasons, it can easily break.

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.

DynamicThrowType only consider the first class matching

4 participants