Fix phpstan/phpstan#7170: Can not set optional array element to explicit value in generic#5335
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Open
Conversation
…c class - Override setOffsetValueType, setExistingOffsetValueType, and unsetOffset in TemplateConstantArrayType to preserve the template wrapper when the result is compatible with the bound - The root cause was that ConstantArrayType::setOffsetValueType returned a plain ConstantArrayType, losing the template context, which then failed the template parameter acceptance check - New regression test in tests/PHPStan/Rules/Properties/data/bug-7170.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a template type is bounded by a constant array with optional keys (e.g.
@template Tdata of array{extension?: array<mixed>}), setting an optional key's value inside the class produced a false positive "does not accept" error. The same code without generics worked fine.Changes
setOffsetValueType,setExistingOffsetValueType, andunsetOffsetinsrc/Type/Generic/TemplateConstantArrayType.phpto preserve the template type wrapper when the result of the parent operation is still compatible with the template's boundtestBug7170intests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.phptests/PHPStan/Rules/Properties/data/bug-7170.phpRoot cause
TemplateConstantArrayTypeextendsConstantArrayTypebut did not overridesetOffsetValueType. When called, the parentConstantArrayType::setOffsetValueTypeusedConstantArrayTypeBuilderwhich returned a plainConstantArrayType, completely losing the template wrapper. The resulting concrete type (e.g.array{extension: array{}}) was then checked against the property's template typeTdatausingTemplateTypeParameterStrategy, which rejected it because a concrete type is not the same as the template type.The fix checks whether the result of the parent operation is still within the template's bound. If so, the template type itself is returned, preserving the template wrapper and allowing the property assignment to succeed.
Test
The regression test reproduces the original issue: a generic class with
@template Tdata of array{extension?: array<mixed>}that sets$this->data['extension'] = []inside an!issetguard. The test expects no errors, matching the behavior of the equivalent non-generic class.Fixes phpstan/phpstan#7170