Skip to content

Template covariance false positive with nullable type on level < 8 #12008

Closed

Description

@ondrejmirtes

Bug report

This shouldn't report an error. The problem is in RuleLevelHelper because it blindly removes null from everything but in case of invariant @template with GenericObjectType this is counter-productive.

Code snippet that reproduces the problem

https://phpstan.org/r/1123b953-b99e-41d0-a4e6-5d3f28eee237

Expected output

NO errors

Did PHPStan help you today? Did it make you happy in any way?

No response

Activity

  1. added this to the Generics milestone on Nov 12, 2024
  2. ondrejmirtes commented on Nov 12, 2024

    @ondrejmirtes
    MemberAuthor

    A solution would be to somehow employ SimultaneousTypeTraverser instead of TypeTraverser which keeps the context of both $acceptingType and $acceptedType. Then we'd be able to check the @template of the accepting side and decide if null should be removed or not.

  3. piotr-cz commented on Nov 13, 2024

    @piotr-cz

    Thanks for reporting, I could not understand what's wrong here after updating to v2.0.1:

    Method Tests\TestCase::getAppInstance() should return Slim\App<Psr\Container\ContainerInterface|null> but returns Slim\App<Psr\Container\ContainerInterface|null>.
    🪪  return.type
    💡 Template type TContainerInterface on class Slim\App is not covariant. Learn more: https://phpstan.org/blog/whats-up-with-template-covariant
    

    By the way, it's referring to Slim/App

  4. VincentLanglet commented on Aug 3, 2025

    @VincentLanglet
    Contributor

    Just discovered this, and it seems like there is at least 5 issues open for this (maybe you'll want to close some)
    cf #9096 (comment)

    A solution would be to somehow employ SimultaneousTypeTraverser instead of TypeTraverser which keeps the context of both $acceptingType and $acceptedType. Then we'd be able to check the @template of the accepting side and decide if null should be removed or not.

    I tried, but as soon as TypeTraverser is replaced by SimultaneousTypeTraverser, some tests are failing.

    For instance when comparing array<string> with list<string|null>, since
    traverseSimultaneously with Array&List, Array the early IntersectionType check

    if (!$right instanceof self) {
          return $this;
    }
    

    prevents to keep traversing the array keys/values.

    I dunno how to handle this...

    The only workaround I found so far would be to have an early check

    $acceptingType->accepts($acceptedType)
    

    to remove lot of false positive (is it ok enough for you ?), but it won't fully solve the general issue.

  5. ondrejmirtes commented on Aug 5, 2025

    @ondrejmirtes
    MemberAuthor

    @VincentLanglet Please try to do a SimultaneousTypeTraverser proof-of-concept that would solve the GenericObjectType problem with levels.

    If it works then we can look into these issues, probably by fixing IntersectionType::traverseSimultaneously() for lists/arrays etc.

  6. VincentLanglet commented on Aug 6, 2025

    @VincentLanglet
    Contributor

    @VincentLanglet Please try to do a SimultaneousTypeTraverser proof-of-concept that would solve the GenericObjectType problem with levels.

    If it works then we can look into these issues, probably by fixing IntersectionType::traverseSimultaneously() for lists/arrays etc.

    Poc opened phpstan/phpstan-src#4210

  7. phpstan-bot commented on Sep 10, 2026

    @phpstan-bot
    Contributor

    Closed via merging phpstan/phpstan-src#6392

  8. github-actions commented on Oct 12, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

  9. locked as resolved and limited conversation to collaborators on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions