4
4
5
5
use PhpParser \Node \Expr \FuncCall ;
6
6
use PHPStan \Analyser \Scope ;
7
- use PHPStan \Php \PhpVersion ;
8
7
use PHPStan \Reflection \FunctionReflection ;
9
- use PHPStan \ShouldNotHappenException ;
10
- use PHPStan \TrinaryLogic ;
11
- use PHPStan \Type \Accessory \AccessoryArrayListType ;
12
- use PHPStan \Type \Accessory \NonEmptyArrayType ;
13
- use PHPStan \Type \ArrayType ;
14
- use PHPStan \Type \Constant \ConstantArrayType ;
15
- use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
16
8
use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
17
- use PHPStan \Type \IntegerType ;
18
- use PHPStan \Type \MixedType ;
19
- use PHPStan \Type \NeverType ;
20
9
use PHPStan \Type \Type ;
21
- use PHPStan \Type \TypeCombinator ;
22
10
use function count ;
23
11
24
12
final class ArrayColumnFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
25
13
{
26
14
27
- public function __construct (private PhpVersion $ phpVersion )
15
+ public function __construct (
16
+ private ArrayColumnHelper $ arrayColumnHelper ,
17
+ )
28
18
{
29
19
}
30
20
@@ -46,167 +36,13 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
46
36
47
37
$ constantArrayTypes = $ arrayType ->getConstantArrays ();
48
38
if (count ($ constantArrayTypes ) === 1 ) {
49
- $ type = $ this ->handleConstantArray ($ constantArrayTypes [0 ], $ columnType , $ indexType , $ scope );
39
+ $ type = $ this ->arrayColumnHelper -> handleConstantArray ($ constantArrayTypes [0 ], $ columnType , $ indexType , $ scope );
50
40
if ($ type !== null ) {
51
41
return $ type ;
52
42
}
53
43
}
54
44
55
- return $ this ->handleAnyArray ($ arrayType , $ columnType , $ indexType , $ scope );
56
- }
57
-
58
- private function handleAnyArray (Type $ arrayType , Type $ columnType , ?Type $ indexType , Scope $ scope ): Type
59
- {
60
- $ iterableAtLeastOnce = $ arrayType ->isIterableAtLeastOnce ();
61
- if ($ iterableAtLeastOnce ->no ()) {
62
- return new ConstantArrayType ([], []);
63
- }
64
-
65
- $ iterableValueType = $ arrayType ->getIterableValueType ();
66
- $ returnValueType = $ this ->getOffsetOrProperty ($ iterableValueType , $ columnType , $ scope , false );
67
-
68
- if ($ returnValueType === null ) {
69
- $ returnValueType = $ this ->getOffsetOrProperty ($ iterableValueType , $ columnType , $ scope , true );
70
- $ iterableAtLeastOnce = TrinaryLogic::createMaybe ();
71
- if ($ returnValueType === null ) {
72
- throw new ShouldNotHappenException ();
73
- }
74
- }
75
-
76
- if ($ returnValueType instanceof NeverType) {
77
- return new ConstantArrayType ([], []);
78
- }
79
-
80
- if ($ indexType !== null ) {
81
- $ type = $ this ->getOffsetOrProperty ($ iterableValueType , $ indexType , $ scope , false );
82
- if ($ type !== null ) {
83
- $ returnKeyType = $ type ;
84
- } else {
85
- $ type = $ this ->getOffsetOrProperty ($ iterableValueType , $ indexType , $ scope , true );
86
- if ($ type !== null ) {
87
- $ returnKeyType = TypeCombinator::union ($ type , new IntegerType ());
88
- } else {
89
- $ returnKeyType = new IntegerType ();
90
- }
91
- }
92
- } else {
93
- $ returnKeyType = new IntegerType ();
94
- }
95
-
96
- $ returnType = new ArrayType ($ this ->castToArrayKeyType ($ returnKeyType ), $ returnValueType );
97
-
98
- if ($ iterableAtLeastOnce ->yes ()) {
99
- $ returnType = TypeCombinator::intersect ($ returnType , new NonEmptyArrayType ());
100
- }
101
- if ($ indexType === null ) {
102
- $ returnType = TypeCombinator::intersect ($ returnType , new AccessoryArrayListType ());
103
- }
104
-
105
- return $ returnType ;
106
- }
107
-
108
- private function handleConstantArray (ConstantArrayType $ arrayType , Type $ columnType , ?Type $ indexType , Scope $ scope ): ?Type
109
- {
110
- $ builder = ConstantArrayTypeBuilder::createEmpty ();
111
-
112
- foreach ($ arrayType ->getValueTypes () as $ i => $ iterableValueType ) {
113
- $ valueType = $ this ->getOffsetOrProperty ($ iterableValueType , $ columnType , $ scope , false );
114
- if ($ valueType === null ) {
115
- return null ;
116
- }
117
- if ($ valueType instanceof NeverType) {
118
- continue ;
119
- }
120
-
121
- if ($ indexType !== null ) {
122
- $ type = $ this ->getOffsetOrProperty ($ iterableValueType , $ indexType , $ scope , false );
123
- if ($ type !== null ) {
124
- $ keyType = $ type ;
125
- } else {
126
- $ type = $ this ->getOffsetOrProperty ($ iterableValueType , $ indexType , $ scope , true );
127
- if ($ type !== null ) {
128
- $ keyType = TypeCombinator::union ($ type , new IntegerType ());
129
- } else {
130
- $ keyType = null ;
131
- }
132
- }
133
- } else {
134
- $ keyType = null ;
135
- }
136
-
137
- if ($ keyType !== null ) {
138
- $ keyType = $ this ->castToArrayKeyType ($ keyType );
139
- }
140
- $ builder ->setOffsetValueType ($ keyType , $ valueType , $ arrayType ->isOptionalKey ($ i ));
141
- }
142
-
143
- return $ builder ->getArray ();
144
- }
145
-
146
- private function getOffsetOrProperty (Type $ type , Type $ offsetOrProperty , Scope $ scope , bool $ allowMaybe ): ?Type
147
- {
148
- $ offsetIsNull = $ offsetOrProperty ->isNull ();
149
- if ($ offsetIsNull ->yes ()) {
150
- return $ type ;
151
- }
152
-
153
- $ returnTypes = [];
154
-
155
- if ($ offsetIsNull ->maybe ()) {
156
- $ returnTypes [] = $ type ;
157
- }
158
-
159
- if (!$ type ->canAccessProperties ()->no ()) {
160
- $ propertyTypes = $ offsetOrProperty ->getConstantStrings ();
161
- if ($ propertyTypes === []) {
162
- return new MixedType ();
163
- }
164
- foreach ($ propertyTypes as $ propertyType ) {
165
- $ propertyName = $ propertyType ->getValue ();
166
- $ hasProperty = $ type ->hasProperty ($ propertyName );
167
- if ($ hasProperty ->maybe ()) {
168
- return $ allowMaybe ? new MixedType () : null ;
169
- }
170
- if (!$ hasProperty ->yes ()) {
171
- continue ;
172
- }
173
-
174
- $ returnTypes [] = $ type ->getProperty ($ propertyName , $ scope )->getReadableType ();
175
- }
176
- }
177
-
178
- if ($ type ->isOffsetAccessible ()->yes ()) {
179
- $ hasOffset = $ type ->hasOffsetValueType ($ offsetOrProperty );
180
- if (!$ allowMaybe && $ hasOffset ->maybe ()) {
181
- return null ;
182
- }
183
- if (!$ hasOffset ->no ()) {
184
- $ returnTypes [] = $ type ->getOffsetValueType ($ offsetOrProperty );
185
- }
186
- }
187
-
188
- if ($ returnTypes === []) {
189
- return new NeverType ();
190
- }
191
-
192
- return TypeCombinator::union (...$ returnTypes );
193
- }
194
-
195
- private function castToArrayKeyType (Type $ type ): Type
196
- {
197
- $ isArray = $ type ->isArray ();
198
- if ($ isArray ->yes ()) {
199
- return $ this ->phpVersion ->throwsTypeErrorForInternalFunctions () ? new NeverType () : new IntegerType ();
200
- }
201
- if ($ isArray ->no ()) {
202
- return $ type ->toArrayKey ();
203
- }
204
- $ withoutArrayType = TypeCombinator::remove ($ type , new ArrayType (new MixedType (), new MixedType ()));
205
- $ keyType = $ withoutArrayType ->toArrayKey ();
206
- if ($ this ->phpVersion ->throwsTypeErrorForInternalFunctions ()) {
207
- return $ keyType ;
208
- }
209
- return TypeCombinator::union ($ keyType , new IntegerType ());
45
+ return $ this ->arrayColumnHelper ->handleAnyArray ($ arrayType , $ columnType , $ indexType , $ scope );
210
46
}
211
47
212
48
}
0 commit comments