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 ;
@@ -630,10 +632,10 @@ public function walkFunction($function): string
630632 $ type = $ this ->createFloat (false );
631633
632634 } elseif ($ castedExprType ->isNumericString ()->yes ()) {
633- $ type = $ this ->createNumericString (false );
635+ $ type = $ this ->createNumericString (false , $ castedExprType -> isLowercaseString ()-> yes (), $ castedExprType -> isUppercaseString ()-> yes () );
634636
635637 } else {
636- $ type = TypeCombinator::union ($ this ->createFloat (false ), $ this ->createNumericString (false ));
638+ $ type = TypeCombinator::union ($ this ->createFloat (false ), $ this ->createNumericString (false , false , true ));
637639 }
638640
639641 } else {
@@ -746,7 +748,7 @@ private function inferAvgFunction(AST\Functions\AvgFunction $function): Type
746748
747749 if ($ this ->driverType === DriverDetector::PDO_MYSQL || $ this ->driverType === DriverDetector::MYSQLI ) {
748750 if ($ exprTypeNoNull ->isInteger ()->yes ()) {
749- return $ this ->createNumericString ($ nullable );
751+ return $ this ->createNumericString ($ nullable, true , true );
750752 }
751753
752754 if ($ exprTypeNoNull ->isString ()->yes () && !$ exprTypeNoNull ->isNumericString ()->yes ()) {
@@ -758,7 +760,7 @@ private function inferAvgFunction(AST\Functions\AvgFunction $function): Type
758760
759761 if ($ this ->driverType === DriverDetector::PGSQL || $ this ->driverType === DriverDetector::PDO_PGSQL ) {
760762 if ($ exprTypeNoNull ->isInteger ()->yes ()) {
761- return $ this ->createNumericString ($ nullable );
763+ return $ this ->createNumericString ($ nullable, true , true );
762764 }
763765
764766 return $ this ->generalizeConstantType ($ exprType , $ nullable );
@@ -794,7 +796,7 @@ private function inferSumFunction(AST\Functions\SumFunction $function): Type
794796
795797 if ($ this ->driverType === DriverDetector::PDO_MYSQL || $ this ->driverType === DriverDetector::MYSQLI ) {
796798 if ($ exprTypeNoNull ->isInteger ()->yes ()) {
797- return $ this ->createNumericString ($ nullable );
799+ return $ this ->createNumericString ($ nullable, true , true );
798800 }
799801
800802 if ($ exprTypeNoNull ->isString ()->yes () && !$ exprTypeNoNull ->isNumericString ()->yes ()) {
@@ -808,7 +810,7 @@ private function inferSumFunction(AST\Functions\SumFunction $function): Type
808810 if ($ exprTypeNoNull ->isInteger ()->yes ()) {
809811 return TypeCombinator::union (
810812 $ this ->createInteger ($ nullable ),
811- $ this ->createNumericString ($ nullable )
813+ $ this ->createNumericString ($ nullable, true , true )
812814 );
813815 }
814816
@@ -845,19 +847,41 @@ private function createNonNegativeInteger(bool $nullable): Type
845847 return $ nullable ? TypeCombinator::addNull ($ integer ) : $ integer ;
846848 }
847849
848- private function createNumericString (bool $ nullable ): Type
850+ private function createNumericString (bool $ nullable, bool $ lowercase = false , bool $ uppercase = false ): Type
849851 {
850- $ numericString = TypeCombinator:: intersect (
852+ $ types = [
851853 new StringType (),
852- new AccessoryNumericStringType ()
853- );
854+ new AccessoryNumericStringType (),
855+ ];
856+ if ($ lowercase ) {
857+ $ types [] = new AccessoryLowercaseStringType ();
858+ }
859+ if ($ uppercase ) {
860+ $ types [] = new AccessoryUppercaseStringType ();
861+ }
862+
863+ $ numericString = new IntersectionType ($ types );
854864
855865 return $ nullable ? TypeCombinator::addNull ($ numericString ) : $ numericString ;
856866 }
857867
858- private function createString (bool $ nullable ): Type
868+ private function createString (bool $ nullable, bool $ lowercase = false , bool $ uppercase = false ): Type
859869 {
860- $ string = new StringType ();
870+ if ($ lowercase || $ uppercase ) {
871+ $ types = [
872+ new StringType (),
873+ ];
874+ if ($ lowercase ) {
875+ $ types [] = new AccessoryLowercaseStringType ();
876+ }
877+ if ($ uppercase ) {
878+ $ types [] = new AccessoryUppercaseStringType ();
879+ }
880+ $ string = new IntersectionType ($ types );
881+ } else {
882+ $ string = new StringType ();
883+ }
884+
861885 return $ nullable ? TypeCombinator::addNull ($ string ) : $ string ;
862886 }
863887
@@ -903,10 +927,18 @@ private function generalizeConstantType(Type $type, bool $makeNullable): Type
903927 $ result = $ this ->createFloat ($ containsNull );
904928
905929 } elseif ($ typeNoNull ->isNumericString ()->yes ()) {
906- $ result = $ this ->createNumericString ($ containsNull );
930+ $ result = $ this ->createNumericString (
931+ $ containsNull ,
932+ $ typeNoNull ->isLowercaseString ()->yes (),
933+ $ typeNoNull ->isUppercaseString ()->yes ()
934+ );
907935
908936 } elseif ($ typeNoNull ->isString ()->yes ()) {
909- $ result = $ this ->createString ($ containsNull );
937+ $ result = $ this ->createString (
938+ $ containsNull ,
939+ $ typeNoNull ->isLowercaseString ()->yes (),
940+ $ typeNoNull ->isUppercaseString ()->yes ()
941+ );
910942
911943 } else {
912944 $ result = $ type ;
@@ -1249,7 +1281,7 @@ public function walkSelectExpression($selectExpression): string
12491281
12501282 // 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
12511283 // so we relax constant types and return just numeric-string to avoid those issues
1252- $ stringifiedFloat = $ this ->createNumericString (false );
1284+ $ stringifiedFloat = $ this ->createNumericString (false , false , true );
12531285
12541286 if ($ stringify ->yes ()) {
12551287 return $ stringifiedFloat ;
@@ -1781,7 +1813,11 @@ private function inferPlusMinusTimesType(array $termTypes): Type
17811813 }
17821814
17831815 if ($ this ->containsOnlyTypes ($ unionWithoutNull , [new IntegerType (), $ this ->createNumericString (false )])) {
1784- return $ this ->createNumericString ($ nullable );
1816+ return $ this ->createNumericString (
1817+ $ nullable ,
1818+ $ unionWithoutNull ->toString ()->isLowercaseString ()->yes (),
1819+ $ unionWithoutNull ->toString ()->isUppercaseString ()->yes ()
1820+ );
17851821 }
17861822
17871823 if ($ this ->containsOnlyNumericTypes ($ unionWithoutNull )) {
@@ -1833,7 +1869,7 @@ private function inferDivisionType(array $termTypes): Type
18331869
18341870 if ($ unionWithoutNull ->isInteger ()->yes ()) {
18351871 if ($ this ->driverType === DriverDetector::MYSQLI || $ this ->driverType === DriverDetector::PDO_MYSQL ) {
1836- return $ this ->createNumericString ($ nullable );
1872+ return $ this ->createNumericString ($ nullable, true , true );
18371873 } elseif ($ this ->driverType === DriverDetector::PDO_PGSQL || $ this ->driverType === DriverDetector::PGSQL || $ this ->driverType === DriverDetector::SQLITE3 || $ this ->driverType === DriverDetector::PDO_SQLITE ) {
18381874 return $ this ->createInteger ($ nullable );
18391875 }
@@ -1861,7 +1897,11 @@ private function inferDivisionType(array $termTypes): Type
18611897 }
18621898
18631899 if ($ this ->containsOnlyTypes ($ unionWithoutNull , [new IntegerType (), $ this ->createNumericString (false )])) {
1864- return $ this ->createNumericString ($ nullable );
1900+ return $ this ->createNumericString (
1901+ $ nullable ,
1902+ $ unionWithoutNull ->toString ()->isLowercaseString ()->yes (),
1903+ $ unionWithoutNull ->toString ()->isUppercaseString ()->yes ()
1904+ );
18651905 }
18661906
18671907 if ($ this ->containsOnlyTypes ($ unionWithoutNull , [new FloatType (), $ this ->createNumericString (false )])) {
0 commit comments