Skip to content

Fix phpstan/phpstan#5357: Intersection of generics are not simplified, calling method on it returns *NEVER*#5343

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

Fix phpstan/phpstan#5357: Intersection of generics are not simplified, calling method on it returns *NEVER*#5343
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-p6yiak6

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a raw (non-generic) class like Datagrid was intersected with a generic interface like DatagridInterface<Proxy>, calling methods on the intersection type returned *NEVER* instead of the expected generic return type.

Changes

  • Modified TypeCombinator::intersect() in src/Type/TypeCombinator.php to merge type parameters when intersecting two GenericObjectType instances of the same class, instead of returning never
  • Added baseline entry in phpstan-baseline.neon for the instanceof GenericObjectType usage
  • Added regression test in tests/PHPStan/Analyser/nsrt/bug-5357.php

Root cause

When Datagrid & DatagridInterface<Proxy> was formed (e.g., after an assert($datagrid instanceof Datagrid)), calling getPager() on the intersection collected return types from both members:

  1. From Datagrid (raw): PagerInterface<ProxyQueryInterface> (template resolved to its bound)
  2. From DatagridInterface<Proxy>: PagerInterface<Proxy> (template resolved to concrete type)

These were then intersected via TypeCombinator::intersect(). Since PagerInterface<ProxyQueryInterface> and PagerInterface<Proxy> are the same generic class with different (invariant) type parameters, isSuperTypeOf() returned no, causing the intersection to incorrectly return never.

The fix detects when two GenericObjectType instances of the same class are being intersected and merges their type parameters individually (e.g., ProxyQueryInterface & Proxy = Proxy since Proxy is a subtype). This produces PagerInterface<Proxy> as expected.

Test

Added tests/PHPStan/Analyser/nsrt/bug-5357.php which reproduces the original issue: a DatagridBuilder returns DatagridInterface<Proxy>, then assert($datagrid instanceof Datagrid) creates the intersection, and $datagrid->getPager() should return PagerInterface<Proxy> instead of *NEVER*.

Fixes phpstan/phpstan#5357

- When two GenericObjectTypes of the same class are intersected (e.g.
  PagerInterface<ProxyQueryInterface> & PagerInterface<Proxy>), merge
  their type parameters instead of returning never
- This fixes the case where a raw generic class (Datagrid) in an
  intersection with a generic interface (DatagridInterface<Proxy>)
  caused method return types to resolve to *NEVER*
- New regression test in tests/PHPStan/Analyser/nsrt/bug-5357.php
- Added baseline entry for instanceof GenericObjectType in TypeCombinator
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.

1 participant