|
6 | 6 | use Closure;
|
7 | 7 | use PhpParser\Comment\Doc;
|
8 | 8 | use PhpParser\Node;
|
| 9 | +use PhpParser\Node\Arg; |
9 | 10 | use PhpParser\Node\Expr;
|
10 | 11 | use PhpParser\Node\Expr\Array_;
|
11 | 12 | use PhpParser\Node\Expr\ArrayDimFetch;
|
|
109 | 110 | use PHPStan\Reflection\ReflectionProvider;
|
110 | 111 | use PHPStan\ShouldNotHappenException;
|
111 | 112 | use PHPStan\TrinaryLogic;
|
112 |
| -use PHPStan\Type\Accessory\NonEmptyArrayType; |
113 | 113 | use PHPStan\Type\ArrayType;
|
114 | 114 | use PHPStan\Type\ClosureType;
|
115 | 115 | use PHPStan\Type\Constant\ConstantArrayType;
|
116 | 116 | use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
|
117 | 117 | use PHPStan\Type\Constant\ConstantBooleanType;
|
118 |
| -use PHPStan\Type\Constant\ConstantIntegerType; |
119 | 118 | use PHPStan\Type\Constant\ConstantStringType;
|
120 | 119 | use PHPStan\Type\ErrorType;
|
121 | 120 | use PHPStan\Type\FileTypeMapper;
|
| 121 | +use PHPStan\Type\GeneralizePrecision; |
122 | 122 | use PHPStan\Type\Generic\GenericClassStringType;
|
123 | 123 | use PHPStan\Type\Generic\TemplateTypeHelper;
|
124 | 124 | use PHPStan\Type\Generic\TemplateTypeMap;
|
@@ -1843,75 +1843,80 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
|
1843 | 1843 | && in_array($functionReflection->getName(), ['array_push', 'array_unshift'], true)
|
1844 | 1844 | && count($expr->getArgs()) >= 2
|
1845 | 1845 | ) {
|
1846 |
| - $argumentTypes = []; |
1847 |
| - foreach (array_slice($expr->getArgs(), 1) as $callArg) { |
1848 |
| - $callArgType = $scope->getType($callArg->value); |
1849 |
| - if ($callArg->unpack) { |
1850 |
| - $iterableValueType = $callArgType->getIterableValueType(); |
1851 |
| - if ($iterableValueType instanceof UnionType) { |
1852 |
| - foreach ($iterableValueType->getTypes() as $innerType) { |
1853 |
| - $argumentTypes[] = $innerType; |
| 1846 | + $arrayArg = $expr->getArgs()[0]->value; |
| 1847 | + $arrayType = $scope->getType($arrayArg); |
| 1848 | + $callArgs = array_slice($expr->getArgs(), 1); |
| 1849 | + |
| 1850 | + /** |
| 1851 | + * @param Arg[] $callArgs |
| 1852 | + * @param callable(?Type, Type, bool=): void $setOffsetValueType |
| 1853 | + */ |
| 1854 | + $setOffsetValueTypes = static function (Scope $scope, array $callArgs, callable $setOffsetValueType, ?bool &$nonConstantArrayWasUnpacked = null): void { |
| 1855 | + foreach ($callArgs as $callArg) { |
| 1856 | + $callArgType = $scope->getType($callArg->value); |
| 1857 | + if ($callArg->unpack) { |
| 1858 | + if ($callArgType->isIterableAtLeastOnce()->no()) { |
| 1859 | + continue; |
1854 | 1860 | }
|
1855 |
| - } else { |
1856 |
| - $argumentTypes[] = $iterableValueType; |
| 1861 | + if (!$callArgType instanceof ConstantArrayType) { |
| 1862 | + $nonConstantArrayWasUnpacked = true; |
| 1863 | + } |
| 1864 | + $iterableValueType = $callArgType->getIterableValueType(); |
| 1865 | + $isOptional = !$callArgType->isIterableAtLeastOnce()->yes(); |
| 1866 | + if ($iterableValueType instanceof UnionType) { |
| 1867 | + foreach ($iterableValueType->getTypes() as $innerType) { |
| 1868 | + $setOffsetValueType(null, $innerType, $isOptional); |
| 1869 | + } |
| 1870 | + } else { |
| 1871 | + $setOffsetValueType(null, $iterableValueType, $isOptional); |
| 1872 | + } |
| 1873 | + continue; |
1857 | 1874 | }
|
1858 |
| - continue; |
| 1875 | + $setOffsetValueType(null, $callArgType); |
1859 | 1876 | }
|
| 1877 | + }; |
1860 | 1878 |
|
1861 |
| - $argumentTypes[] = $callArgType; |
1862 |
| - } |
| 1879 | + if ($arrayType instanceof ConstantArrayType) { |
| 1880 | + $prepend = $functionReflection->getName() === 'array_unshift'; |
| 1881 | + $arrayTypeBuilder = $prepend ? ConstantArrayTypeBuilder::createEmpty() : ConstantArrayTypeBuilder::createFromConstantArray($arrayType); |
1863 | 1882 |
|
1864 |
| - $arrayArg = $expr->getArgs()[0]->value; |
1865 |
| - $originalArrayType = $scope->getType($arrayArg); |
1866 |
| - $constantArrays = TypeUtils::getOldConstantArrays($originalArrayType); |
1867 |
| - if ( |
1868 |
| - $functionReflection->getName() === 'array_push' |
1869 |
| - || ($originalArrayType->isArray()->yes() && count($constantArrays) === 0) |
1870 |
| - ) { |
1871 |
| - $arrayType = $originalArrayType; |
1872 |
| - foreach ($argumentTypes as $argType) { |
1873 |
| - $arrayType = $arrayType->setOffsetValueType(null, $argType); |
1874 |
| - } |
1875 |
| - |
1876 |
| - $scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType($arrayArg, TypeCombinator::intersect($arrayType, new NonEmptyArrayType())); |
1877 |
| - } elseif (count($constantArrays) > 0) { |
1878 |
| - $defaultArrayBuilder = ConstantArrayTypeBuilder::createEmpty(); |
1879 |
| - foreach ($argumentTypes as $argType) { |
1880 |
| - $defaultArrayBuilder->setOffsetValueType(null, $argType); |
1881 |
| - } |
| 1883 | + $setOffsetValueTypes( |
| 1884 | + $scope, |
| 1885 | + $callArgs, |
| 1886 | + static function (?Type $offsetType, Type $valueType, bool $optional = false) use (&$arrayTypeBuilder): void { |
| 1887 | + $arrayTypeBuilder->setOffsetValueType($offsetType, $valueType, $optional); |
| 1888 | + }, |
| 1889 | + $nonConstantArrayWasUnpacked, |
| 1890 | + ); |
1882 | 1891 |
|
1883 |
| - $defaultArrayType = $defaultArrayBuilder->getArray(); |
1884 |
| - if (!$defaultArrayType instanceof ConstantArrayType) { |
1885 |
| - $arrayType = $originalArrayType; |
1886 |
| - foreach ($argumentTypes as $argType) { |
1887 |
| - $arrayType = $arrayType->setOffsetValueType(null, $argType); |
| 1892 | + if ($prepend) { |
| 1893 | + $keyTypes = $arrayType->getKeyTypes(); |
| 1894 | + $valueTypes = $arrayType->getValueTypes(); |
| 1895 | + foreach ($keyTypes as $k => $keyType) { |
| 1896 | + $arrayTypeBuilder->setOffsetValueType( |
| 1897 | + $keyType instanceof ConstantStringType ? $keyType : null, |
| 1898 | + $valueTypes[$k], |
| 1899 | + $arrayType->isOptionalKey($k), |
| 1900 | + ); |
1888 | 1901 | }
|
| 1902 | + } |
1889 | 1903 |
|
1890 |
| - $scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType($arrayArg, TypeCombinator::intersect($arrayType, new NonEmptyArrayType())); |
1891 |
| - } else { |
1892 |
| - $arrayTypes = []; |
1893 |
| - foreach ($constantArrays as $constantArray) { |
1894 |
| - $arrayTypeBuilder = ConstantArrayTypeBuilder::createFromConstantArray($defaultArrayType); |
1895 |
| - foreach ($constantArray->getKeyTypes() as $i => $keyType) { |
1896 |
| - $valueType = $constantArray->getValueTypes()[$i]; |
1897 |
| - if ($keyType instanceof ConstantIntegerType) { |
1898 |
| - $keyType = null; |
1899 |
| - } |
1900 |
| - $arrayTypeBuilder->setOffsetValueType( |
1901 |
| - $keyType, |
1902 |
| - $valueType, |
1903 |
| - $constantArray->isOptionalKey($i), |
1904 |
| - ); |
1905 |
| - } |
1906 |
| - $arrayTypes[] = $arrayTypeBuilder->getArray(); |
1907 |
| - } |
| 1904 | + $arrayType = $arrayTypeBuilder->getArray(); |
1908 | 1905 |
|
1909 |
| - $scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType( |
1910 |
| - $arrayArg, |
1911 |
| - TypeCombinator::union(...$arrayTypes), |
1912 |
| - ); |
| 1906 | + if ($arrayType instanceof ConstantArrayType && $nonConstantArrayWasUnpacked) { |
| 1907 | + $arrayType = $arrayType->generalize(GeneralizePrecision::lessSpecific()); |
1913 | 1908 | }
|
| 1909 | + } else { |
| 1910 | + $setOffsetValueTypes( |
| 1911 | + $scope, |
| 1912 | + $callArgs, |
| 1913 | + static function (?Type $offsetType, Type $valueType) use (&$arrayType): void { |
| 1914 | + $arrayType = $arrayType->setOffsetValueType($offsetType, $valueType); |
| 1915 | + }, |
| 1916 | + ); |
1914 | 1917 | }
|
| 1918 | + |
| 1919 | + $scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType($arrayArg, $arrayType); |
1915 | 1920 | }
|
1916 | 1921 |
|
1917 | 1922 | if (
|
|
0 commit comments