Skip to content

Do not re-wrap NeverType as TemplateMixedType in TemplateUnionType::filterTypes()#5500

Merged
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-kjebg09
Apr 21, 2026
Merged

Do not re-wrap NeverType as TemplateMixedType in TemplateUnionType::filterTypes()#5500
ondrejmirtes merged 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-kjebg09

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a pure function casts a template type T of int|string to string, PHPStan incorrectly reports "Possibly impure call to method stdClass::__toString()". This happens because TemplateUnionType::filterTypes() wraps a NeverType result (no matching types) back into TemplateMixedType via TemplateTypeFactory::create(), and MixedType reports that it has every method, including __toString.

Changes

  • src/Type/Generic/TemplateUnionType.php: Return NeverType directly from filterTypes() instead of re-wrapping it through TemplateTypeFactory::create() (which falls through to TemplateMixedType)
  • src/Type/Generic/TemplateBenevolentUnionType.php: Same fix applied to the benevolent union variant

Root cause

TemplateUnionType::filterTypes() calls parent::filterTypes() which returns NeverType when all union members are filtered out. The override then passes this NeverType to TemplateTypeFactory::create(), which has no branch for NeverType and falls through to the catch-all: new TemplateMixedType(...). Since MixedType::hasMethod() returns Yes for any method name, filterTypeWithMethod() finds a __toString method on the resulting type and creates a DummyMethodReflection with stdClass as its declaring class — producing the false positive purity error.

The fix is at the filterTypes() level, so it covers all callers:

  • filterTypeWithMethod (string cast, concatenation, interpolation, echo, print, .=)
  • filterTypeWithProperty, filterTypeWithConstant, filterTypeWhenIterable

Analogous cases probed:

  • TemplateBenevolentUnionType — same bug pattern, fixed with same approach
  • Pure methods (not just functions) — confirmed fixed
  • String concatenation and interpolation — confirmed fixed (same ImplicitToStringCallHelper path)
  • Non-string scalar union bounds (T of int|float|bool) — confirmed fixed

Test

  • tests/PHPStan/Rules/Pure/data/bug-14504.php — tests (string) cast, string concatenation, string interpolation, and non-string scalar union bounds (int|float|bool) in pure functions with template types
  • tests/PHPStan/Rules/Pure/data/bug-14504-method.php — tests the same pattern in a pure method context

Fixes phpstan/phpstan#14504

…pe::filterTypes()`

- When TemplateUnionType::filterTypes() filters out all inner types, the parent
  returns NeverType. The override was re-wrapping it via TemplateTypeFactory::create(),
  which has no NeverType branch and falls through to TemplateMixedType.
- This caused MixedType::hasMethod() to return Yes for __toString, leading to a
  false "Possibly impure call to method stdClass::__toString()" on pure functions
  casting template types like T of int|string to string.
- Applied the same fix to TemplateBenevolentUnionType::filterTypes().
- The fix covers all callers of filterTypes: filterTypeWithMethod (string cast,
  concatenation, interpolation, echo, print), filterTypeWithProperty,
  filterTypeWithConstant, and filterTypeWhenIterable.
@ondrejmirtes ondrejmirtes merged commit 743670c into phpstan:2.1.x Apr 21, 2026
652 of 655 checks passed
@ondrejmirtes ondrejmirtes deleted the create-pull-request/patch-kjebg09 branch April 21, 2026 05:57
@staabm staabm mentioned this pull request Apr 23, 2026
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