1616use PHPStan \Type \NeverType ;
1717use PHPStan \Type \Type ;
1818use PHPStan \Type \TypeCombinator ;
19- use PHPStan \Type \UnionType ;
19+ use function array_keys ;
20+ use function count ;
2021use function in_array ;
2122
2223class ArrayMergeFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
@@ -36,23 +37,44 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3637 }
3738
3839 $ argTypes = [];
40+ $ optionalArgTypes = [];
3941 $ allConstant = true ;
40- foreach ($ args as $ i => $ arg ) {
42+ foreach ($ args as $ arg ) {
4143 $ argType = $ scope ->getType ($ arg ->value );
42- $ argTypes [$ i ] = $ argType ;
4344
44- if (!$ arg ->unpack && $ argType instanceof ConstantArrayType) {
45- continue ;
46- }
45+ if ($ arg ->unpack ) {
46+ if ($ argType instanceof ConstantArrayType) {
47+ $ argTypesFound = $ argType ->getValueTypes ();
48+ } else {
49+ $ argTypesFound = [$ argType ->getIterableValueType ()];
50+ }
51+
52+ foreach ($ argTypesFound as $ argTypeFound ) {
53+ $ argTypes [] = $ argTypeFound ;
54+ if ($ argTypeFound instanceof ConstantArrayType) {
55+ continue ;
56+ }
57+ $ allConstant = false ;
58+ }
4759
48- $ allConstant = false ;
60+ if (!$ argType ->isIterableAtLeastOnce ()->yes ()) {
61+ // unpacked params can be empty, making them optional
62+ $ optionalArgTypesOffset = count ($ argTypes ) - 1 ;
63+ foreach (array_keys ($ argTypesFound ) as $ key ) {
64+ $ optionalArgTypes [] = $ optionalArgTypesOffset + $ key ;
65+ }
66+ }
67+ } else {
68+ $ argTypes [] = $ argType ;
69+ if (!$ argType instanceof ConstantArrayType) {
70+ $ allConstant = false ;
71+ }
72+ }
4973 }
5074
5175 if ($ allConstant ) {
5276 $ newArrayBuilder = ConstantArrayTypeBuilder::createEmpty ();
53- foreach ($ args as $ i => $ arg ) {
54- $ argType = $ argTypes [$ i ];
55-
77+ foreach ($ argTypes as $ argType ) {
5678 if (!$ argType instanceof ConstantArrayType) {
5779 throw new ShouldNotHappenException ();
5880 }
@@ -78,22 +100,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
78100 $ keyTypes = [];
79101 $ valueTypes = [];
80102 $ nonEmpty = false ;
81- foreach ($ args as $ i => $ arg ) {
82- $ argType = $ argTypes [$ i ];
83-
84- if ($ arg ->unpack ) {
85- $ argType = $ argType ->getIterableValueType ();
86- if ($ argType instanceof UnionType) {
87- foreach ($ argType ->getTypes () as $ innerType ) {
88- $ argType = $ innerType ;
89- }
90- }
91- }
92-
103+ foreach ($ argTypes as $ key => $ argType ) {
93104 $ keyTypes [] = $ argType ->getIterableKeyType ();
94105 $ valueTypes [] = $ argType ->getIterableValueType ();
95106
96- if (!$ argType ->isIterableAtLeastOnce ()->yes ()) {
107+ if (in_array ( $ key , $ optionalArgTypes , true ) || !$ argType ->isIterableAtLeastOnce ()->yes ()) {
97108 continue ;
98109 }
99110
0 commit comments