19
19
use PHPStan \Type \TypeTraverser ;
20
20
use PHPStan \Type \UnionType ;
21
21
use PHPStan \Type \VerbosityLevel ;
22
+ use function array_merge ;
22
23
use function count ;
23
24
use function sprintf ;
24
25
use function strpos ;
@@ -33,6 +34,7 @@ public function __construct(
33
34
private bool $ checkUnionTypes ,
34
35
private bool $ checkExplicitMixed ,
35
36
private bool $ checkImplicitMixed ,
37
+ private bool $ newRuleLevelHelper ,
36
38
private bool $ checkBenevolentUnionTypes ,
37
39
)
38
40
{
@@ -105,10 +107,148 @@ private function transformAcceptedType(Type $acceptingType, Type $acceptedType):
105
107
106
108
public function acceptsWithReason (Type $ acceptingType , Type $ acceptedType , bool $ strictTypes ): RuleLevelHelperAcceptsResult
107
109
{
108
- [$ acceptedType , $ checkForUnion ] = $ this ->transformAcceptedType ($ acceptingType , $ acceptedType );
109
- $ acceptingType = $ this ->transformCommonType ($ acceptingType );
110
+ if ($ this ->newRuleLevelHelper ) {
111
+ [$ acceptedType , $ checkForUnion ] = $ this ->transformAcceptedType ($ acceptingType , $ acceptedType );
112
+ $ acceptingType = $ this ->transformCommonType ($ acceptingType );
113
+
114
+ $ accepts = $ acceptingType ->acceptsWithReason ($ acceptedType , $ strictTypes );
115
+
116
+ return new RuleLevelHelperAcceptsResult (
117
+ $ checkForUnion ? $ accepts ->yes () : !$ accepts ->no (),
118
+ $ accepts ->reasons ,
119
+ );
120
+ }
121
+
122
+ $ checkForUnion = $ this ->checkUnionTypes ;
123
+
124
+ if ($ this ->checkBenevolentUnionTypes ) {
125
+ $ traverse = static function (Type $ type , callable $ traverse ) use (&$ checkForUnion ): Type {
126
+ if ($ type instanceof BenevolentUnionType) {
127
+ $ checkForUnion = true ;
128
+ return new UnionType ($ type ->getTypes ());
129
+ }
130
+
131
+ return $ traverse ($ type );
132
+ };
133
+
134
+ $ acceptedType = TypeTraverser::map ($ acceptedType , $ traverse );
135
+ }
136
+
137
+ if (
138
+ $ this ->checkExplicitMixed
139
+ ) {
140
+ $ traverse = static function (Type $ type , callable $ traverse ): Type {
141
+ if ($ type instanceof TemplateMixedType) {
142
+ return $ type ->toStrictMixedType ();
143
+ }
144
+ if (
145
+ $ type instanceof MixedType
146
+ && $ type ->isExplicitMixed ()
147
+ ) {
148
+ return new StrictMixedType ();
149
+ }
150
+
151
+ return $ traverse ($ type );
152
+ };
153
+ $ acceptingType = TypeTraverser::map ($ acceptingType , $ traverse );
154
+ $ acceptedType = TypeTraverser::map ($ acceptedType , $ traverse );
155
+ }
156
+
157
+ if (
158
+ $ this ->checkImplicitMixed
159
+ ) {
160
+ $ traverse = static function (Type $ type , callable $ traverse ): Type {
161
+ if ($ type instanceof TemplateMixedType) {
162
+ return $ type ->toStrictMixedType ();
163
+ }
164
+ if (
165
+ $ type instanceof MixedType
166
+ && !$ type ->isExplicitMixed ()
167
+ ) {
168
+ return new StrictMixedType ();
169
+ }
170
+
171
+ return $ traverse ($ type );
172
+ };
173
+ $ acceptingType = TypeTraverser::map ($ acceptingType , $ traverse );
174
+ $ acceptedType = TypeTraverser::map ($ acceptedType , $ traverse );
175
+ }
176
+
177
+ if (
178
+ !$ this ->checkNullables
179
+ && !$ acceptingType instanceof NullType
180
+ && !$ acceptedType instanceof NullType
181
+ && !$ acceptedType instanceof BenevolentUnionType
182
+ ) {
183
+ $ acceptedType = TypeCombinator::removeNull ($ acceptedType );
184
+ }
110
185
111
186
$ accepts = $ acceptingType ->acceptsWithReason ($ acceptedType , $ strictTypes );
187
+ if ($ accepts ->yes ()) {
188
+ return new RuleLevelHelperAcceptsResult (true , $ accepts ->reasons );
189
+ }
190
+ if ($ acceptingType instanceof UnionType) {
191
+ $ reasons = [];
192
+ foreach ($ acceptingType ->getTypes () as $ innerType ) {
193
+ $ accepts = self ::acceptsWithReason ($ innerType , $ acceptedType , $ strictTypes );
194
+ if ($ accepts ->result ) {
195
+ return $ accepts ;
196
+ }
197
+
198
+ $ reasons = array_merge ($ reasons , $ accepts ->reasons );
199
+ }
200
+
201
+ return new RuleLevelHelperAcceptsResult (false , $ reasons );
202
+ }
203
+
204
+ if (
205
+ $ acceptedType ->isArray ()->yes ()
206
+ && $ acceptingType ->isArray ()->yes ()
207
+ && (
208
+ $ acceptedType ->isConstantArray ()->no ()
209
+ || !$ acceptedType ->isIterableAtLeastOnce ()->no ()
210
+ )
211
+ && $ acceptingType ->isConstantArray ()->no ()
212
+ ) {
213
+ if ($ acceptingType ->isIterableAtLeastOnce ()->yes () && !$ acceptedType ->isIterableAtLeastOnce ()->yes ()) {
214
+ $ verbosity = VerbosityLevel::getRecommendedLevelByType ($ acceptingType , $ acceptedType );
215
+ return new RuleLevelHelperAcceptsResult (false , [
216
+ sprintf (
217
+ '%s %s empty. ' ,
218
+ $ acceptedType ->describe ($ verbosity ),
219
+ $ acceptedType ->isIterableAtLeastOnce ()->no () ? 'is ' : 'might be ' ,
220
+ ),
221
+ ]);
222
+ }
223
+
224
+ if (
225
+ $ acceptingType ->isList ()->yes ()
226
+ && !$ acceptedType ->isList ()->yes ()
227
+ ) {
228
+ $ report = $ checkForUnion || $ acceptedType ->isList ()->no ();
229
+
230
+ if ($ report ) {
231
+ $ verbosity = VerbosityLevel::getRecommendedLevelByType ($ acceptingType , $ acceptedType );
232
+ return new RuleLevelHelperAcceptsResult (false , [
233
+ sprintf (
234
+ '%s %s a list. ' ,
235
+ $ acceptedType ->describe ($ verbosity ),
236
+ $ acceptedType ->isList ()->no () ? 'is not ' : 'might not be ' ,
237
+ ),
238
+ ]);
239
+ }
240
+ }
241
+
242
+ return self ::acceptsWithReason (
243
+ $ acceptingType ->getIterableKeyType (),
244
+ $ acceptedType ->getIterableKeyType (),
245
+ $ strictTypes ,
246
+ )->and (self ::acceptsWithReason (
247
+ $ acceptingType ->getIterableValueType (),
248
+ $ acceptedType ->getIterableValueType (),
249
+ $ strictTypes ,
250
+ ));
251
+ }
112
252
113
253
return new RuleLevelHelperAcceptsResult (
114
254
$ checkForUnion ? $ accepts ->yes () : !$ accepts ->no (),
0 commit comments