16
16
use PHPStan \Type \NeverType ;
17
17
use PHPStan \Type \Type ;
18
18
use PHPStan \Type \TypeCombinator ;
19
- use PHPStan \Type \UnionType ;
19
+ use function array_keys ;
20
+ use function count ;
20
21
use function in_array ;
21
22
22
23
class ArrayMergeFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
@@ -36,23 +37,44 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
36
37
}
37
38
38
39
$ argTypes = [];
40
+ $ optionalArgTypes = [];
39
41
$ allConstant = true ;
40
- foreach ($ args as $ i => $ arg ) {
42
+ foreach ($ args as $ arg ) {
41
43
$ argType = $ scope ->getType ($ arg ->value );
42
- $ argTypes [$ i ] = $ argType ;
43
44
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
+ }
47
59
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
+ }
49
73
}
50
74
51
75
if ($ allConstant ) {
52
76
$ newArrayBuilder = ConstantArrayTypeBuilder::createEmpty ();
53
- foreach ($ args as $ i => $ arg ) {
54
- $ argType = $ argTypes [$ i ];
55
-
77
+ foreach ($ argTypes as $ argType ) {
56
78
if (!$ argType instanceof ConstantArrayType) {
57
79
throw new ShouldNotHappenException ();
58
80
}
@@ -78,22 +100,11 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
78
100
$ keyTypes = [];
79
101
$ valueTypes = [];
80
102
$ 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 ) {
93
104
$ keyTypes [] = $ argType ->getIterableKeyType ();
94
105
$ valueTypes [] = $ argType ->getIterableValueType ();
95
106
96
- if (!$ argType ->isIterableAtLeastOnce ()->yes ()) {
107
+ if (in_array ( $ key , $ optionalArgTypes , true ) || !$ argType ->isIterableAtLeastOnce ()->yes ()) {
97
108
continue ;
98
109
}
99
110
0 commit comments