66use PHPStan \Analyser \Scope ;
77use PHPStan \DependencyInjection \AutowiredService ;
88use PHPStan \Reflection \FunctionReflection ;
9+ use PHPStan \TrinaryLogic ;
10+ use PHPStan \Type \Accessory \AccessoryArrayListType ;
911use PHPStan \Type \Accessory \NonEmptyArrayType ;
1012use PHPStan \Type \ArrayType ;
13+ use PHPStan \Type \Constant \ConstantArrayType ;
14+ use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
15+ use PHPStan \Type \Constant \ConstantIntegerType ;
16+ use PHPStan \Type \Constant \ConstantStringType ;
1117use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
18+ use PHPStan \Type \NeverType ;
1219use PHPStan \Type \Type ;
1320use PHPStan \Type \TypeCombinator ;
21+ use function array_keys ;
1422use function count ;
23+ use function in_array ;
1524use function strtolower ;
1625
1726#[AutowiredService]
@@ -25,54 +34,107 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
2534
2635public function getTypeFromFunctionCall (FunctionReflection $ functionReflection , FuncCall $ functionCall , Scope $ scope ): ?Type
2736{
28- $ arrayTypes = $ this -> collectArrayTypes ( $ functionCall , $ scope );
37+ $ args = $ functionCall -> getArgs ( );
2938
30- if (count ( $ arrayTypes ) === 0 ) {
39+ if (! isset ( $ args [ 0 ]) ) {
3140return null ;
3241}
3342
34- return $ this ->getResultType (...$ arrayTypes );
35- }
43+ $ argTypes = [];
44+ $ optionalArgTypes = [];
45+ foreach ($ args as $ arg ) {
46+ $ argType = $ scope ->getType ($ arg ->value );
3647
37- private function getResultType (Type ...$ arrayTypes ): Type
38- {
39- $ keyTypes = [];
40- $ valueTypes = [];
41- $ nonEmptyArray = false ;
42- foreach ($ arrayTypes as $ arrayType ) {
43- if (!$ nonEmptyArray && $ arrayType ->isIterableAtLeastOnce ()->yes ()) {
44- $ nonEmptyArray = true ;
48+ if ($ arg ->unpack ) {
49+ if ($ argType ->isConstantArray ()->yes ()) {
50+ foreach ($ argType ->getConstantArrays () as $ constantArray ) {
51+ foreach ($ constantArray ->getValueTypes () as $ valueType ) {
52+ $ argTypes [] = $ valueType ;
53+ }
54+ }
55+ } else {
56+ $ argTypes [] = $ argType ->getIterableValueType ();
57+ }
58+
59+ if (!$ argType ->isIterableAtLeastOnce ()->yes ()) {
60+ // unpacked params can be empty, making them optional
61+ $ optionalArgTypesOffset = count ($ argTypes ) - 1 ;
62+ foreach (array_keys ($ argTypes ) as $ key ) {
63+ $ optionalArgTypes [] = $ optionalArgTypesOffset + $ key ;
64+ }
65+ }
66+ } else {
67+ $ argTypes [] = $ argType ;
4568}
46-
47- $ keyTypes [] = $ arrayType ->getIterableKeyType ();
48- $ valueTypes [] = $ arrayType ->getIterableValueType ();
4969}
5070
51- $ keyType = TypeCombinator::union (...$ keyTypes );
52- $ valueType = TypeCombinator::union (...$ valueTypes );
71+ $ allConstant = TrinaryLogic::createYes ()->lazyAnd (
72+ $ argTypes ,
73+ static fn (Type $ argType ) => $ argType ->isConstantArray (),
74+ );
75+
76+ if ($ allConstant ->yes ()) {
77+ $ newArrayBuilder = ConstantArrayTypeBuilder::createEmpty ();
78+
79+ foreach ($ argTypes as $ argType ) {
80+ /** @var array<int|string, ConstantIntegerType|ConstantStringType> $keyTypes */
81+ $ keyTypes = [];
82+ foreach ($ argType ->getConstantArrays () as $ constantArray ) {
83+ foreach ($ constantArray ->getKeyTypes () as $ keyType ) {
84+ $ keyTypes [$ keyType ->getValue ()] = $ keyType ;
85+ }
86+ }
87+
88+ foreach ($ keyTypes as $ keyType ) {
89+ $ newArrayBuilder ->setOffsetValueType (
90+ $ keyType ,
91+ $ argType ->getOffsetValueType ($ keyType ),
92+ !$ argType ->hasOffsetValueType ($ keyType )->yes (),
93+ );
94+ }
95+ }
5396
54- $ arrayType = new ArrayType ($ keyType , $ valueType );
55- return $ nonEmptyArray ? TypeCombinator::intersect ($ arrayType , new NonEmptyArrayType ()) : $ arrayType ;
56- }
97+ return $ newArrayBuilder ->getArray ();
98+ }
5799
58- /**
59- * @return Type[]
60- */
61- private function collectArrayTypes (FuncCall $ functionCall , Scope $ scope ): array
62- {
63- $ args = $ functionCall ->getArgs ();
100+ $ keyTypes = [];
101+ $ valueTypes = [];
102+ $ nonEmpty = false ;
103+ $ isList = true ;
104+ foreach ($ argTypes as $ key => $ argType ) {
105+ $ keyType = $ argType ->getIterableKeyType ();
106+ $ keyTypes [] = $ keyType ;
107+ $ valueTypes [] = $ argType ->getIterableValueType ();
108+
109+ if (!$ argType ->isList ()->yes ()) {
110+ $ isList = false ;
111+ }
64112
65- $ arrayTypes = [];
66- foreach ($ args as $ arg ) {
67- $ argType = $ scope ->getType ($ arg ->value );
68- if (!$ argType ->isArray ()->yes ()) {
113+ if (in_array ($ key , $ optionalArgTypes , true ) || !$ argType ->isIterableAtLeastOnce ()->yes ()) {
69114continue ;
70115}
71116
72- $ arrayTypes [] = $ arg ->unpack ? $ argType ->getIterableValueType () : $ argType ;
117+ $ nonEmpty = true ;
118+ }
119+
120+ $ keyType = TypeCombinator::union (...$ keyTypes );
121+ if ($ keyType instanceof NeverType) {
122+ return new ConstantArrayType ([], []);
123+ }
124+
125+ $ arrayType = new ArrayType (
126+ $ keyType ,
127+ TypeCombinator::union (...$ valueTypes ),
128+ );
129+
130+ if ($ nonEmpty ) {
131+ $ arrayType = TypeCombinator::intersect ($ arrayType , new NonEmptyArrayType ());
132+ }
133+ if ($ isList ) {
134+ $ arrayType = TypeCombinator::intersect ($ arrayType , new AccessoryArrayListType ());
73135}
74136
75- return $ arrayTypes ;
137+ return $ arrayType ;
76138}
77139
78140}
0 commit comments