1818use PHPStan \Php \PhpVersion ;
1919use PHPStan \ShouldNotHappenException ;
2020use PHPStan \TrinaryLogic ;
21+ use PHPStan \Type \Accessory \AccessoryLowercaseStringType ;
2122use PHPStan \Type \Accessory \AccessoryNumericStringType ;
23+ use PHPStan \Type \Accessory \AccessoryUppercaseStringType ;
2224use PHPStan \Type \ArrayType ;
2325use PHPStan \Type \BooleanType ;
2426use PHPStan \Type \Constant \ConstantBooleanType ;
@@ -622,10 +624,10 @@ public function walkFunction($function): string
622624 $ type = $ this ->createFloat (false );
623625
624626 } elseif ($ castedExprType ->isNumericString ()->yes ()) {
625- $ type = $ this ->createNumericString (false );
627+ $ type = $ this ->createNumericString (false , $ castedExprType -> isLowercaseString ()-> yes (), $ castedExprType -> isUppercaseString ()-> yes () );
626628
627629 } else {
628- $ type = TypeCombinator::union ($ this ->createFloat (false ), $ this ->createNumericString (false ));
630+ $ type = TypeCombinator::union ($ this ->createFloat (false ), $ this ->createNumericString (false , false , true ));
629631 }
630632
631633 } else {
@@ -738,7 +740,7 @@ private function inferAvgFunction(AST\Functions\AvgFunction $function): Type
738740
739741 if ($ this ->driverType === DriverDetector::PDO_MYSQL || $ this ->driverType === DriverDetector::MYSQLI ) {
740742 if ($ exprTypeNoNull ->isInteger ()->yes ()) {
741- return $ this ->createNumericString ($ nullable );
743+ return $ this ->createNumericString ($ nullable, true , true );
742744 }
743745
744746 if ($ exprTypeNoNull ->isString ()->yes () && !$ exprTypeNoNull ->isNumericString ()->yes ()) {
@@ -750,7 +752,7 @@ private function inferAvgFunction(AST\Functions\AvgFunction $function): Type
750752
751753 if ($ this ->driverType === DriverDetector::PGSQL || $ this ->driverType === DriverDetector::PDO_PGSQL ) {
752754 if ($ exprTypeNoNull ->isInteger ()->yes ()) {
753- return $ this ->createNumericString ($ nullable );
755+ return $ this ->createNumericString ($ nullable, true , true );
754756 }
755757
756758 return $ this ->generalizeConstantType ($ exprType , $ nullable );
@@ -786,7 +788,7 @@ private function inferSumFunction(AST\Functions\SumFunction $function): Type
786788
787789 if ($ this ->driverType === DriverDetector::PDO_MYSQL || $ this ->driverType === DriverDetector::MYSQLI ) {
788790 if ($ exprTypeNoNull ->isInteger ()->yes ()) {
789- return $ this ->createNumericString ($ nullable );
791+ return $ this ->createNumericString ($ nullable, true , true );
790792 }
791793
792794 if ($ exprTypeNoNull ->isString ()->yes () && !$ exprTypeNoNull ->isNumericString ()->yes ()) {
@@ -800,7 +802,7 @@ private function inferSumFunction(AST\Functions\SumFunction $function): Type
800802 if ($ exprTypeNoNull ->isInteger ()->yes ()) {
801803 return TypeCombinator::union (
802804 $ this ->createInteger ($ nullable ),
803- $ this ->createNumericString ($ nullable ),
805+ $ this ->createNumericString ($ nullable, true , true ),
804806 );
805807 }
806808
@@ -837,19 +839,41 @@ private function createNonNegativeInteger(bool $nullable): Type
837839 return $ nullable ? TypeCombinator::addNull ($ integer ) : $ integer ;
838840 }
839841
840- private function createNumericString (bool $ nullable ): Type
842+ private function createNumericString (bool $ nullable, bool $ lowercase = false , bool $ uppercase = false ): Type
841843 {
842- $ numericString = TypeCombinator:: intersect (
844+ $ types = [
843845 new StringType (),
844846 new AccessoryNumericStringType (),
845- );
847+ ];
848+ if ($ lowercase ) {
849+ $ types [] = new AccessoryLowercaseStringType ();
850+ }
851+ if ($ uppercase ) {
852+ $ types [] = new AccessoryUppercaseStringType ();
853+ }
854+
855+ $ numericString = new IntersectionType ($ types );
846856
847857 return $ nullable ? TypeCombinator::addNull ($ numericString ) : $ numericString ;
848858 }
849859
850- private function createString (bool $ nullable ): Type
860+ private function createString (bool $ nullable, bool $ lowercase = false , bool $ uppercase = false ): Type
851861 {
852- $ string = new StringType ();
862+ if ($ lowercase || $ uppercase ) {
863+ $ types = [
864+ new StringType (),
865+ ];
866+ if ($ lowercase ) {
867+ $ types [] = new AccessoryLowercaseStringType ();
868+ }
869+ if ($ uppercase ) {
870+ $ types [] = new AccessoryUppercaseStringType ();
871+ }
872+ $ string = new IntersectionType ($ types );
873+ } else {
874+ $ string = new StringType ();
875+ }
876+
853877 return $ nullable ? TypeCombinator::addNull ($ string ) : $ string ;
854878 }
855879
@@ -895,10 +919,18 @@ private function generalizeConstantType(Type $type, bool $makeNullable): Type
895919 $ result = $ this ->createFloat ($ containsNull );
896920
897921 } elseif ($ typeNoNull ->isNumericString ()->yes ()) {
898- $ result = $ this ->createNumericString ($ containsNull );
922+ $ result = $ this ->createNumericString (
923+ $ containsNull ,
924+ $ typeNoNull ->isLowercaseString ()->yes (),
925+ $ typeNoNull ->isUppercaseString ()->yes (),
926+ );
899927
900928 } elseif ($ typeNoNull ->isString ()->yes ()) {
901- $ result = $ this ->createString ($ containsNull );
929+ $ result = $ this ->createString (
930+ $ containsNull ,
931+ $ typeNoNull ->isLowercaseString ()->yes (),
932+ $ typeNoNull ->isUppercaseString ()->yes (),
933+ );
902934
903935 } else {
904936 $ result = $ type ;
@@ -1241,7 +1273,7 @@ public function walkSelectExpression($selectExpression): string
12411273
12421274 // e.g. 1.0 on sqlite results to '1' with pdo_stringify on PHP 8.1, but '1.0' on PHP 8.0 with no setup
12431275 // so we relax constant types and return just numeric-string to avoid those issues
1244- $ stringifiedFloat = $ this ->createNumericString (false );
1276+ $ stringifiedFloat = $ this ->createNumericString (false , false , true );
12451277
12461278 if ($ stringify ->yes ()) {
12471279 return $ stringifiedFloat ;
@@ -1773,7 +1805,11 @@ private function inferPlusMinusTimesType(array $termTypes): Type
17731805 }
17741806
17751807 if ($ this ->containsOnlyTypes ($ unionWithoutNull , [new IntegerType (), $ this ->createNumericString (false )])) {
1776- return $ this ->createNumericString ($ nullable );
1808+ return $ this ->createNumericString (
1809+ $ nullable ,
1810+ $ unionWithoutNull ->toString ()->isLowercaseString ()->yes (),
1811+ $ unionWithoutNull ->toString ()->isUppercaseString ()->yes (),
1812+ );
17771813 }
17781814
17791815 if ($ this ->containsOnlyNumericTypes ($ unionWithoutNull )) {
@@ -1825,7 +1861,7 @@ private function inferDivisionType(array $termTypes): Type
18251861
18261862 if ($ unionWithoutNull ->isInteger ()->yes ()) {
18271863 if ($ this ->driverType === DriverDetector::MYSQLI || $ this ->driverType === DriverDetector::PDO_MYSQL ) {
1828- return $ this ->createNumericString ($ nullable );
1864+ return $ this ->createNumericString ($ nullable, true , true );
18291865 } elseif ($ this ->driverType === DriverDetector::PDO_PGSQL || $ this ->driverType === DriverDetector::PGSQL || $ this ->driverType === DriverDetector::SQLITE3 || $ this ->driverType === DriverDetector::PDO_SQLITE ) {
18301866 return $ this ->createInteger ($ nullable );
18311867 }
@@ -1853,7 +1889,11 @@ private function inferDivisionType(array $termTypes): Type
18531889 }
18541890
18551891 if ($ this ->containsOnlyTypes ($ unionWithoutNull , [new IntegerType (), $ this ->createNumericString (false )])) {
1856- return $ this ->createNumericString ($ nullable );
1892+ return $ this ->createNumericString (
1893+ $ nullable ,
1894+ $ unionWithoutNull ->toString ()->isLowercaseString ()->yes (),
1895+ $ unionWithoutNull ->toString ()->isUppercaseString ()->yes (),
1896+ );
18571897 }
18581898
18591899 if ($ this ->containsOnlyTypes ($ unionWithoutNull , [new FloatType (), $ this ->createNumericString (false )])) {
0 commit comments