55use PhpParser \Node \Expr \FuncCall ;
66use PHPStan \Analyser \Scope ;
77use PHPStan \Reflection \FunctionReflection ;
8+ use PHPStan \TrinaryLogic ;
9+ use PHPStan \Type \Accessory \AccessoryArrayListType ;
810use PHPStan \Type \Accessory \NonEmptyArrayType ;
911use PHPStan \Type \ArrayType ;
12+ use PHPStan \Type \Constant \ConstantArrayType ;
13+ use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
14+ use PHPStan \Type \Constant \ConstantIntegerType ;
15+ use PHPStan \Type \Constant \ConstantStringType ;
1016use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
17+ use PHPStan \Type \NeverType ;
1118use PHPStan \Type \Type ;
1219use PHPStan \Type \TypeCombinator ;
1320use function count ;
@@ -23,54 +30,107 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
2330
2431 public function getTypeFromFunctionCall (FunctionReflection $ functionReflection , FuncCall $ functionCall , Scope $ scope ): ?Type
2532 {
26- $ arrayTypes = $ this -> collectArrayTypes ( $ functionCall , $ scope );
33+ $ args = $ functionCall -> getArgs ( );
2734
28- if (count ( $ arrayTypes ) === 0 ) {
35+ if (! isset ( $ args [ 0 ]) ) {
2936 return null ;
3037 }
3138
32- return $ this ->getResultType (...$ arrayTypes );
33- }
39+ $ argTypes = [];
40+ $ optionalArgTypes = [];
41+ foreach ($ args as $ arg ) {
42+ $ argType = $ scope ->getType ($ arg ->value );
3443
35- private function getResultType (Type ...$ arrayTypes ): Type
36- {
37- $ keyTypes = [];
38- $ valueTypes = [];
39- $ nonEmptyArray = false ;
40- foreach ($ arrayTypes as $ arrayType ) {
41- if (!$ nonEmptyArray && $ arrayType ->isIterableAtLeastOnce ()->yes ()) {
42- $ nonEmptyArray = true ;
44+ if ($ arg ->unpack ) {
45+ if ($ argType ->isConstantArray ()->yes ()) {
46+ foreach ($ argType ->getConstantArrays () as $ constantArray ) {
47+ foreach ($ constantArray ->getValueTypes () as $ valueType ) {
48+ $ argTypes [] = $ valueType ;
49+ }
50+ }
51+ } else {
52+ $ argTypes [] = $ argType ->getIterableValueType ();
53+ }
54+
55+ if (!$ argType ->isIterableAtLeastOnce ()->yes ()) {
56+ // unpacked params can be empty, making them optional
57+ $ optionalArgTypesOffset = count ($ argTypes ) - 1 ;
58+ foreach (array_keys ($ argTypes ) as $ key ) {
59+ $ optionalArgTypes [] = $ optionalArgTypesOffset + $ key ;
60+ }
61+ }
62+ } else {
63+ $ argTypes [] = $ argType ;
4364 }
44-
45- $ keyTypes [] = $ arrayType ->getIterableKeyType ();
46- $ valueTypes [] = $ arrayType ->getIterableValueType ();
4765 }
4866
49- $ keyType = TypeCombinator::union (...$ keyTypes );
50- $ valueType = TypeCombinator::union (...$ valueTypes );
67+ $ allConstant = TrinaryLogic::createYes ()->lazyAnd (
68+ $ argTypes ,
69+ static fn (Type $ argType ) => $ argType ->isConstantArray (),
70+ );
71+
72+ if ($ allConstant ->yes ()) {
73+ $ newArrayBuilder = ConstantArrayTypeBuilder::createEmpty ();
74+
75+ foreach ($ argTypes as $ argType ) {
76+ /** @var array<int|string, ConstantIntegerType|ConstantStringType> $keyTypes */
77+ $ keyTypes = [];
78+ foreach ($ argType ->getConstantArrays () as $ constantArray ) {
79+ foreach ($ constantArray ->getKeyTypes () as $ keyType ) {
80+ $ keyTypes [$ keyType ->getValue ()] = $ keyType ;
81+ }
82+ }
83+
84+ foreach ($ keyTypes as $ keyType ) {
85+ $ newArrayBuilder ->setOffsetValueType (
86+ $ keyType ,
87+ $ argType ->getOffsetValueType ($ keyType ),
88+ !$ argType ->hasOffsetValueType ($ keyType )->yes (),
89+ );
90+ }
91+ }
5192
52- $ arrayType = new ArrayType ($ keyType , $ valueType );
53- return $ nonEmptyArray ? TypeCombinator::intersect ($ arrayType , new NonEmptyArrayType ()) : $ arrayType ;
54- }
93+ return $ newArrayBuilder ->getArray ();
94+ }
5595
56- /**
57- * @return Type[]
58- */
59- private function collectArrayTypes (FuncCall $ functionCall , Scope $ scope ): array
60- {
61- $ args = $ functionCall ->getArgs ();
96+ $ keyTypes = [];
97+ $ valueTypes = [];
98+ $ nonEmpty = false ;
99+ $ isList = true ;
100+ foreach ($ argTypes as $ key => $ argType ) {
101+ $ keyType = $ argType ->getIterableKeyType ();
102+ $ keyTypes [] = $ keyType ;
103+ $ valueTypes [] = $ argType ->getIterableValueType ();
104+
105+ if (!$ argType ->isList ()->yes ()) {
106+ $ isList = false ;
107+ }
62108
63- $ arrayTypes = [];
64- foreach ($ args as $ arg ) {
65- $ argType = $ scope ->getType ($ arg ->value );
66- if (!$ argType ->isArray ()->yes ()) {
109+ if (in_array ($ key , $ optionalArgTypes , true ) || !$ argType ->isIterableAtLeastOnce ()->yes ()) {
67110 continue ;
68111 }
69112
70- $ arrayTypes [] = $ arg ->unpack ? $ argType ->getIterableValueType () : $ argType ;
113+ $ nonEmpty = true ;
114+ }
115+
116+ $ keyType = TypeCombinator::union (...$ keyTypes );
117+ if ($ keyType instanceof NeverType) {
118+ return new ConstantArrayType ([], []);
119+ }
120+
121+ $ arrayType = new ArrayType (
122+ $ keyType ,
123+ TypeCombinator::union (...$ valueTypes ),
124+ );
125+
126+ if ($ nonEmpty ) {
127+ $ arrayType = TypeCombinator::intersect ($ arrayType , new NonEmptyArrayType ());
128+ }
129+ if ($ isList ) {
130+ $ arrayType = TypeCombinator::intersect ($ arrayType , new AccessoryArrayListType ());
71131 }
72132
73- return $ arrayTypes ;
133+ return $ arrayType ;
74134 }
75135
76136}
0 commit comments