|
36 | 36 | use PHPStan\Analyser\ExprHandler\Helper\MethodThrowPointHelper; |
37 | 37 | use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper; |
38 | 38 | use PHPStan\Analyser\ExprHandler\Helper\VirtualExprResultHelper; |
| 39 | +use PHPStan\Analyser\Generics\TemplateArgumentConstraints; |
39 | 40 | use PHPStan\Analyser\Generics\TemplateArgumentObserver; |
40 | 41 | use PHPStan\Analyser\ImpurePoint; |
41 | 42 | use PHPStan\Analyser\InternalThrowPoint; |
@@ -1492,7 +1493,8 @@ public function applyWrite( |
1492 | 1493 | && !$setVarType->isArray()->yes() |
1493 | 1494 | && !(new ObjectType(ArrayAccess::class))->isSuperTypeOf($setVarType)->no() |
1494 | 1495 | ) { |
1495 | | - $scope = $scope->addTemplateArgumentConstraints($nodeScopeResolver->collectOffsetSetUsage( |
| 1496 | + $scope = $scope->addTemplateArgumentConstraints($this->collectOffsetSetUsage( |
| 1497 | + $nodeScopeResolver, |
1496 | 1498 | $scope, |
1497 | 1499 | $setVarType, |
1498 | 1500 | $offsetTypes[count($offsetTypes) - 1][0], |
@@ -1813,6 +1815,32 @@ public function applyWrite( |
1813 | 1815 | ); |
1814 | 1816 | } |
1815 | 1817 |
|
| 1818 | + /** |
| 1819 | + * A write through ArrayAccess (`$storage[$key] = $value`) reaches the |
| 1820 | + * object's template arguments exactly like the offsetSet() call it stands |
| 1821 | + * for, so observe both the key and the written value against that method's |
| 1822 | + * parameters resolved on the receiver. |
| 1823 | + */ |
| 1824 | + private function collectOffsetSetUsage(NodeScopeResolver $nodeScopeResolver, MutatingScope $scope, Type $receiverType, ?Type $keyType, Type $valueType): TemplateArgumentConstraints |
| 1825 | + { |
| 1826 | + $constraints = TemplateArgumentConstraints::createEmpty(); |
| 1827 | + $frame = $nodeScopeResolver->observingTemplateArgumentFrame($scope); |
| 1828 | + if ($frame === null || !$receiverType->hasMethod('offsetSet')->yes()) { |
| 1829 | + return $constraints; |
| 1830 | + } |
| 1831 | + |
| 1832 | + $parameters = $receiverType->getMethod('offsetSet', $scope)->getOnlyVariant()->getParameters(); |
| 1833 | + if ($keyType !== null && isset($parameters[0])) { |
| 1834 | + $constraints = $constraints->merge($this->templateArgumentObserver->collectArgument($parameters[0]->getType(), $keyType)); |
| 1835 | + } |
| 1836 | + if (!isset($parameters[1])) { |
| 1837 | + return $constraints; |
| 1838 | + } |
| 1839 | + |
| 1840 | + $constraints = $constraints->merge($this->templateArgumentObserver->collectArgument($parameters[1]->getType(), $valueType)); |
| 1841 | + return $constraints; |
| 1842 | + } |
| 1843 | + |
1816 | 1844 | private function createArrayDimFetchConditionalExpressionHolder( |
1817 | 1845 | Expr\Variable $keyVar, |
1818 | 1846 | Expr $arrayArg, |
|
0 commit comments