28
28
use PHPStan \Type \Constant \ConstantBooleanType ;
29
29
use PHPStan \Type \Constant \ConstantFloatType ;
30
30
use PHPStan \Type \Constant \ConstantIntegerType ;
31
- use PHPStan \Type \Constant \ConstantStringType ;
32
31
use PHPStan \Type \ConstantTypeHelper ;
33
32
use PHPStan \Type \Doctrine \DescriptorNotRegisteredException ;
34
33
use PHPStan \Type \Doctrine \DescriptorRegistry ;
35
34
use PHPStan \Type \FloatType ;
36
- use PHPStan \Type \GeneralizePrecision ;
37
35
use PHPStan \Type \IntegerRangeType ;
38
36
use PHPStan \Type \IntegerType ;
39
37
use PHPStan \Type \IntersectionType ;
@@ -431,7 +429,9 @@ public function walkFunction($function): string
431
429
// ABS(col_string) => float float x x
432
430
433
431
$ exprType = $ this ->unmarshalType ($ this ->walkSimpleArithmeticExpression ($ function ->simpleArithmeticExpression ));
432
+ $ exprType = $ this ->castStringLiteralForFloatExpression ($ exprType );
434
433
$ exprType = $ this ->generalizeLiteralType ($ exprType , false );
434
+
435
435
$ exprTypeNoNull = TypeCombinator::removeNull ($ exprType );
436
436
$ nullable = TypeCombinator::containsNull ($ exprType );
437
437
@@ -564,16 +564,14 @@ public function walkFunction($function): string
564
564
$ firstExprType = $ this ->unmarshalType ($ this ->walkSimpleArithmeticExpression ($ function ->firstSimpleArithmeticExpression ));
565
565
$ secondExprType = $ this ->unmarshalType ($ this ->walkSimpleArithmeticExpression ($ function ->secondSimpleArithmeticExpression ));
566
566
567
- $ type = $ firstExprType ;
568
- $ typeNoNull = TypeCombinator::removeNull ($ type );
567
+ $ union = TypeCombinator:: union ( $ firstExprType, $ secondExprType ) ;
568
+ $ unionNoNull = TypeCombinator::removeNull ($ union );
569
569
570
- if (!$ typeNoNull ->isInteger ()->yes ()) {
570
+ if (!$ unionNoNull ->isInteger ()->yes ()) {
571
571
return $ this ->marshalType (new MixedType ()); // dont try to deal with non-integer chaos
572
572
}
573
573
574
- $ type = TypeCombinator::containsNull ($ type )
575
- ? TypeCombinator::addNull (IntegerRangeType::fromInterval (0 , null ))
576
- : IntegerRangeType::fromInterval (0 , null );
574
+ $ type = IntegerRangeType::fromInterval (0 , null );
577
575
578
576
if (TypeCombinator::containsNull ($ firstExprType ) || TypeCombinator::containsNull ($ secondExprType )) {
579
577
$ type = TypeCombinator::addNull ($ type );
@@ -1236,15 +1234,15 @@ public function walkSimpleSelectExpression($simpleSelectExpression): string
1236
1234
public function walkAggregateExpression ($ aggExpression ): string
1237
1235
{
1238
1236
switch (strtoupper ($ aggExpression ->functionName )) {
1239
- case 'MAX ' :
1240
1237
case 'AVG ' :
1241
1238
case 'SUM ' :
1242
- case 'MIN ' :
1243
- $ type = $ this ->unmarshalType (
1244
- $ this ->walkSimpleArithmeticExpression ($ aggExpression ->pathExpression )
1245
- );
1239
+ $ type = $ this ->unmarshalType ($ this ->walkSimpleArithmeticExpression ($ aggExpression ->pathExpression ));
1240
+ $ type = $ this ->castStringLiteralForNumericExpression ($ type );
1241
+ return $ this ->marshalType ($ type );
1246
1242
1247
- return $ this ->marshalType ($ type ); // nullability added in walkFunction
1243
+ case 'MAX ' :
1244
+ case 'MIN ' :
1245
+ return $ this ->walkSimpleArithmeticExpression ($ aggExpression ->pathExpression );
1248
1246
1249
1247
case 'COUNT ' :
1250
1248
return $ this ->marshalType (IntegerRangeType::fromInterval (0 , null ));
@@ -1254,6 +1252,52 @@ public function walkAggregateExpression($aggExpression): string
1254
1252
}
1255
1253
}
1256
1254
1255
+ /**
1256
+ * Numeric strings are kept as strings in literal usage, but casted to numeric value once used in numeric expression
1257
+ * - SELECT '1' => '1'
1258
+ * - SELECT 1 * '1' => 1
1259
+ */
1260
+ private function castStringLiteralForFloatExpression (Type $ type ): Type
1261
+ {
1262
+ if (!$ type instanceof DqlConstantStringType || $ type ->getOriginLiteralType () !== AST \Literal::STRING ) {
1263
+ return $ type ;
1264
+ }
1265
+
1266
+ $ value = $ type ->getValue ();
1267
+
1268
+ if (is_numeric ($ value )) {
1269
+ return new ConstantFloatType ((float ) $ value );
1270
+ }
1271
+
1272
+ return $ type ;
1273
+ }
1274
+
1275
+ /**
1276
+ * Numeric strings are kept as strings in literal usage, but casted to numeric value once used in numeric expression
1277
+ * - SELECT '1' => '1'
1278
+ * - SELECT 1 * '1' => 1
1279
+ */
1280
+ private function castStringLiteralForNumericExpression (Type $ type ): Type
1281
+ {
1282
+ if (!$ type instanceof DqlConstantStringType || $ type ->getOriginLiteralType () !== AST \Literal::STRING ) {
1283
+ return $ type ;
1284
+ }
1285
+
1286
+ $ driver = $ this ->em ->getConnection ()->getDriver ();
1287
+ $ isMysql = $ driver instanceof MysqliDriver || $ driver instanceof PdoMysqlDriver;
1288
+ $ value = $ type ->getValue ();
1289
+
1290
+ if (is_numeric ($ value )) {
1291
+ if (strpos ($ value , '. ' ) === false && strpos ($ value , 'e ' ) === false && !$ isMysql ) {
1292
+ return new ConstantIntegerType ((int ) $ value );
1293
+ }
1294
+
1295
+ return new ConstantFloatType ((float ) $ value );
1296
+ }
1297
+
1298
+ return $ type ;
1299
+ }
1300
+
1257
1301
/**
1258
1302
* @param AST\GroupByClause $groupByClause
1259
1303
*/
@@ -1393,21 +1437,12 @@ public function walkInParameter($inParam): string
1393
1437
public function walkLiteral ($ literal ): string
1394
1438
{
1395
1439
$ driver = $ this ->em ->getConnection ()->getDriver ();
1396
- $ isMysql = $ driver instanceof MysqliDriver || $ driver instanceof PdoMysqlDriver;
1397
1440
1398
1441
switch ($ literal ->type ) {
1399
1442
case AST \Literal::STRING :
1400
1443
$ value = $ literal ->value ;
1401
1444
assert (is_string ($ value ));
1402
- if (is_numeric ($ value )) {
1403
- if (strpos ($ value , '. ' ) === false && strpos ($ value , 'e ' ) === false && !$ isMysql ) {
1404
- $ type = new ConstantIntegerType ((int ) $ value );
1405
- } else {
1406
- $ type = new ConstantFloatType ((float ) $ value );
1407
- }
1408
- } else {
1409
- $ type = new ConstantStringType ($ value );
1410
- }
1445
+ $ type = new DqlConstantStringType ($ value , $ literal ->type );
1411
1446
break ;
1412
1447
1413
1448
case AST \Literal::BOOLEAN :
@@ -1435,10 +1470,10 @@ public function walkLiteral($literal): string
1435
1470
if (stripos ($ value , 'e ' ) !== false ) {
1436
1471
$ type = new ConstantFloatType ((float ) $ value );
1437
1472
} else {
1438
- $ type = new ConstantStringType ((string ) (float ) $ value );
1473
+ $ type = new DqlConstantStringType ((string ) (float ) $ value, $ literal -> type );
1439
1474
}
1440
1475
} elseif ($ driver instanceof PgSQLDriver || $ driver instanceof PdoPgSQLDriver) {
1441
- $ type = new ConstantStringType ((string ) (float ) $ value );
1476
+ $ type = new DqlConstantStringType ((string ) (float ) $ value, $ literal -> type );
1442
1477
1443
1478
} else {
1444
1479
$ type = new ConstantFloatType ((float ) $ value );
@@ -1528,11 +1563,9 @@ public function walkSimpleArithmeticExpression($simpleArithmeticExpr): string
1528
1563
continue ;
1529
1564
}
1530
1565
1531
- $ type = $ this ->unmarshalType ($ this ->walkArithmeticPrimary ($ term ));
1532
- if ($ term instanceof AST \Literal) {
1533
- $ type = $ type ->generalize (GeneralizePrecision::lessSpecific ()); // make '1' string, not numeric-string
1534
- }
1535
- $ types [] = $ type ;
1566
+ $ types [] = $ this ->castStringLiteralForNumericExpression (
1567
+ $ this ->unmarshalType ($ this ->walkArithmeticPrimary ($ term ))
1568
+ );
1536
1569
}
1537
1570
1538
1571
return $ this ->marshalType ($ this ->inferPlusMinusTimesType ($ types ));
@@ -1557,7 +1590,9 @@ public function walkArithmeticTerm($term): string
1557
1590
continue ; // Skip '*' or '/'
1558
1591
}
1559
1592
1560
- $ types [] = $ this ->unmarshalType ($ this ->walkArithmeticPrimary ($ factor ));
1593
+ $ types [] = $ this ->castStringLiteralForNumericExpression (
1594
+ $ this ->unmarshalType ($ this ->walkArithmeticPrimary ($ factor ))
1595
+ );
1561
1596
}
1562
1597
1563
1598
if (array_values ($ operators ) === ['* ' ]) {
@@ -1723,6 +1758,10 @@ private function inferDivisionType(array $termTypes): Type
1723
1758
return $ this ->createNumericString ($ nullable );
1724
1759
}
1725
1760
1761
+ if ($ this ->containsOnlyTypes ($ unionWithoutNull , [new FloatType (), $ this ->createNumericString (false )])) {
1762
+ return $ this ->createFloat ($ nullable );
1763
+ }
1764
+
1726
1765
if ($ this ->containsOnlyTypes ($ unionWithoutNull , [new IntegerType (), new StringType ()])) {
1727
1766
return $ this ->createFloat (true );
1728
1767
}
0 commit comments