Skip to content

Fix phpstan/phpstan#7976: Array object nested types are not properly identified#5402

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

Fix phpstan/phpstan#7976: Array object nested types are not properly identified#5402
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-95idj72

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When returning new ArrayObject($data) where $data is built up through loop assignments, PHPStan inferred hasOffsetValue accessories on the inner array types. These accessories made the invariant template type check fail with a confusing error like "should return ArrayObject<int|string, non-empty-array<string, mixed>> but returns ArrayObject<int|string, non-empty-array<string, mixed>>." — where both types display identically but are structurally different internally.

Changes

  • Added generalizeArrayType() helper in src/Type/Generic/TemplateTypeHelper.php that rebuilds array types from their fundamental properties (key type, value type, non-empty, list), stripping inferred-only accessories like HasOffsetValueType
  • Called this helper in generalizeInferredTemplateType() for non-covariant template parameters
  • Added regression test in tests/PHPStan/Rules/Methods/data/bug-7976.php and test method in ReturnTypeRuleTest.php

Root cause

When PHPStan infers template type arguments from constructor calls like new ArrayObject($data), the inferred TValue retains HasOffsetValueType accessories from type inference. For invariant template parameters, PHPStan requires exact type equality via equals(). Since HasOffsetValueType cannot be expressed in PHPDoc annotations, the user's declared return type never has these accessories, causing the equality check to always fail — even though the types are fundamentally the same.

The fix rebuilds the array type from its interface-level properties (iterable key/value types, non-empty status, list status), effectively stripping accessories that are only added by type inference and cannot be expressed in user-facing type declarations.

Test

Added testBug7976 in ReturnTypeRuleTest with a test data file reproducing the original issue: a method building up array data in a loop with nested foreach and returning new ArrayObject($data). The test expects no errors (false positive fix).

Fixes phpstan/phpstan#7976

…lates

- Added generalizeArrayType() to strip HasOffsetValueType accessories from
  inferred template types for non-covariant generic parameters
- The root cause was that array types inferred with hasOffsetValue accessories
  failed the strict equality check required by invariant template parameters
- Rebuilds array types from fundamental properties (key/value types, non-empty,
  list) to match what users can express in PHPDoc annotations
- New regression test in tests/PHPStan/Rules/Methods/data/bug-7976.php
@staabm staabm deleted the create-pull-request/patch-95idj72 branch April 5, 2026 06:05
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