25
25
use PHPStan \Type \StringType ;
26
26
use PHPStan \Type \Type ;
27
27
use PHPStan \Type \TypeCombinator ;
28
+ use PHPStan \Type \UnionType ;
29
+
30
+ use PHPStan \Type \VerbosityLevel ;
31
+
28
32
use function array_key_exists ;
29
33
use function array_merge ;
30
34
use function hexdec ;
@@ -56,9 +60,14 @@ public function __construct(private ReflectionProvider $reflectionProvider, priv
56
60
57
61
private function getOffsetValueType (Type $ inputType , Type $ offsetType , ?Type $ filterType , ?Type $ flagsType ): Type
58
62
{
59
- $ inexistentOffsetType = $ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType )
60
- ? new ConstantBooleanType (false )
61
- : new NullType ();
63
+ $ hasNullOnFailure = $ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType );
64
+ if ($ hasNullOnFailure ->yes ()) {
65
+ $ inexistentOffsetType = new ConstantBooleanType (false );
66
+ } elseif ($ hasNullOnFailure ->no ()) {
67
+ $ inexistentOffsetType = new NullType ();
68
+ } else {
69
+ $ inexistentOffsetType = new UnionType ([new ConstantBooleanType (false ), new NullType ()]);
70
+ }
62
71
63
72
$ hasOffsetValueType = $ inputType ->hasOffsetValueType ($ offsetType );
64
73
if ($ hasOffsetValueType ->no ()) {
@@ -120,20 +129,44 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
120
129
}
121
130
122
131
$ hasOptions = $ this ->hasOptions ($ flagsType );
132
+ if ($ hasOptions ->maybe ()) {
133
+ // Too complicated
134
+ return $ mixedType ;
135
+ }
136
+
123
137
$ options = $ hasOptions ->yes () ? $ this ->getOptions ($ flagsType , $ filterValue ) : [];
124
138
125
- $ defaultType = $ options ['default ' ] ?? ($ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType )
126
- ? new NullType ()
127
- : new ConstantBooleanType (false ));
139
+ if (isset ($ options ['default ' ])) {
140
+ $ defaultType = $ options ['default ' ];
141
+ } else {
142
+ $ hasNullOnFailure = $ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType );
143
+ if ($ hasNullOnFailure ->yes ()) {
144
+ $ defaultType = new NullType ();
145
+ } elseif ($ hasNullOnFailure ->no ()) {
146
+ $ defaultType = new ConstantBooleanType (false );
147
+ } else {
148
+ $ defaultType = new UnionType ([new ConstantBooleanType (false ), new NullType ()]);
149
+ }
150
+ }
128
151
129
- $ inputIsArray = $ inputType ->isArray ();
130
152
$ hasRequireArrayFlag = $ this ->hasFlag ('FILTER_REQUIRE_ARRAY ' , $ flagsType );
131
- if ($ inputIsArray ->no () && $ hasRequireArrayFlag ) {
153
+ if ($ hasRequireArrayFlag ->maybe ()) {
154
+ // Too complicated
155
+ return $ mixedType ;
156
+ }
157
+
158
+ $ inputIsArray = $ inputType ->isArray ();
159
+ if ($ inputIsArray ->no () && $ hasRequireArrayFlag ->yes ()) {
132
160
return $ defaultType ;
133
161
}
134
162
135
163
$ hasForceArrayFlag = $ this ->hasFlag ('FILTER_FORCE_ARRAY ' , $ flagsType );
136
- if ($ inputIsArray ->yes () && ($ hasRequireArrayFlag || $ hasForceArrayFlag )) {
164
+ if ($ hasRequireArrayFlag ->no () && $ hasForceArrayFlag ->maybe ()) {
165
+ // Too complicated
166
+ return $ mixedType ;
167
+ }
168
+
169
+ if ($ inputIsArray ->yes () && ($ hasRequireArrayFlag ->yes () || $ hasForceArrayFlag ->yes ())) {
137
170
$ inputArrayKeyType = $ inputType ->getIterableKeyType ();
138
171
$ inputType = $ inputType ->getIterableValueType ();
139
172
}
@@ -147,9 +180,11 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
147
180
$ type = $ exactType ?? $ this ->getFilterTypeMap ()[$ filterValue ] ?? $ mixedType ;
148
181
$ type = $ this ->applyRangeOptions ($ type , $ options , $ defaultType );
149
182
150
- if ($ inputType ->isNonEmptyString ()->yes ()
183
+ if (
184
+ $ inputType ->isNonEmptyString ()->yes ()
151
185
&& $ type ->isString ()->yes ()
152
- && !$ this ->canStringBeSanitized ($ filterValue , $ flagsType )) {
186
+ && $ this ->canStringBeSanitized ($ filterValue , $ flagsType )->no ()
187
+ ) {
153
188
$ accessory = new AccessoryNonEmptyStringType ();
154
189
if ($ inputType ->isNonFalsyString ()->yes ()) {
155
190
$ accessory = new AccessoryNonFalsyStringType ();
@@ -163,14 +198,14 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
163
198
}
164
199
}
165
200
166
- if ($ hasRequireArrayFlag ) {
167
- $ type = new ArrayType ($ inputArrayKeyType ?? $ mixedType , $ type );
201
+ if ($ hasRequireArrayFlag-> yes () ) {
202
+ $ type = new ArrayType ($ inputArrayKeyType ?? new MixedType () , $ type );
168
203
if (!$ inputIsArray ->yes ()) {
169
204
$ type = TypeCombinator::union ($ type , $ defaultType );
170
205
}
171
206
}
172
207
173
- if (! $ hasRequireArrayFlag && $ hasForceArrayFlag ) {
208
+ if ($ hasRequireArrayFlag-> no () && $ hasForceArrayFlag-> yes () ) {
174
209
return new ArrayType ($ inputArrayKeyType ?? $ mixedType , $ type );
175
210
}
176
211
@@ -329,16 +364,19 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp
329
364
}
330
365
331
366
if ($ in instanceof ConstantStringType) {
332
- $ value = $ in ->getValue ();
333
367
$ allowOctal = $ this ->hasFlag ('FILTER_FLAG_ALLOW_OCTAL ' , $ flagsType );
334
368
$ allowHex = $ this ->hasFlag ('FILTER_FLAG_ALLOW_HEX ' , $ flagsType );
369
+ if ($ allowOctal ->maybe () || $ allowHex ->maybe ()) {
370
+ return null ;
371
+ }
335
372
336
- if ($ allowOctal && preg_match ('/\A0[oO][0-7]+\z/ ' , $ value ) === 1 ) {
373
+ $ value = $ in ->getValue ();
374
+ if ($ allowOctal ->yes () && preg_match ('/\A0[oO][0-7]+\z/ ' , $ value ) === 1 ) {
337
375
$ octalValue = octdec ($ value );
338
376
return is_int ($ octalValue ) ? new ConstantIntegerType ($ octalValue ) : $ defaultType ;
339
377
}
340
378
341
- if ($ allowHex && preg_match ('/\A0[xX][0-9A-Fa-f]+\z/ ' , $ value ) === 1 ) {
379
+ if ($ allowHex-> yes () && preg_match ('/\A0[xX][0-9A-Fa-f]+\z/ ' , $ value ) === 1 ) {
342
380
$ hexValue = hexdec ($ value );
343
381
return is_int ($ hexValue ) ? new ConstantIntegerType ($ hexValue ) : $ defaultType ;
344
382
}
@@ -348,7 +386,7 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp
348
386
}
349
387
350
388
if ($ filterValue === $ this ->getConstant ('FILTER_DEFAULT ' )) {
351
- if (! $ this ->canStringBeSanitized ($ filterValue , $ flagsType ) && $ in ->isString ()->yes ()) {
389
+ if ($ this ->canStringBeSanitized ($ filterValue , $ flagsType)-> no ( ) && $ in ->isString ()->yes ()) {
352
390
return $ in ;
353
391
}
354
392
@@ -443,20 +481,23 @@ private function getOptions(Type $flagsType, int $filterValue): array
443
481
/**
444
482
* @param non-empty-string $flagName
445
483
*/
446
- private function hasFlag (string $ flagName , ?Type $ flagsType ): bool
484
+ private function hasFlag (string $ flagName , ?Type $ flagsType ): TrinaryLogic
447
485
{
448
486
$ flag = $ this ->getConstant ($ flagName );
449
487
if ($ flag === null ) {
450
- return false ;
488
+ return TrinaryLogic:: createNo () ;
451
489
}
452
490
453
- if ($ flagsType === null ) {
454
- return false ;
491
+ if ($ flagsType === null ) { // Will default to 0
492
+ return TrinaryLogic:: createNo () ;
455
493
}
456
494
457
495
$ type = $ this ->getFlagsValue ($ flagsType );
496
+ if (!$ type instanceof ConstantIntegerType) {
497
+ return TrinaryLogic::createMaybe ();
498
+ }
458
499
459
- return $ type instanceof ConstantIntegerType && ( $ type ->getValue () & $ flag ) === $ flag ;
500
+ return TrinaryLogic:: createFromBoolean (( $ type ->getValue () & $ flag ) === $ flag) ;
460
501
}
461
502
462
503
private function getFlagsValue (Type $ exprType ): Type
@@ -465,25 +506,36 @@ private function getFlagsValue(Type $exprType): Type
465
506
return $ exprType ;
466
507
}
467
508
468
- return $ exprType ->getOffsetValueType ($ this ->flagsString );
509
+ $ hasOffsetValue = $ exprType ->hasOffsetValueType ($ this ->flagsString );
510
+ if ($ hasOffsetValue ->no ()) {
511
+ return new ConstantIntegerType (0 );
512
+ }
513
+ if ($ hasOffsetValue ->yes ()) {
514
+ return $ exprType ->getOffsetValueType ($ this ->flagsString );
515
+ }
516
+
517
+ return TypeCombinator::union (
518
+ new ConstantIntegerType (0 ),
519
+ $ exprType ->getOffsetValueType ($ this ->flagsString ),
520
+ );
469
521
}
470
522
471
- private function canStringBeSanitized (int $ filterValue , ?Type $ flagsType ): bool
523
+ private function canStringBeSanitized (int $ filterValue , ?Type $ flagsType ): TrinaryLogic
472
524
{
473
525
// If it is a validation filter, the string will not be changed
474
526
if (($ filterValue & self ::VALIDATION_FILTER_BITMASK ) !== 0 ) {
475
- return false ;
527
+ return TrinaryLogic:: createNo () ;
476
528
}
477
529
478
530
// FILTER_DEFAULT will not sanitize, unless it has FILTER_FLAG_STRIP_LOW,
479
531
// FILTER_FLAG_STRIP_HIGH, or FILTER_FLAG_STRIP_BACKTICK
480
532
if ($ filterValue === $ this ->getConstant ('FILTER_DEFAULT ' )) {
481
533
return $ this ->hasFlag ('FILTER_FLAG_STRIP_LOW ' , $ flagsType )
482
- || $ this ->hasFlag ('FILTER_FLAG_STRIP_HIGH ' , $ flagsType )
483
- || $ this ->hasFlag ('FILTER_FLAG_STRIP_BACKTICK ' , $ flagsType );
534
+ -> or ( $ this ->hasFlag ('FILTER_FLAG_STRIP_HIGH ' , $ flagsType) )
535
+ -> or ( $ this ->hasFlag ('FILTER_FLAG_STRIP_BACKTICK ' , $ flagsType) );
484
536
}
485
537
486
- return true ;
538
+ return TrinaryLogic:: createYes () ;
487
539
}
488
540
489
541
}
0 commit comments