Skip to content

Fix #7170: Can not set optional array element to explicit value in generic#4963

Open
phpstan-bot wants to merge 2 commits into2.1.xfrom
create-pull-request/patch-mx3pncf
Open

Fix #7170: Can not set optional array element to explicit value in generic#4963
phpstan-bot wants to merge 2 commits into2.1.xfrom
create-pull-request/patch-mx3pncf

Conversation

@phpstan-bot
Copy link
Collaborator

@phpstan-bot phpstan-bot commented Feb 16, 2026

Summary

When a class has a template type bounded by an array shape with optional keys (e.g., @template Tdata of array{extension?: array<mixed>}), setting one of those optional keys to a value that matches the bound's value type incorrectly reported a property assignment error: "does not accept array{extension: array{}}". The same code without generics worked fine.

Changes

  • Override setOffsetValueType() and setExistingOffsetValueType() in src/Type/Generic/TemplateConstantArrayType.php to preserve the template type when the offset being set exists in the bound and the new value is accepted by the bound's value type for that key
  • Add isOffsetWithinBound() helper method that checks whether an offset assignment is compatible with the template's bound
  • Add regression test in tests/PHPStan/Rules/Properties/data/bug-7170.php and test method in tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php

Root cause

When setOffsetValueType() was called on a TemplateConstantArrayType, it delegated to the parent ConstantArrayType::setOffsetValueType() which returned a plain ConstantArrayType, losing the template type wrapper. This concrete type was then checked against the template property type via TemplateTypeArgumentStrategy::accepts(), which correctly flagged it as maybe (since a concrete array type is not guaranteed to match all possible template instantiations). However, the check was too conservative for this case: modifying an offset that exists in the template's bound with a value compatible with that bound preserves the template contract.

The fix ensures that when the modification is within the template's bound specification, the template type is preserved, so the subsequent acceptance check correctly passes (Tdata accepts Tdata).

Test

The regression test creates a generic class with @template Tdata of array{extension?: array<mixed>} and sets $this->data['extension'] = [] inside an isset() guard. The test expects no errors, matching the behavior of the equivalent non-generic class.

Fixes phpstan/phpstan#7170

Closes phpstan/phpstan#10172

phpstan-bot and others added 2 commits February 16, 2026 23:40
…stant array property

- Override setOffsetValueType() and setExistingOffsetValueType() in TemplateConstantArrayType
- When the offset exists in the template's bound and the value type is accepted by the bound's value type, return the template type itself
- This preserves the template contract: modifying an in-bound offset on a template value still produces a valid template value
- New regression test in tests/PHPStan/Rules/Properties/data/bug-7170.php

Closes phpstan/phpstan#7170
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