7
7
use PHPStan \DependencyInjection \AutowiredService ;
8
8
use PHPStan \Reflection \FunctionReflection ;
9
9
use PHPStan \Reflection \ParametersAcceptorSelector ;
10
+ use PHPStan \Type \Accessory \AccessoryArrayListType ;
10
11
use PHPStan \Type \Accessory \AccessoryLowercaseStringType ;
11
12
use PHPStan \Type \Accessory \AccessoryNonEmptyStringType ;
12
13
use PHPStan \Type \Accessory \AccessoryNonFalsyStringType ;
13
14
use PHPStan \Type \Accessory \AccessoryUppercaseStringType ;
15
+ use PHPStan \Type \Accessory \NonEmptyArrayType ;
16
+ use PHPStan \Type \ArrayType ;
14
17
use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
15
18
use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
16
19
use PHPStan \Type \IntersectionType ;
19
22
use PHPStan \Type \Type ;
20
23
use PHPStan \Type \TypeCombinator ;
21
24
use PHPStan \Type \TypeUtils ;
25
+ use PHPStan \Type \UnionType ;
22
26
use function array_key_exists ;
23
27
use function count ;
24
28
use function in_array ;
@@ -86,6 +90,12 @@ private function getPreliminarilyResolvedTypeFromFunctionCall(
86
90
return TypeUtils::toBenevolentUnion ($ defaultReturnType );
87
91
}
88
92
93
+ $ stringOrArray = new UnionType ([new StringType (), new ArrayType (new MixedType (), new MixedType ())]);
94
+ if (!$ stringOrArray ->isSuperTypeOf ($ subjectArgumentType )->yes ()) {
95
+ return $ defaultReturnType ;
96
+ }
97
+
98
+ $ replaceArgumentType = null ;
89
99
if (array_key_exists ($ functionReflection ->getName (), self ::FUNCTIONS_REPLACE_POSITION )) {
90
100
$ replaceArgumentPosition = self ::FUNCTIONS_REPLACE_POSITION [$ functionReflection ->getName ()];
91
101
@@ -94,68 +104,88 @@ private function getPreliminarilyResolvedTypeFromFunctionCall(
94
104
if ($ replaceArgumentType ->isArray ()->yes ()) {
95
105
$ replaceArgumentType = $ replaceArgumentType ->getIterableValueType ();
96
106
}
107
+ }
108
+ }
97
109
98
- $ accessories = [];
99
- if ($ subjectArgumentType ->isNonFalsyString ()->yes () && $ replaceArgumentType ->isNonFalsyString ()->yes ()) {
100
- $ accessories [] = new AccessoryNonFalsyStringType ();
101
- } elseif ($ subjectArgumentType ->isNonEmptyString ()->yes () && $ replaceArgumentType ->isNonEmptyString ()->yes ()) {
102
- $ accessories [] = new AccessoryNonEmptyStringType ();
103
- }
110
+ $ result = [];
104
111
105
- if ($ subjectArgumentType ->isLowercaseString ()->yes () && $ replaceArgumentType ->isLowercaseString ()->yes ()) {
106
- $ accessories [] = new AccessoryLowercaseStringType ();
107
- }
112
+ $ stringArgumentType = TypeCombinator::intersect (new StringType (), $ subjectArgumentType );
113
+ if ($ stringArgumentType ->isString ()->yes ()) {
114
+ $ result [] = $ this ->getReplaceType ($ stringArgumentType , $ replaceArgumentType );
115
+ }
108
116
109
- if ($ subjectArgumentType ->isUppercaseString ()->yes () && $ replaceArgumentType ->isUppercaseString ()->yes ()) {
110
- $ accessories [] = new AccessoryUppercaseStringType ();
117
+ $ arrayArgumentType = TypeCombinator::intersect (new ArrayType (new MixedType (), new MixedType ()), $ subjectArgumentType );
118
+ if ($ arrayArgumentType ->isArray ()->yes ()) {
119
+ $ keyShouldBeOptional = in_array (
120
+ $ functionReflection ->getName (),
121
+ ['preg_replace ' , 'preg_replace_callback ' , 'preg_replace_callback_array ' ],
122
+ true ,
123
+ );
124
+
125
+ $ constantArrays = $ arrayArgumentType ->getConstantArrays ();
126
+ if ($ constantArrays !== []) {
127
+ foreach ($ constantArrays as $ constantArray ) {
128
+ $ valueTypes = $ constantArray ->getValueTypes ();
129
+
130
+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
131
+ foreach ($ constantArray ->getKeyTypes () as $ index => $ keyType ) {
132
+ $ builder ->setOffsetValueType (
133
+ $ keyType ,
134
+ $ this ->getReplaceType ($ valueTypes [$ index ], $ replaceArgumentType ),
135
+ $ keyShouldBeOptional || $ constantArray ->isOptionalKey ($ index ),
136
+ );
137
+ }
138
+ $ result [] = $ builder ->getArray ();
111
139
}
112
-
113
- if (count ($ accessories ) > 0 ) {
114
- $ accessories [] = new StringType ();
115
- return new IntersectionType ($ accessories );
140
+ } else {
141
+ $ newArrayType = new ArrayType (
142
+ $ arrayArgumentType ->getIterableKeyType (),
143
+ $ this ->getReplaceType ($ arrayArgumentType ->getIterableValueType (), $ replaceArgumentType ),
144
+ );
145
+ if ($ arrayArgumentType ->isList ()->yes ()) {
146
+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new AccessoryArrayListType ());
116
147
}
148
+ if ($ arrayArgumentType ->isIterableAtLeastOnce ()->yes ()) {
149
+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new NonEmptyArrayType ());
150
+ }
151
+
152
+ $ result [] = $ newArrayType ;
117
153
}
118
154
}
119
155
120
- $ isStringSuperType = $ subjectArgumentType ->isString ();
121
- $ isArraySuperType = $ subjectArgumentType ->isArray ();
122
- $ compareSuperTypes = $ isStringSuperType ->compareTo ($ isArraySuperType );
123
- if ($ compareSuperTypes === $ isStringSuperType ) {
156
+ return TypeCombinator::union (...$ result );
157
+ }
158
+
159
+ private function getReplaceType (
160
+ Type $ subjectArgumentType ,
161
+ ?Type $ replaceArgumentType ,
162
+ ): Type
163
+ {
164
+ if ($ replaceArgumentType === null ) {
124
165
return new StringType ();
125
- } elseif ($ compareSuperTypes === $ isArraySuperType ) {
126
- $ subjectArrays = $ subjectArgumentType ->getArrays ();
127
- if (count ($ subjectArrays ) > 0 ) {
128
- $ result = [];
129
- foreach ($ subjectArrays as $ arrayType ) {
130
- $ constantArrays = $ arrayType ->getConstantArrays ();
131
-
132
- if (
133
- $ constantArrays !== []
134
- && in_array ($ functionReflection ->getName (), ['preg_replace ' , 'preg_replace_callback ' , 'preg_replace_callback_array ' ], true )
135
- ) {
136
- foreach ($ constantArrays as $ constantArray ) {
137
- $ generalizedArray = $ constantArray ->generalizeValues ();
138
-
139
- $ builder = ConstantArrayTypeBuilder::createEmpty ();
140
- // turn all keys optional
141
- foreach ($ constantArray ->getKeyTypes () as $ keyType ) {
142
- $ builder ->setOffsetValueType ($ keyType , $ generalizedArray ->getOffsetValueType ($ keyType ), true );
143
- }
144
- $ result [] = $ builder ->getArray ();
145
- }
146
-
147
- continue ;
148
- }
166
+ }
149
167
150
- $ result [] = $ arrayType ->generalizeValues ();
151
- }
168
+ $ accessories = [];
169
+ if ($ subjectArgumentType ->isNonFalsyString ()->yes () && $ replaceArgumentType ->isNonFalsyString ()->yes ()) {
170
+ $ accessories [] = new AccessoryNonFalsyStringType ();
171
+ } elseif ($ subjectArgumentType ->isNonEmptyString ()->yes () && $ replaceArgumentType ->isNonEmptyString ()->yes ()) {
172
+ $ accessories [] = new AccessoryNonEmptyStringType ();
173
+ }
152
174
153
- return TypeCombinator::union (...$ result );
154
- }
155
- return $ subjectArgumentType ;
175
+ if ($ subjectArgumentType ->isLowercaseString ()->yes () && $ replaceArgumentType ->isLowercaseString ()->yes ()) {
176
+ $ accessories [] = new AccessoryLowercaseStringType ();
177
+ }
178
+
179
+ if ($ subjectArgumentType ->isUppercaseString ()->yes () && $ replaceArgumentType ->isUppercaseString ()->yes ()) {
180
+ $ accessories [] = new AccessoryUppercaseStringType ();
181
+ }
182
+
183
+ if (count ($ accessories ) > 0 ) {
184
+ $ accessories [] = new StringType ();
185
+ return new IntersectionType ($ accessories );
156
186
}
157
187
158
- return $ defaultReturnType ;
188
+ return new StringType () ;
159
189
}
160
190
161
191
private function getSubjectType (
0 commit comments