2626use PHPStan \Type \StringType ;
2727use PHPStan \Type \Type ;
2828use PHPStan \Type \TypeCombinator ;
29+ use PHPStan \Type \UnionType ;
30+
31+ use PHPStan \Type \VerbosityLevel ;
32+
2933use function array_key_exists ;
3034use function array_merge ;
3135use function hexdec ;
@@ -57,9 +61,14 @@ public function __construct(private ReflectionProvider $reflectionProvider, priv
5761
5862 private function getOffsetValueType (Type $ inputType , Type $ offsetType , ?Type $ filterType , ?Type $ flagsType ): Type
5963 {
60- $ inexistentOffsetType = $ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType )
61- ? new ConstantBooleanType (false )
62- : new NullType ();
64+ $ hasNullOnFailure = $ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType );
65+ if ($ hasNullOnFailure ->yes ()) {
66+ $ inexistentOffsetType = new ConstantBooleanType (false );
67+ } elseif ($ hasNullOnFailure ->no ()) {
68+ $ inexistentOffsetType = new NullType ();
69+ } else {
70+ $ inexistentOffsetType = new UnionType ([new ConstantBooleanType (false ), new NullType ()]);
71+ }
6372
6473 $ hasOffsetValueType = $ inputType ->hasOffsetValueType ($ offsetType );
6574 if ($ hasOffsetValueType ->no ()) {
@@ -121,24 +130,49 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
121130 }
122131
123132 $ hasOptions = $ this ->hasOptions ($ flagsType );
133+ if ($ hasOptions ->maybe ()) {
134+ // Too complicated
135+ return $ mixedType ;
136+ }
137+
124138 $ options = $ hasOptions ->yes () ? $ this ->getOptions ($ flagsType , $ filterValue ) : [];
125139
126- $ defaultType = $ options ['default ' ] ?? ($ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType )
127- ? new NullType ()
128- : new ConstantBooleanType (false ));
140+ if (isset ($ options ['default ' ])) {
141+ $ defaultType = $ options ['default ' ];
142+ } else {
143+ $ hasNullOnFailure = $ this ->hasFlag ('FILTER_NULL_ON_FAILURE ' , $ flagsType );
144+ if ($ hasNullOnFailure ->yes ()) {
145+ $ defaultType = new NullType ();
146+ } elseif ($ hasNullOnFailure ->no ()) {
147+ $ defaultType = new ConstantBooleanType (false );
148+ } else {
149+ $ defaultType = new UnionType ([new ConstantBooleanType (false ), new NullType ()]);
150+ }
151+ }
152+
153+ $ hasRequireArrayFlag = $ this ->hasFlag ('FILTER_REQUIRE_ARRAY ' , $ flagsType );
154+ if ($ hasRequireArrayFlag ->maybe ()) {
155+ // Too complicated
156+ return $ mixedType ;
157+ }
129158
130159 $ inputIsArray = $ inputType ->isArray ();
131160 $ hasRequireArrayFlag = $ this ->hasFlag ('FILTER_REQUIRE_ARRAY ' , $ flagsType );
132- if ($ inputIsArray ->no () && $ hasRequireArrayFlag ) {
133- if ($ this ->hasFlag ('FILTER_THROW_ON_FAILURE ' , $ flagsType )) {
161+ if ($ inputIsArray ->no () && $ hasRequireArrayFlag-> yes () ) {
162+ if ($ this ->hasFlag ('FILTER_THROW_ON_FAILURE ' , $ flagsType )-> yes () ) {
134163 return new ErrorType ();
135164 }
136165
137166 return $ defaultType ;
138167 }
139168
140169 $ hasForceArrayFlag = $ this ->hasFlag ('FILTER_FORCE_ARRAY ' , $ flagsType );
141- if ($ inputIsArray ->yes () && ($ hasRequireArrayFlag || $ hasForceArrayFlag )) {
170+ if ($ hasRequireArrayFlag ->no () && $ hasForceArrayFlag ->maybe ()) {
171+ // Too complicated
172+ return $ mixedType ;
173+ }
174+
175+ if ($ inputIsArray ->yes () && ($ hasRequireArrayFlag ->yes () || $ hasForceArrayFlag ->yes ())) {
142176 $ inputArrayKeyType = $ inputType ->getIterableKeyType ();
143177 $ inputType = $ inputType ->getIterableValueType ();
144178 }
@@ -152,9 +186,11 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
152186 $ type = $ exactType ?? $ this ->getFilterTypeMap ()[$ filterValue ] ?? $ mixedType ;
153187 $ type = $ this ->applyRangeOptions ($ type , $ options , $ defaultType );
154188
155- if ($ inputType ->isNonEmptyString ()->yes ()
189+ if (
190+ $ inputType ->isNonEmptyString ()->yes ()
156191 && $ type ->isString ()->yes ()
157- && !$ this ->canStringBeSanitized ($ filterValue , $ flagsType )) {
192+ && $ this ->canStringBeSanitized ($ filterValue , $ flagsType )->no ()
193+ ) {
158194 $ accessory = new AccessoryNonEmptyStringType ();
159195 if ($ inputType ->isNonFalsyString ()->yes ()) {
160196 $ accessory = new AccessoryNonFalsyStringType ();
@@ -168,18 +204,18 @@ public function getType(Type $inputType, ?Type $filterType, ?Type $flagsType): T
168204 }
169205 }
170206
171- if ($ hasRequireArrayFlag ) {
172- $ type = new ArrayType ($ inputArrayKeyType ?? $ mixedType , $ type );
207+ if ($ hasRequireArrayFlag-> yes () ) {
208+ $ type = new ArrayType ($ inputArrayKeyType ?? new MixedType () , $ type );
173209 if (!$ inputIsArray ->yes ()) {
174210 $ type = TypeCombinator::union ($ type , $ defaultType );
175211 }
176212 }
177213
178- if (! $ hasRequireArrayFlag && $ hasForceArrayFlag ) {
214+ if ($ hasRequireArrayFlag-> no () && $ hasForceArrayFlag-> yes () ) {
179215 return new ArrayType ($ inputArrayKeyType ?? $ mixedType , $ type );
180216 }
181217
182- if ($ this ->hasFlag ('FILTER_THROW_ON_FAILURE ' , $ flagsType )) {
218+ if ($ this ->hasFlag ('FILTER_THROW_ON_FAILURE ' , $ flagsType )-> yes () ) {
183219 $ type = TypeCombinator::remove ($ type , $ defaultType );
184220 }
185221
@@ -338,16 +374,19 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp
338374 }
339375
340376 if ($ in instanceof ConstantStringType) {
341- $ value = $ in ->getValue ();
342377 $ allowOctal = $ this ->hasFlag ('FILTER_FLAG_ALLOW_OCTAL ' , $ flagsType );
343378 $ allowHex = $ this ->hasFlag ('FILTER_FLAG_ALLOW_HEX ' , $ flagsType );
379+ if ($ allowOctal ->maybe () || $ allowHex ->maybe ()) {
380+ return null ;
381+ }
344382
345- if ($ allowOctal && preg_match ('/\A0[oO][0-7]+\z/ ' , $ value ) === 1 ) {
383+ $ value = $ in ->getValue ();
384+ if ($ allowOctal ->yes () && preg_match ('/\A0[oO][0-7]+\z/ ' , $ value ) === 1 ) {
346385 $ octalValue = octdec ($ value );
347386 return is_int ($ octalValue ) ? new ConstantIntegerType ($ octalValue ) : $ defaultType ;
348387 }
349388
350- if ($ allowHex && preg_match ('/\A0[xX][0-9A-Fa-f]+\z/ ' , $ value ) === 1 ) {
389+ if ($ allowHex-> yes () && preg_match ('/\A0[xX][0-9A-Fa-f]+\z/ ' , $ value ) === 1 ) {
351390 $ hexValue = hexdec ($ value );
352391 return is_int ($ hexValue ) ? new ConstantIntegerType ($ hexValue ) : $ defaultType ;
353392 }
@@ -357,7 +396,7 @@ private function determineExactType(Type $in, int $filterValue, Type $defaultTyp
357396 }
358397
359398 if ($ filterValue === $ this ->getConstant ('FILTER_DEFAULT ' )) {
360- if (! $ this ->canStringBeSanitized ($ filterValue , $ flagsType ) && $ in ->isString ()->yes ()) {
399+ if ($ this ->canStringBeSanitized ($ filterValue , $ flagsType)-> no ( ) && $ in ->isString ()->yes ()) {
361400 return $ in ;
362401 }
363402
@@ -452,20 +491,23 @@ private function getOptions(Type $flagsType, int $filterValue): array
452491 /**
453492 * @param non-empty-string $flagName
454493 */
455- private function hasFlag (string $ flagName , ?Type $ flagsType ): bool
494+ private function hasFlag (string $ flagName , ?Type $ flagsType ): TrinaryLogic
456495 {
457496 $ flag = $ this ->getConstant ($ flagName );
458497 if ($ flag === null ) {
459- return false ;
498+ return TrinaryLogic:: createNo () ;
460499 }
461500
462- if ($ flagsType === null ) {
463- return false ;
501+ if ($ flagsType === null ) { // Will default to 0
502+ return TrinaryLogic:: createNo () ;
464503 }
465504
466505 $ type = $ this ->getFlagsValue ($ flagsType );
506+ if (!$ type instanceof ConstantIntegerType) {
507+ return TrinaryLogic::createMaybe ();
508+ }
467509
468- return $ type instanceof ConstantIntegerType && ( $ type ->getValue () & $ flag ) === $ flag ;
510+ return TrinaryLogic:: createFromBoolean (( $ type ->getValue () & $ flag ) === $ flag) ;
469511 }
470512
471513 private function getFlagsValue (Type $ exprType ): Type
@@ -474,25 +516,36 @@ private function getFlagsValue(Type $exprType): Type
474516 return $ exprType ;
475517 }
476518
477- return $ exprType ->getOffsetValueType ($ this ->flagsString );
519+ $ hasOffsetValue = $ exprType ->hasOffsetValueType ($ this ->flagsString );
520+ if ($ hasOffsetValue ->no ()) {
521+ return new ConstantIntegerType (0 );
522+ }
523+ if ($ hasOffsetValue ->yes ()) {
524+ return $ exprType ->getOffsetValueType ($ this ->flagsString );
525+ }
526+
527+ return TypeCombinator::union (
528+ new ConstantIntegerType (0 ),
529+ $ exprType ->getOffsetValueType ($ this ->flagsString ),
530+ );
478531 }
479532
480- private function canStringBeSanitized (int $ filterValue , ?Type $ flagsType ): bool
533+ private function canStringBeSanitized (int $ filterValue , ?Type $ flagsType ): TrinaryLogic
481534 {
482535 // If it is a validation filter, the string will not be changed
483536 if (($ filterValue & self ::VALIDATION_FILTER_BITMASK ) !== 0 ) {
484- return false ;
537+ return TrinaryLogic:: createNo () ;
485538 }
486539
487540 // FILTER_DEFAULT will not sanitize, unless it has FILTER_FLAG_STRIP_LOW,
488541 // FILTER_FLAG_STRIP_HIGH, or FILTER_FLAG_STRIP_BACKTICK
489542 if ($ filterValue === $ this ->getConstant ('FILTER_DEFAULT ' )) {
490543 return $ this ->hasFlag ('FILTER_FLAG_STRIP_LOW ' , $ flagsType )
491- || $ this ->hasFlag ('FILTER_FLAG_STRIP_HIGH ' , $ flagsType )
492- || $ this ->hasFlag ('FILTER_FLAG_STRIP_BACKTICK ' , $ flagsType );
544+ -> or ( $ this ->hasFlag ('FILTER_FLAG_STRIP_HIGH ' , $ flagsType) )
545+ -> or ( $ this ->hasFlag ('FILTER_FLAG_STRIP_BACKTICK ' , $ flagsType) );
493546 }
494547
495- return true ;
548+ return TrinaryLogic:: createYes () ;
496549 }
497550
498551}
0 commit comments