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,89 @@ 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 instanceof NeverType) {
113
+ $ result [] = $ this -> getReplaceType ( $ stringArgumentType , $ replaceArgumentType );
106
114
107
- if ($ subjectArgumentType ->isUppercaseString ()->yes () && $ replaceArgumentType ->isUppercaseString ()->yes ()) {
108
- $ accessories [] = new AccessoryUppercaseStringType ();
109
- }
115
+ $ subjectArgumentType = $ subjectArgumentType ->tryRemove ($ stringArgumentType );
116
+ }
110
117
111
- if (count ($ accessories ) > 0 ) {
112
- $ accessories [] = new StringType ();
113
- return new IntersectionType ($ accessories );
118
+ if ($ subjectArgumentType ->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 = $ subjectArgumentType ->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 ();
114
139
}
140
+ } else {
141
+ $ newArrayType = new ArrayType (
142
+ $ subjectArgumentType ->getIterableKeyType (),
143
+ $ this ->getReplaceType ($ subjectArgumentType ->getIterableValueType (), $ replaceArgumentType ),
144
+ );
145
+ if ($ subjectArgumentType ->isList ()->yes ()) {
146
+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new AccessoryArrayListType ());
147
+ }
148
+ if ($ subjectArgumentType ->isIterableAtLeastOnce ()->yes ()) {
149
+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new NonEmptyArrayType ());
150
+ }
151
+
152
+ $ result [] = $ newArrayType ;
115
153
}
116
154
}
117
155
118
- $ isStringSuperType = $ subjectArgumentType ->isString ();
119
- $ isArraySuperType = $ subjectArgumentType ->isArray ();
120
- $ compareSuperTypes = $ isStringSuperType ->compareTo ($ isArraySuperType );
121
- 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 ) {
122
165
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
- }
166
+ }
147
167
148
- $ result [] = $ arrayType ->generalizeValues ();
149
- }
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
+ }
150
174
151
- return TypeCombinator::union (...$ result );
152
- }
153
- 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 );
154
186
}
155
187
156
- return $ defaultReturnType ;
188
+ return new StringType () ;
157
189
}
158
190
159
191
private function getSubjectType (
0 commit comments