Skip to content

Fix phpstan/phpstan#14203: Incorrect return.type recognized from generic callback#5299

Closed
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-038g840
Closed

Fix phpstan/phpstan#14203: Incorrect return.type recognized from generic callback#5299
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-038g840

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When calling a generic method (like map()) on a union of generic objects (e.g., Collection<int, SpecificA>|Collection<int, SpecificB>), the callback's return type was ignored and the original union type was returned instead of properly resolving the template type from the callback.

Changes

  • Modified src/Analyser/ExprHandler/Helper/MethodCallReturnTypeHelper.php to decompose union types and resolve method return types for each member separately before combining the results, while excluding TemplateType instances to preserve template type wrapping
  • Updated tests/PHPStan/Analyser/nsrt/static-late-binding.php line 88 assertion to match the now-correct return type for $clUnioned::retStatic() (was bool|A|X, now correctly bool|A since X::retStatic() returns bool not static)
  • Added regression test tests/PHPStan/Analyser/nsrt/bug-14203.php

Root cause

MethodCallReturnTypeHelper::methodCallReturnType() called getMethod() on the union type, which produced a UnionTypeMethodReflection. Its getVariants() used combineAcceptors() to merge variants from different generic instantiations. This merged the return types into a union before template type resolution from the callback argument could happen, so the TMapValue template type could not be properly resolved from the callable.

The fix processes each union member independently — resolving template types per member — and then unions the results. TemplateType instances (which extend UnionType via TemplateUnionType) are excluded from decomposition to preserve template type semantics.

Test

Added tests/PHPStan/Analyser/nsrt/bug-14203.php which tests both the working case (single Collection<int, SpecificA|SpecificB>) and the previously broken case (union Collection<int, SpecificA>|Collection<int, SpecificB>) to verify that map() with a typed callback correctly resolves to Collection<int, MyDTO> in both cases.

Fixes phpstan/phpstan#14203

- When calling a method with template parameters on a union type (e.g.,
  Collection<int, A>|Collection<int, B>), resolve return types for each
  union member separately instead of combining variants first
- The root cause was that combineAcceptors merged variants from different
  generic instantiations, losing proper template type resolution for
  callable return types
- Excludes TemplateUnionType from decomposition to preserve template
  type wrapping
- Updated static-late-binding test assertion to match now-correct result
- New regression test in tests/PHPStan/Analyser/nsrt/bug-14203.php

Closes phpstan/phpstan#14203
@VincentLanglet
Copy link
Contributor

This seems to solve similar issue than #5225...

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