Skip to content

Fix phpstan/phpstan#13876: Covariance: Error reported on L6, but not L10#5165

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

Fix phpstan/phpstan#13876: Covariance: Error reported on L6, but not L10#5165
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ral89e8

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

Fix false positive error "Property B::$b (Trap<int|null, A|null>) does not accept Trap<int|null, A|null>" that occurred at rule levels 3-7 when assigning a generic object with nullable template arguments to a property with the same generic type.

Changes

  • Modified src/Type/Generic/GenericObjectType.php: Added a fallback check in isSuperTypeOfInternal() for accepts context when invariant template variance check fails. If the only difference between the accepting and accepted type arguments is null (stripped by RuleLevelHelper::transformAcceptedType()), the type is accepted.
  • Added $checkNullables property to tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php to allow testing with checkNullables = false.
  • New regression test data in tests/PHPStan/Rules/Properties/data/bug-13876.php.

Root cause

At rule levels 3-7, RuleLevelHelper::transformAcceptedType() traverses the accepted type and removes null via TypeCombinator::removeNull(). This traversal descends into generic type arguments through GenericObjectType::traverse(), transforming Trap<int|null, A|null> into Trap<int, A> on the accepted side. The accepting type (property type) retains its nulls as Trap<int|null, A|null>.

For invariant template parameters, TemplateTypeVariance::isValidVariance() uses strict Type::equals() checking. Since int|null ≠ int and A|null ≠ A, the check fails and a false positive is reported - even though both types are semantically identical before null stripping.

The fix detects this situation in accepts context: when the accepting type argument is a supertype of the accepted argument AND the accepted argument is a supertype of the accepting argument with null removed, the types differ only by null stripping and should be accepted.

Test

Added testBug13876() in TypesAssignedToPropertiesRuleTest that sets checkNullables = false and verifies no errors are reported when assigning Trap<int|null, A|null> to a property of type Trap<int|null, A|null>.

Fixes phpstan/phpstan#13876

… off

- At rule levels 3-7, RuleLevelHelper strips null from accepted type arguments
- This caused Trap<int|null, A|null> to become Trap<int, A> on the accepted side
- Invariant template check uses equals(), so int|null ≠ int → false positive
- Added fallback in GenericObjectType::isSuperTypeOfInternal() to detect when
  the only difference is null stripping and accept the type in that case
- New regression test in tests/PHPStan/Rules/Properties/data/bug-13876.php

Closes phpstan/phpstan#13876
@VincentLanglet
Copy link
Copy Markdown
Contributor

It might require more work but I think the right fix would be this one #4210

@staabm staabm deleted the create-pull-request/patch-ral89e8 branch March 14, 2026 21:17
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.

3 participants