Skip to content

Commit 490aa43

Browse files
committed
Fix wrong coalesce type inference
1 parent 3369068 commit 490aa43

File tree

2 files changed

+99
-16
lines changed

2 files changed

+99
-16
lines changed

src/Type/Doctrine/Query/QueryResultTypeWalker.php

+83-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Doctrine\ORM\Query\ParserResult;
1515
use Doctrine\ORM\Query\SqlWalker;
1616
use PHPStan\ShouldNotHappenException;
17+
use PHPStan\Type\Accessory\AccessoryNumericStringType;
1718
use PHPStan\Type\BooleanType;
1819
use PHPStan\Type\Constant\ConstantBooleanType;
1920
use PHPStan\Type\Constant\ConstantFloatType;
@@ -575,6 +576,79 @@ public function walkFunction($function): string
575576
}
576577
}
577578

579+
private function createFloat(bool $nullable): Type
580+
{
581+
$float = new FloatType();
582+
return $nullable ? TypeCombinator::addNull($float) : $float;
583+
}
584+
585+
private function createFloatOrInt(bool $nullable): Type // @phpstan-ignore-line unused, but kept to ease conflict resolution with origin (#506)
586+
{
587+
$union = TypeCombinator::union(
588+
new FloatType(),
589+
new IntegerType()
590+
);
591+
return $nullable ? TypeCombinator::addNull($union) : $union;
592+
}
593+
594+
private function createInteger(bool $nullable): Type
595+
{
596+
$integer = new IntegerType();
597+
return $nullable ? TypeCombinator::addNull($integer) : $integer;
598+
}
599+
600+
private function createNonNegativeInteger(bool $nullable): Type // @phpstan-ignore-line unused, but kept to ease conflict resolution with origin (#506)
601+
{
602+
$integer = IntegerRangeType::fromInterval(0, null);
603+
return $nullable ? TypeCombinator::addNull($integer) : $integer;
604+
}
605+
606+
private function createNumericString(bool $nullable): Type
607+
{
608+
$numericString = TypeCombinator::intersect(
609+
new StringType(),
610+
new AccessoryNumericStringType()
611+
);
612+
613+
return $nullable ? TypeCombinator::addNull($numericString) : $numericString;
614+
}
615+
616+
private function createString(bool $nullable): Type
617+
{
618+
$string = new StringType();
619+
return $nullable ? TypeCombinator::addNull($string) : $string;
620+
}
621+
622+
/**
623+
* E.g. to ensure SUM(1) is inferred as int, not 1
624+
*/
625+
private function generalizeConstantType(Type $type, bool $makeNullable): Type
626+
{
627+
$containsNull = $this->canBeNull($type);
628+
$typeNoNull = TypeCombinator::removeNull($type);
629+
630+
if (!$typeNoNull->isConstantScalarValue()->yes()) {
631+
$result = $type;
632+
633+
} elseif ($typeNoNull->isInteger()->yes()) {
634+
$result = $this->createInteger($containsNull);
635+
636+
} elseif ($typeNoNull->isFloat()->yes()) {
637+
$result = $this->createFloat($containsNull);
638+
639+
} elseif ($typeNoNull->isNumericString()->yes()) {
640+
$result = $this->createNumericString($containsNull);
641+
642+
} elseif ($typeNoNull->isString()->yes()) {
643+
$result = $this->createString($containsNull);
644+
645+
} else {
646+
$result = $type;
647+
}
648+
649+
return $makeNullable ? TypeCombinator::addNull($result) : $result;
650+
}
651+
578652
/**
579653
* @param AST\OrderByClause $orderByClause
580654
*/
@@ -642,7 +716,10 @@ public function walkCoalesceExpression($coalesceExpression): string
642716
$type = $this->unmarshalType($expression->dispatch($this));
643717
$allTypesContainNull = $allTypesContainNull && TypeCombinator::containsNull($type);
644718

645-
$expressionTypes[] = $type;
719+
// Some drivers manipulate the types, lets avoid false positives by generalizing constant types
720+
// e.g. sqlsrv: "COALESCE returns the data type of value with the highest precedence"
721+
// e.g. mysql: COALESCE(1, 'foo') === '1' (undocumented? https://gist.github.com/jrunning/4535434)
722+
$expressionTypes[] = $this->generalizeConstantType($type, false);
646723
}
647724

648725
$type = TypeCombinator::union(...$expressionTypes);
@@ -1400,6 +1477,11 @@ private function resolveDatabaseInternalType(string $typeName, ?string $enumType
14001477
return $type;
14011478
}
14021479

1480+
private function canBeNull(Type $type): bool
1481+
{
1482+
return !$type->isSuperTypeOf(new NullType())->no();
1483+
}
1484+
14031485
private function toNumericOrNull(Type $type): Type
14041486
{
14051487
return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type {

tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php

+16-15
Original file line numberDiff line numberDiff line change
@@ -548,26 +548,18 @@ public function getTestData(): iterable
548548
$this->constantArray([
549549
[
550550
new ConstantIntegerType(1),
551-
TypeCombinator::union(
552-
new ConstantStringType('a'),
553-
new ConstantStringType('b')
554-
),
551+
new StringType(),
555552
],
556553
[
557554
new ConstantIntegerType(2),
558555
TypeCombinator::union(
559-
new ConstantIntegerType(1),
560-
new ConstantIntegerType(2),
561-
new ConstantStringType('1'),
562-
new ConstantStringType('2')
556+
$this->numericString(),
557+
new IntegerType()
563558
),
564559
],
565560
[
566561
new ConstantIntegerType(3),
567-
TypeCombinator::union(
568-
new ConstantStringType('1'),
569-
new ConstantStringType('2')
570-
),
562+
$this->numericString(),
571563
],
572564
]),
573565
'
@@ -842,11 +834,20 @@ public function getTestData(): iterable
842834
new ConstantIntegerType(3),
843835
$this->intStringified(),
844836
],
837+
[
838+
new ConstantIntegerType(4),
839+
TypeCombinator::union(
840+
new IntegerType(),
841+
new FloatType(),
842+
$this->numericString()
843+
),
844+
],
845845
]),
846846
'
847847
SELECT COALESCE(m.stringNullColumn, m.intColumn, false),
848848
COALESCE(m.stringNullColumn, m.stringNullColumn),
849-
COALESCE(NULLIF(m.intColumn, 1), 0)
849+
COALESCE(NULLIF(m.intColumn, 1), 0),
850+
COALESCE(1, 1.1)
850851
FROM QueryResult\Entities\Many m
851852
',
852853
];
@@ -1142,8 +1143,8 @@ public function getTestData(): iterable
11421143
[
11431144
new ConstantIntegerType(2),
11441145
TypeCombinator::union(
1145-
new ConstantIntegerType(1),
1146-
new ConstantStringType('1')
1146+
new IntegerType(),
1147+
$this->numericString()
11471148
),
11481149
],
11491150
[

0 commit comments

Comments
 (0)