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