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