2525use PHPStan \Type \StringType ;
2626use PHPStan \Type \Type ;
2727use PHPStan \Type \TypeCombinator ;
28+ use PHPStan \Type \UnionType ;
29+
30+ use PHPStan \Type \VerbosityLevel ;
31+
2832use function array_key_exists ;
2933use function array_merge ;
3034use function hexdec ;
@@ -56,9 +60,14 @@ public function __construct(private ReflectionProvider $reflectionProvider, priv
5660
5761 private function getOffsetValueType (Type $ inputType , Type $ offsetType , ?Type $ filterType , ?Type $ flagsType ): Type
5862 {
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+ }
6271
6372 $ hasOffsetValueType = $ inputType ->hasOffsetValueType ($ offsetType );
6473 if ($ hasOffsetValueType ->no ()) {
@@ -120,20 +129,44 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
120129 }
121130
122131 $ hasOptions = $ this ->hasOptions ($ flagsType );
132+ if ($ hasOptions ->maybe ()) {
133+ // Too complicated
134+ return $ mixedType ;
135+ }
136+
123137 $ options = $ hasOptions ->yes () ? $ this ->getOptions ($ flagsType , $ filterValue ) : [];
124138
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+ }
128151
129- $ inputIsArray = $ inputType ->isArray ();
130152 $ 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 ()) {
132160 return $ defaultType ;
133161 }
134162
135163 $ 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 ())) {
137170 $ inputArrayKeyType = $ inputType ->getIterableKeyType ();
138171 $ inputType = $ inputType ->getIterableValueType ();
139172 }
@@ -147,9 +180,11 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
147180 $ type = $ exactType ?? $ this ->getFilterTypeMap ()[$ filterValue ] ?? $ mixedType ;
148181 $ type = $ this ->applyRangeOptions ($ type , $ options , $ defaultType );
149182
150- if ($ inputType ->isNonEmptyString ()->yes ()
183+ if (
184+ $ inputType ->isNonEmptyString ()->yes ()
151185 && $ type ->isString ()->yes ()
152- && !$ this ->canStringBeSanitized ($ filterValue , $ flagsType )) {
186+ && $ this ->canStringBeSanitized ($ filterValue , $ flagsType )->no ()
187+ ) {
153188 $ accessory = new AccessoryNonEmptyStringType ();
154189 if ($ inputType ->isNonFalsyString ()->yes ()) {
155190 $ accessory = new AccessoryNonFalsyStringType ();
@@ -163,14 +198,14 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
163198 }
164199 }
165200
166- if ($ hasRequireArrayFlag ) {
167- $ type = new ArrayType ($ inputArrayKeyType ?? $ mixedType , $ type );
201+ if ($ hasRequireArrayFlag-> yes () ) {
202+ $ type = new ArrayType ($ inputArrayKeyType ?? new MixedType () , $ type );
168203 if (!$ inputIsArray ->yes ()) {
169204 $ type = TypeCombinator::union ($ type , $ defaultType );
170205 }
171206 }
172207
173- if (! $ hasRequireArrayFlag && $ hasForceArrayFlag ) {
208+ if ($ hasRequireArrayFlag-> no () && $ hasForceArrayFlag-> yes () ) {
174209 return new ArrayType ($ inputArrayKeyType ?? $ mixedType , $ type );
175210 }
176211
@@ -329,16 +364,19 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp
329364 }
330365
331366 if ($ in instanceof ConstantStringType) {
332- $ value = $ in ->getValue ();
333367 $ allowOctal = $ this ->hasFlag ('FILTER_FLAG_ALLOW_OCTAL ' , $ flagsType );
334368 $ allowHex = $ this ->hasFlag ('FILTER_FLAG_ALLOW_HEX ' , $ flagsType );
369+ if ($ allowOctal ->maybe () || $ allowHex ->maybe ()) {
370+ return null ;
371+ }
335372
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 ) {
337375 $ octalValue = octdec ($ value );
338376 return is_int ($ octalValue ) ? new ConstantIntegerType ($ octalValue ) : $ defaultType ;
339377 }
340378
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 ) {
342380 $ hexValue = hexdec ($ value );
343381 return is_int ($ hexValue ) ? new ConstantIntegerType ($ hexValue ) : $ defaultType ;
344382 }
@@ -348,7 +386,7 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp
348386 }
349387
350388 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 ()) {
352390 return $ in ;
353391 }
354392
@@ -443,20 +481,23 @@ private function getOptions(Type $flagsType, int $filterValue): array
443481 /**
444482 * @param non-empty-string $flagName
445483 */
446- private function hasFlag (string $ flagName , ?Type $ flagsType ): bool
484+ private function hasFlag (string $ flagName , ?Type $ flagsType ): TrinaryLogic
447485 {
448486 $ flag = $ this ->getConstant ($ flagName );
449487 if ($ flag === null ) {
450- return false ;
488+ return TrinaryLogic:: createNo () ;
451489 }
452490
453- if ($ flagsType === null ) {
454- return false ;
491+ if ($ flagsType === null ) { // Will default to 0
492+ return TrinaryLogic:: createNo () ;
455493 }
456494
457495 $ type = $ this ->getFlagsValue ($ flagsType );
496+ if (!$ type instanceof ConstantIntegerType) {
497+ return TrinaryLogic::createMaybe ();
498+ }
458499
459- return $ type instanceof ConstantIntegerType && ( $ type ->getValue () & $ flag ) === $ flag ;
500+ return TrinaryLogic:: createFromBoolean (( $ type ->getValue () & $ flag ) === $ flag) ;
460501 }
461502
462503 private function getFlagsValue (Type $ exprType ): Type
@@ -465,25 +506,36 @@ private function getFlagsValue(Type $exprType): Type
465506 return $ exprType ;
466507 }
467508
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+ );
469521 }
470522
471- private function canStringBeSanitized (int $ filterValue , ?Type $ flagsType ): bool
523+ private function canStringBeSanitized (int $ filterValue , ?Type $ flagsType ): TrinaryLogic
472524 {
473525 // If it is a validation filter, the string will not be changed
474526 if (($ filterValue & self ::VALIDATION_FILTER_BITMASK ) !== 0 ) {
475- return false ;
527+ return TrinaryLogic:: createNo () ;
476528 }
477529
478530 // FILTER_DEFAULT will not sanitize, unless it has FILTER_FLAG_STRIP_LOW,
479531 // FILTER_FLAG_STRIP_HIGH, or FILTER_FLAG_STRIP_BACKTICK
480532 if ($ filterValue === $ this ->getConstant ('FILTER_DEFAULT ' )) {
481533 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) );
484536 }
485537
486- return true ;
538+ return TrinaryLogic:: createYes () ;
487539 }
488540
489541}
0 commit comments