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 ;
@@ -31,26 +29,32 @@ final class HashFunctionsReturnTypeExtension implements DynamicFunctionReturnTyp
3129 'hash ' => [
3230 'cryptographic ' => false ,
3331 'possiblyFalse ' => false ,
32+ 'binary ' => 2 ,
3433 ],
3534 'hash_file ' => [
3635 'cryptographic ' => false ,
3736 'possiblyFalse ' => true ,
37+ 'binary ' => 2 ,
3838 ],
3939 'hash_hkdf ' => [
4040 'cryptographic ' => true ,
4141 'possiblyFalse ' => false ,
42+ 'binary ' => true ,
4243 ],
4344 'hash_hmac ' => [
4445 'cryptographic ' => true ,
4546 'possiblyFalse ' => false ,
47+ 'binary ' => 3 ,
4648 ],
4749 'hash_hmac_file ' => [
4850 'cryptographic ' => true ,
4951 'possiblyFalse ' => true ,
52+ 'binary ' => 3 ,
5053 ],
5154 'hash_pbkdf2 ' => [
5255 'cryptographic ' => true ,
5356 'possiblyFalse ' => false ,
57+ 'binary ' => 5 ,
5458 ],
5559 ];
5660
@@ -87,50 +91,56 @@ public function isFunctionSupported(FunctionReflection $functionReflection): boo
8791 return isset (self ::SUPPORTED_FUNCTIONS [$ name ]);
8892 }
8993
90- public function getTypeFromFunctionCall (FunctionReflection $ functionReflection , FuncCall $ functionCall , Scope $ scope ): Type
94+ public function getTypeFromFunctionCall (FunctionReflection $ functionReflection , FuncCall $ functionCall , Scope $ scope ): ? Type
9195 {
92- $ defaultReturnType = ParametersAcceptorSelector::selectFromArgs (
93- $ scope ,
94- $ functionCall ->getArgs (),
95- $ functionReflection ->getVariants (),
96- )->getReturnType ();
97-
9896 if (!isset ($ functionCall ->getArgs ()[0 ])) {
99- return $ defaultReturnType ;
97+ return null ;
10098 }
10199
102- $ algorithmType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
103- if ($ algorithmType instanceof MixedType) {
104- return TypeUtils::toBenevolentUnion ($ defaultReturnType );
100+ $ functionData = self ::SUPPORTED_FUNCTIONS [strtolower ($ functionReflection ->getName ())];
101+ if (\is_bool ($ functionData ['binary ' ])) {
102+ $ binaryType = new ConstantBooleanType (\is_bool ($ functionData ['binary ' ]));
103+ } elseif (isset ($ functionCall ->getArgs ()[$ functionData ['binary ' ]])) {
104+ $ binaryType = $ scope ->getType ($ functionCall ->getArgs ()[$ functionData ['binary ' ]]->value );
105+ } else {
106+ $ binaryType = new ConstantBooleanType (false );
107+ }
108+
109+ $ stringTypes = [
110+ new StringType (),
111+ new AccessoryNonFalsyStringType (),
112+ ];
113+ if ($ binaryType ->isFalse ()->yes ()) {
114+ $ stringTypes [] = new AccessoryLowercaseStringType ();
105115 }
116+ $ stringReturnType = new IntersectionType ($ stringTypes );
106117
118+ $ algorithmType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
107119 $ constantAlgorithmTypes = $ algorithmType ->getConstantStrings ();
120+ if (count ($ constantAlgorithmTypes ) === 0 ) {
121+ $ returnType = $ stringReturnType ;
122+
123+ if ($ functionData ['possiblyFalse ' ]) {
124+ $ returnType = TypeCombinator::union ($ returnType , new ConstantBooleanType (false ));
125+ }
108126
109- if ($ constantAlgorithmTypes === []) {
110- return TypeUtils::toBenevolentUnion ($ defaultReturnType );
127+ return TypeUtils::toBenevolentUnion ($ returnType );
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