66use PHPStan \Analyser \Scope ;
77use PHPStan \DependencyInjection \AutowiredService ;
88use PHPStan \Reflection \FunctionReflection ;
9+ use PHPStan \TrinaryLogic ;
910use PHPStan \Type \BenevolentUnionType ;
1011use PHPStan \Type \Constant \ConstantBooleanType ;
12+ use PHPStan \Type \Constant \ConstantStringType ;
1113use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
12- use PHPStan \Type \MixedType ;
1314use PHPStan \Type \StringType ;
1415use PHPStan \Type \Type ;
1516use PHPStan \Type \UnionType ;
17+ use function base64_decode ;
1618
1719#[AutowiredService]
1820final class Base64DecodeDynamicFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
@@ -29,32 +31,41 @@ public function getTypeFromFunctionCall(
2931 Scope $ scope ,
3032 ): Type
3133 {
32- if (!isset ($ functionCall ->getArgs ()[1 ])) {
34+ if (!isset ($ functionCall ->getArgs ()[0 ])) {
3335 return new StringType ();
3436 }
3537
36- $ argType = $ scope ->getType ($ functionCall ->getArgs ()[1 ]->value );
37-
38- if ($ argType instanceof MixedType) {
39- return new BenevolentUnionType ([new StringType (), new ConstantBooleanType (false )]);
38+ $ stringArgNode = $ functionCall ->getArgs ()[0 ]->value ;
39+ $ constantStrings = $ scope ->getType ($ stringArgNode )->getConstantStrings ();
40+ if ($ constantStrings !== []) {
41+ $ isValidBase64 = TrinaryLogic::lazyExtremeIdentity (
42+ $ constantStrings ,
43+ static function (ConstantStringType $ constantString ): TrinaryLogic {
44+ $ isValid = base64_decode ($ constantString ->getValue (), true ) !== false ;
45+ return TrinaryLogic::createFromBoolean ($ isValid );
46+ },
47+ );
48+ } else {
49+ $ isValidBase64 = TrinaryLogic::createMaybe ();
4050 }
4151
42- $ isTrueType = $ argType -> isTrue ();
43- $ isFalseType = $ argType -> isFalse () ;
44- $ compareTypes = $ isTrueType -> compareTo ( $ isFalseType );
45- if ( $ compareTypes === $ isTrueType ) {
46- return new UnionType ([ new StringType (), new ConstantBooleanType ( false )] );
52+ if ( isset ( $ functionCall -> getArgs ()[ 1 ])) {
53+ $ strictArgNode = $ functionCall -> getArgs ()[ 1 ]-> value ;
54+ $ isStrict = $ scope -> getType ( $ strictArgNode )-> toBoolean ()-> toTrinaryLogic ( );
55+ } else {
56+ $ isStrict = TrinaryLogic:: createNo ( );
4757 }
48- if ($ compareTypes === $ isFalseType ) {
58+
59+ if ($ isStrict ->no () || $ isValidBase64 ->yes ()) {
4960 return new StringType ();
5061 }
51-
52- // second argument could be interpreted as true
53- if (!$ isTrueType ->no ()) {
54- return new UnionType ([new StringType (), new ConstantBooleanType (false )]);
62+ if ($ isStrict ->yes () && $ isValidBase64 ->no ()) {
63+ return new ConstantBooleanType (false );
5564 }
56-
57- return new StringType ();
65+ if ($ isStrict ->maybe () && $ isValidBase64 ->maybe ()) {
66+ return new BenevolentUnionType ([new StringType (), new ConstantBooleanType (false )]);
67+ }
68+ return new UnionType ([new StringType (), new ConstantBooleanType (false )]);
5869 }
5970
6071}
0 commit comments