Skip to content

Fix phpstan/phpstan#10290: Function should return Err<array> but returns Err<array>#5337

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

Fix phpstan/phpstan#10290: Function should return Err<array> but returns Err<array>#5337
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-s337eqk

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a generic class has an invariant template parameter (@template T), PHPStan was rejecting subtypes in the accepts() context. For example, Err<array<mixed>> would not accept Err<non-empty-array<mixed, mixed>> even though non-empty-array is a subtype of array. This caused false positive errors like:

Function f() should return Err<array>|Ok<non-empty-string> but returns Err<array>.

This fix makes the accepts() method (used for error reporting in return types, parameter passing, etc.) treat invariant generic template parameters as covariant — accepting subtypes. The isSuperTypeOf() method (used for type inference) retains strict invariant behavior.

Changes

  • Modified src/Type/Generic/GenericObjectType.php: In isSuperTypeOfInternal(), when in acceptsContext and the template is declared invariant, check if the expected type is a supertype of the actual type. If yes, accept it.
  • New regression test: tests/PHPStan/Rules/Functions/data/bug-10290.php — Result/Ok/Err pattern with invariant generics
  • Updated tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php — added testBug10290
  • Updated tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.phptestBug4590 now expects no errors
  • Updated tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php — removed false positive errors in testGenericVariance, testBug5372, testGenericObjectLowerBound
  • Updated tests/PHPStan/Type/Generic/GenericObjectTypeTest.php — added dataAcceptsTypeProjections() to separate accepts vs isSuperTypeOf expectations for type projections
  • Updated tests/PHPStan/Type/StaticTypeTest.php — updated GenericStaticType accepts test

Root cause

GenericObjectType::isSuperTypeOfInternal() delegates invariant template checking to TemplateTypeVariance::isValidVariance(), which uses Type::equals() — requiring exact type match. In the accepts() context (parameter $acceptsContext = true), this was too strict. The fix adds a condition: when in accepts context and the template is declared invariant, if the expected type argument is a supertype of the actual type argument ($a->isSuperTypeOf($b)->yes()), accept it instead of requiring equality.

Test

Added tests/PHPStan/Rules/Functions/data/bug-10290.php reproducing the issue with Result/Ok/Err generic classes. The test verifies that:

  • Err<non-empty-array<mixed, mixed>> is accepted for return type Err<array<mixed>>
  • Ok<non-falsy-string> is accepted for return type Ok<non-empty-string>
  • Ok<bool> is still rejected for return type Ok<true> (supertype, not subtype)

Fixes phpstan/phpstan#10290

…ate positions in accepts() context

- Modified GenericObjectType::isSuperTypeOfInternal() to accept subtypes for invariant
  template parameters when in the accepts context (used for error reporting)
- When the declared template variance is invariant and the type argument is a strict
  subtype, accepts() now returns Yes instead of No
- isSuperTypeOf() behavior remains unchanged (strict invariant equality)
- New regression test in tests/PHPStan/Rules/Functions/data/bug-10290.php
- Also fixes phpstan/phpstan#4590 (OkResponse<array{ok: string}> accepted for OkResponse<array<string, string>>)
- Updated existing tests to reflect the more lenient accepts behavior
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