Repository: phpstan/phpstan-doctrine (php)

Issue:
SUM() aggregation returns wrong type
I have the following code:

```php
$qb = $this->em->createQueryBuilder()
               ->addSelect('tvs.title')
               ->addSelect('tvs.broadcastDate')
               ->addSelect('COUNT(tva.id) AS count')
               ->addSelect('SUM(CASE WHEN tva.freePeriod = TRUE THEN 1 ELSE 0 END) AS freePeriodCount')
               ->addSelect('SUM(CASE WHEN tva.subscriptions IS NOT NULL THEN 0 ELSE 1 END) AS userStreamingCount')
               ->from(TvActivity::class, 'tva')
               ->join('tva.tvShow', 'tvs')
               ->addGroupBy('tvs.id');
$dbRows = $qb->getQuery()->getResult();
\PHPStan\dumpType($dbRows);
```

The dumped type is:

```
list<array{
    title: string,
    broadcastDate: DateTimeImmutable,
    count: int<0, max>|numeric-string,
    freePeriodCount: 0|1|'0'|'1'|null,
    userStreamingCount: 0|1|'0'|'1'|null,
}>
```

The last two fields can be arbitrary numbers, but are reported as 0 and 1 only. If it helps I can create a full reproduction repo, but hopefully things are clear like this. It's also wrong when doing `SUM(1)`, so the exact entity definitions might not matter.

Using phpstan-doctrine 1.3.40.

Pull request:
Fix aggregate function on litteral
Closes #460

Since you introduced this in https://github.com/phpstan/phpstan-doctrine/pull/232, I'll be happy to have your review @arnaud-lb 

When doing a SUM, I think the type must be "ungeneralize" (cf #460 )

When doing a AVG,
- if the value is not constant, I think the type must be "ungeneralize" too
- also, I got a failure without `$type = TypeCombinator::union($type, $type->toFloat(), $type->toFloat()->toString());` because the AVG value computed in my tests was `1.0` or `'1.0'` (even for `AVG(1)) and not `1` or `'1'` so I think we need to add the float result (and the stringified float result).

Patch:
diff --git a/src/Type/Doctrine/Query/QueryResultTypeWalker.php b/src/Type/Doctrine/Query/QueryResultTypeWalker.php
index c186a5e..1451992 100644
--- a/src/Type/Doctrine/Query/QueryResultTypeWalker.php
+++ b/src/Type/Doctrine/Query/QueryResultTypeWalker.php
@@ -933,12 +933,29 @@ class QueryResultTypeWalker extends SqlWalker
 		switch ($aggExpression->functionName) {
 			case 'MAX':
 			case 'MIN':
+				$type = $this->unmarshalType(
+					$this->walkSimpleArithmeticExpression($aggExpression->pathExpression)
+				);
+
+				return $this->marshalType(TypeCombinator::addNull($type));
+
 			case 'AVG':
+				$type = $this->unmarshalType(
+					$this->walkSimpleArithmeticExpression($aggExpression->pathExpression)
+				);
+
+				$type = TypeCombinator::union($type, $type->toFloat());
+				$type = TypeUtils::generalizeType($type, GeneralizePrecision::lessSpecific());
+
+				return $this->marshalType(TypeCombinator::addNull($type));
+
 			case 'SUM':
 				$type = $this->unmarshalType(
 					$this->walkSimpleArithmeticExpression($aggExpression->pathExpression)
 				);
 
+				$type = TypeUtils::generalizeType($type, GeneralizePrecision::lessSpecific());
+
 				return $this->marshalType(TypeCombinator::addNull($type));
 
 			case 'COUNT':


Test patch:
diff --git a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php
index 27d1aca..ea1e0ae 100644
--- a/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php
+++ b/tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php
@@ -681,6 +681,82 @@ final class QueryResultTypeWalkerTest extends PHPStanTestCase
 			',
 		];
 
+		yield 'aggregate on literal' => [
+			$this->constantArray([
+				[
+					new ConstantIntegerType(1),
+					TypeCombinator::union(
+						new ConstantStringType('1'),
+						new ConstantIntegerType(1),
+						new NullType()
+					),
+				],
+				[
+					new ConstantIntegerType(2),
+					TypeCombinator::union(
+						new ConstantStringType('0'),
+						new ConstantIntegerType(0),
+						new ConstantStringType('1'),
+						new ConstantIntegerType(1),
+						new NullType()
+					),
+				],
+				[
+					new ConstantIntegerType(3),
+					TypeCombinator::union(
+						new ConstantStringType('1'),
+						new ConstantIntegerType(1),
+						new NullType()
+					),
+				],
+				[
+					new ConstantIntegerType(4),
+					TypeCombinator::union(
+						new ConstantStringType('0'),
+						new ConstantIntegerType(0),
+						new ConstantStringType('1'),
+						new ConstantIntegerType(1),
+						new NullType()
+					),
+				],
+				[
+					new ConstantIntegerType(5),
+					TypeCombinator::union(
+						$this->intStringified(),
+						new FloatType(),
+						new NullType()
+					),
+				],
+				[
+					new ConstantIntegerType(6),
+					TypeCombinator::union(
+						$this->intStringified(),
+						new FloatType(),
+						new NullType()
+					),
+				],
+				[
+					new ConstantIntegerType(7),
+					TypeCombinator::addNull($this->intStringified()),
+				],
+				[
+					new ConstantIntegerType(8),
+					TypeCombinator::addNull($this->intStringified()),
+				],
+			]),
+			'
+				SELECT		MAX(1),
+							MAX(CASE WHEN m.intColumn = 0 THEN 1 ELSE 0 END),
+							MIN(1),
+							MIN(CASE WHEN m.intColumn = 0 THEN 1 ELSE 0 END),
+							AVG(1),
+							AVG(CASE WHEN m.intColumn = 0 THEN 1 ELSE 0 END),
+							SUM(1),
+							SUM(CASE WHEN m.intColumn = 0 THEN 1 ELSE 0 END)
+				FROM		QueryResult\Entities\Many m
+			',
+		];
+
 		yield 'literal' => [
 			$this->constantArray([
 				[