44
55use PhpParser \Node \Expr \FuncCall ;
66use PHPStan \Analyser \Scope ;
7- use PHPStan \Php \PhpVersion ;
87use 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 ;
168use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
17- use PHPStan \Type \IntegerType ;
18- use PHPStan \Type \MixedType ;
19- use PHPStan \Type \NeverType ;
209use PHPStan \Type \Type ;
21- use PHPStan \Type \TypeCombinator ;
2210use function count ;
2311
2412final class ArrayColumnFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
2513{
2614
27- public function __construct (private PhpVersion $ phpVersion )
15+ public function __construct (
16+ private ArrayColumnHelper $ arrayColumnHelper ,
17+ )
2818 {
2919 }
3020
@@ -46,167 +36,13 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
4636
4737 $ constantArrayTypes = $ arrayType ->getConstantArrays ();
4838 if (count ($ constantArrayTypes ) === 1 ) {
49- $ type = $ this ->handleConstantArray ($ constantArrayTypes [0 ], $ columnType , $ indexType , $ scope );
39+ $ type = $ this ->arrayColumnHelper -> handleConstantArray ($ constantArrayTypes [0 ], $ columnType , $ indexType , $ scope );
5040 if ($ type !== null ) {
5141 return $ type ;
5242 }
5343 }
5444
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 );
21046 }
21147
21248}
0 commit comments