66use PHPStan \Analyser \Scope ;
77use PHPStan \Php \PhpVersion ;
88use PHPStan \Reflection \FunctionReflection ;
9- use PHPStan \Reflection \ParametersAcceptorSelector ;
109use PHPStan \Type \Accessory \AccessoryLowercaseStringType ;
1110use PHPStan \Type \Accessory \AccessoryNonFalsyStringType ;
1211use PHPStan \Type \Constant \ConstantBooleanType ;
1312use PHPStan \Type \Constant \ConstantStringType ;
1413use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
1514use PHPStan \Type \IntersectionType ;
16- use PHPStan \Type \MixedType ;
1715use PHPStan \Type \NeverType ;
1816use PHPStan \Type \StringType ;
1917use PHPStan \Type \Type ;
2018use PHPStan \Type \TypeCombinator ;
2119use PHPStan \Type \TypeUtils ;
2220use function array_map ;
21+ use function count ;
2322use function hash_algos ;
2423use function in_array ;
24+ use function is_bool ;
2525use function strtolower ;
2626
2727final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTypeExtension
@@ -31,26 +31,32 @@ final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTyp
3131 'hash ' => [
3232 'cryptographic ' => false ,
3333 'possiblyFalse ' => false ,
34+ 'binary ' => 2 ,
3435 ],
3536 'hash_file ' => [
3637 'cryptographic ' => false ,
3738 'possiblyFalse ' => true ,
39+ 'binary ' => 2 ,
3840 ],
3941 'hash_hkdf ' => [
4042 'cryptographic ' => true ,
4143 'possiblyFalse ' => false ,
44+ 'binary ' => true ,
4245 ],
4346 'hash_hmac ' => [
4447 'cryptographic ' => true ,
4548 'possiblyFalse ' => false ,
49+ 'binary ' => 3 ,
4650 ],
4751 'hash_hmac_file ' => [
4852 'cryptographic ' => true ,
4953 'possiblyFalse ' => true ,
54+ 'binary ' => 3 ,
5055 ],
5156 'hash_pbkdf2 ' => [
5257 'cryptographic ' => true ,
5358 'possiblyFalse ' => false ,
59+ 'binary ' => 5 ,
5460 ],
5561 ];
5662
@@ -87,50 +93,54 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
8793 return isset (self ::SUPPORTED_FUNCTIONS [$ name ]);
8894 }
8995
90- public function getTypeFromFunctionCall (FunctionReflection $ functionReflection , FuncCall $ functionCall , Scope $ scope ): Type
96+ public function getTypeFromFunctionCall (FunctionReflection $ functionReflection , FuncCall $ functionCall , Scope $ scope ): ? Type
9197 {
92- $ defaultReturnType = ParametersAcceptorSelector::selectFromArgs (
93- $ scope ,
94- $ functionCall ->getArgs (),
95- $ functionReflection ->getVariants (),
96- )->getReturnType ();
97-
9898 if (!isset ($ functionCall ->getArgs ()[0 ])) {
99- return $ defaultReturnType ;
99+ return null ;
100100 }
101101
102- $ algorithmType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
103- if ($ algorithmType instanceof MixedType) {
104- return TypeUtils::toBenevolentUnion ($ defaultReturnType );
102+ $ functionData = self ::SUPPORTED_FUNCTIONS [strtolower ($ functionReflection ->getName ())];
103+ if (is_bool ($ functionData ['binary ' ])) {
104+ $ binaryType = new ConstantBooleanType ($ functionData ['binary ' ]);
105+ } elseif (isset ($ functionCall ->getArgs ()[$ functionData ['binary ' ]])) {
106+ $ binaryType = $ scope ->getType ($ functionCall ->getArgs ()[$ functionData ['binary ' ]]->value );
107+ } else {
108+ $ binaryType = new ConstantBooleanType (false );
109+ }
110+
111+ $ stringTypes = [
112+ new StringType (),
113+ new AccessoryNonFalsyStringType (),
114+ ];
115+ if ($ binaryType ->isFalse ()->yes ()) {
116+ $ stringTypes [] = new AccessoryLowercaseStringType ();
105117 }
118+ $ stringReturnType = new IntersectionType ($ stringTypes );
106119
120+ $ algorithmType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
107121 $ constantAlgorithmTypes = $ algorithmType ->getConstantStrings ();
122+ if (count ($ constantAlgorithmTypes ) === 0 ) {
123+ if ($ functionData ['possiblyFalse ' ]) {
124+ return TypeUtils::toBenevolentUnion (TypeCombinator::union ($ stringReturnType , new ConstantBooleanType (false )));
125+ }
108126
109- if ($ constantAlgorithmTypes === []) {
110- return TypeUtils::toBenevolentUnion ($ defaultReturnType );
127+ return $ stringReturnType ;
111128 }
112129
113130 $ neverType = new NeverType ();
114131 $ falseType = new ConstantBooleanType (false );
115- $ nonFalsyLowercaseString = new IntersectionType ([
116- new StringType (),
117- new AccessoryNonFalsyStringType (),
118- new AccessoryLowercaseStringType (),
119- ]);
120-
121132 $ invalidAlgorithmType = $ this ->phpVersion ->throwsValueErrorForInternalFunctions () ? $ neverType : $ falseType ;
122- $ functionData = self ::SUPPORTED_FUNCTIONS [strtolower ($ functionReflection ->getName ())];
123133
124134 $ returnTypes = array_map (
125- function (ConstantStringType $ type ) use ($ functionData , $ nonFalsyLowercaseString , $ invalidAlgorithmType ) {
135+ function (ConstantStringType $ type ) use ($ functionData , $ stringReturnType , $ invalidAlgorithmType ) {
126136 $ algorithm = strtolower ($ type ->getValue ());
127137 if (!in_array ($ algorithm , $ this ->hashAlgorithms , true )) {
128138 return $ invalidAlgorithmType ;
129139 }
130140 if ($ functionData ['cryptographic ' ] && in_array ($ algorithm , self ::NON_CRYPTOGRAPHIC_ALGORITHMS , true )) {
131141 return $ invalidAlgorithmType ;
132142 }
133- return $ nonFalsyLowercaseString ;
143+ return $ stringReturnType ;
134144 },
135145 $ constantAlgorithmTypes ,
136146 );
0 commit comments